From b223d2d25433c7ad7e044f7440e532f563c2aa75 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 3 Apr 2025 16:32:00 +0200 Subject: [PATCH 001/796] PG-1437 Fix race condition in the event trigger If we look up something about a relation in the command start event trigger we need to hold onto that lock until the end of the command and not release it so no other commd is able to change the data we looked at between now and when the DDL command itself actually locks the relation. --- contrib/pg_tde/src/pg_tde_event_capture.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 9683d4a56ea97..8274fa316c459 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -122,7 +122,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (IsA(parsetree, IndexStmt)) { IndexStmt *stmt = (IndexStmt *) parsetree; - Oid relationId = RangeVarGetRelid(stmt->relation, NoLock, true); + Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); @@ -132,8 +132,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (relationId != InvalidOid) { - LOCKMODE lockmode = AccessShareLock; /* TODO. Verify lock mode? */ - Relation rel = table_open(relationId, lockmode); + Relation rel = table_open(relationId, NoLock); if (rel->rd_rel->relam == get_tde_table_am_oid()) { @@ -141,7 +140,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) /* set the global state */ tdeCurrentCreateEvent.encryptMode = true; } - table_close(rel, lockmode); + + table_close(rel, NoLock); if (tdeCurrentCreateEvent.encryptMode) { @@ -181,7 +181,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) { AlterTableStmt *stmt = (AlterTableStmt *) parsetree; ListCell *lcmd; - Oid relationId = RangeVarGetRelid(stmt->relation, NoLock, true); + Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); @@ -216,8 +216,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (relationId != InvalidOid) { - LOCKMODE lockmode = AccessShareLock; - Relation rel = relation_open(relationId, lockmode); + Relation rel = relation_open(relationId, NoLock); if (rel->rd_rel->relam == get_tde_table_am_oid()) { @@ -227,7 +226,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) */ tdeCurrentCreateEvent.encryptMode = true; } - relation_close(rel, lockmode); + + relation_close(rel, NoLock); if (tdeCurrentCreateEvent.encryptMode) { From 7ea53b41766e65ad13a9e9019a7bc21012a1f8e8 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 27 Mar 2025 23:49:54 +0100 Subject: [PATCH 002/796] PG-1455 Add field with random base IV to relation and WAL keys For now we do not use this field but we plan to use it when encrypting Wrelation data and WAL. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 5 +++++ contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 5392b976097b2..6eb0b51757dfb 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -194,6 +194,11 @@ pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type) (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate internal key: %s", ERR_error_string(ERR_get_error(), NULL)))); + if (!RAND_bytes(int_key->base_iv, INTERNAL_KEY_IV_LEN)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate IV: %s", + ERR_error_string(ERR_get_error(), NULL)))); } const char * diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 75e19a955d066..acf00b603272a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -21,6 +21,9 @@ #define TDE_KEY_TYPE_WAL_ENCRYPTED 0x10 #define MAP_ENTRY_VALID (TDE_KEY_TYPE_SMGR | TDE_KEY_TYPE_GLOBAL) +#define INTERNAL_KEY_LEN 16 +#define INTERNAL_KEY_IV_LEN 16 + typedef struct InternalKey { /* @@ -28,6 +31,7 @@ typedef struct InternalKey * pg_tde_read/write_one_keydata() */ uint8 key[INTERNAL_KEY_LEN]; + uint8 base_iv[INTERNAL_KEY_IV_LEN]; uint32 rel_type; XLogRecPtr start_lsn; From 127c5686234ee4c111ef296e7618d744d30cd87f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 28 Mar 2025 00:00:10 +0100 Subject: [PATCH 003/796] PG-1455 Add random base numbers to IVs for relation data We might as well increase the entropy by adding a random base value to the IVs used used when encrypting relations. But since for now the pair of key + IV is generated and used together it adds little extra security over what we already have. We add the IV with XOR since that is a cheap and easy operation which uases no extra collissions. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 36 ++++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 2a140c2fd0df2..0decca0e95a2b 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -26,6 +26,8 @@ typedef struct TDESMgrRelationData typedef TDESMgrRelationData *TDESMgrRelation; +static void CalcBlockIv(BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); + static InternalKey * tde_smgr_get_key(SMgrRelation reln, RelFileLocator *old_locator, bool can_create) { @@ -96,12 +98,11 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, for (int i = 0; i < nblocks; ++i) { BlockNumber bn = blocknum + i; - unsigned char iv[16] = {0,}; + unsigned char iv[16]; local_buffers[i] = &local_blocks_aligned[i * BLCKSZ]; - - memcpy(iv + 4, &bn, sizeof(BlockNumber)); + CalcBlockIv(bn, int_key->base_iv, iv); AesEncrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); } @@ -129,12 +130,11 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, { unsigned char *local_blocks = palloc(BLCKSZ * (1 + 1)); unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); - unsigned char iv[16] = { - 0, - }; + unsigned char iv[16]; AesInit(); - memcpy(iv + 4, &blocknum, sizeof(BlockNumber)); + + CalcBlockIv(blocknum, int_key->base_iv, iv); AesEncrypt(int_key->key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); @@ -162,7 +162,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, { bool allZero = true; BlockNumber bn = blocknum + i; - unsigned char iv[16] = {0,}; + unsigned char iv[16]; for (int j = 0; j < 32; ++j) { @@ -189,7 +189,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (allZero) continue; - memcpy(iv + 4, &bn, sizeof(BlockNumber)); + CalcBlockIv(bn, int_key->base_iv, iv); AesDecrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); } @@ -294,3 +294,21 @@ RegisterStorageMgr(void) elog(FATAL, "Another storage manager was loaded before pg_tde. Multiple storage managers is unsupported."); storage_manager_id = smgr_register(&tde_smgr, sizeof(TDESMgrRelationData)); } + +/* + * The intialization vector of a block is its block number conmverted to a + * 128 bit big endian number XOR the base IV of the relation file. + */ +static void +CalcBlockIv(BlockNumber bn, const unsigned char *base_iv, unsigned char *iv) +{ + memset(iv, 0, 16); + + iv[12] = bn >> 24; + iv[13] = bn >> 16; + iv[14] = bn >> 8; + iv[15] = bn; + + for (int i = 0; i < 16; i++) + iv[i] ^= base_iv[i]; +} From 72050ff258a1b3a5335feaf63f73bc7c7b054ddd Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 28 Mar 2025 01:04:35 +0100 Subject: [PATCH 004/796] PG-1455 Add random base numbers to IVs for WAL encryption Same as last commit but for the WAL encryption. Rewrote the calculation in a way gcc's vectorizer likes, as verified with Godbolt. The code generated by clang is ok and branch free but it fails to properly vectorize both before and after. --- .../pg_tde/src/access/pg_tde_xlog_encrypt.c | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index c41af6ae45b09..0ab70b2827f96 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -35,7 +35,7 @@ #include "port/atomics.h" #endif -static void SetXLogPageIVPrefix(TimeLineID tli, XLogRecPtr lsn, char *iv_prefix); +static void CalcXLogPageIVPrefix(TimeLineID tli, XLogRecPtr lsn, const unsigned char *base_iv, char *iv_prefix); static ssize_t tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize); static ssize_t tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, @@ -164,7 +164,7 @@ static ssize_t TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno) { - char iv_prefix[16] = {0,}; + char iv_prefix[16]; InternalKey *key = &EncryptionKey; char *enc_buff = EncryptionState->segBuf; @@ -175,7 +175,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, count, offset, offset, LSN_FORMAT_ARGS(segno), LSN_FORMAT_ARGS(key->start_lsn)); #endif - SetXLogPageIVPrefix(tli, segno, iv_prefix); + CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); PG_TDE_ENCRYPT_DATA(iv_prefix, offset, (char *) buf, count, enc_buff, key, &EncryptionCryptCtx); @@ -251,7 +251,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize) { ssize_t readsz; - char iv_prefix[16] = {0,}; WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); XLogRecPtr write_key_lsn = 0; WALKeyCacheRec *curr_key = NULL; @@ -297,8 +296,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, } } - SetXLogPageIVPrefix(tli, segno, iv_prefix); - XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start); XLogSegNoOffsetToRecPtr(segno, offset + count, segSize, data_end); @@ -325,6 +322,10 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, */ if (data_start <= curr_key->end_lsn && curr_key->start_lsn <= data_end) { + char iv_prefix[16]; + + CalcXLogPageIVPrefix(tli, segno, curr_key->key->base_iv, iv_prefix); + dec_off = XLogSegmentOffset(Max(data_start, curr_key->start_lsn), segSize); dec_end = XLogSegmentOffset(Min(data_end, curr_key->end_lsn), segSize); @@ -358,21 +359,34 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, return readsz; } -/* IV: TLI(uint32) + XLogRecPtr(uint64)*/ -static inline void -SetXLogPageIVPrefix(TimeLineID tli, XLogRecPtr lsn, char *iv_prefix) +/* IV: (TLI(uint32) + XLogRecPtr(uint64)) XOR BaseIV(uint8[12]) */ +static void +CalcXLogPageIVPrefix(TimeLineID tli, XLogRecPtr lsn, const unsigned char *base_iv, char *iv_prefix) { - iv_prefix[0] = (tli >> 24); - iv_prefix[1] = ((tli >> 16) & 0xFF); - iv_prefix[2] = ((tli >> 8) & 0xFF); - iv_prefix[3] = (tli & 0xFF); - - iv_prefix[4] = (lsn >> 56); - iv_prefix[5] = ((lsn >> 48) & 0xFF); - iv_prefix[6] = ((lsn >> 40) & 0xFF); - iv_prefix[7] = ((lsn >> 32) & 0xFF); - iv_prefix[8] = ((lsn >> 24) & 0xFF); - iv_prefix[9] = ((lsn >> 16) & 0xFF); - iv_prefix[10] = ((lsn >> 8) & 0xFF); - iv_prefix[11] = (lsn & 0xFF); + /* Temporary variables to make GCC vectorize it */ + char a[16], + b[16]; + + memset(a, 0, 16); + a[0] = tli >> 24; + a[1] = tli >> 16; + a[2] = tli >> 8; + a[3] = tli; + + memset(b, 0, 16); + b[4] = lsn >> 56; + b[5] = lsn >> 48; + b[6] = lsn >> 40; + b[7] = lsn >> 32; + b[8] = lsn >> 24; + b[9] = lsn >> 16; + b[10] = lsn >> 8; + b[11] = lsn; + + for (int i = 0; i < 16; i++) + iv_prefix[i] = (a[i] | b[i]) ^ base_iv[i]; + + /* Zero lowest 4 bytes */ + for (int i = 12; i < 16; i++) + iv_prefix[i] = 0; } From 36bc82d6c024abef8db45d3bf35ad8e179067f9f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 1 Apr 2025 16:33:09 +0200 Subject: [PATCH 005/796] PG-1455 Change xlog base IVs to use addition This way they can eventually be made more similar to using OpenSSL for encrypting and decrypting the CTR stream. --- .../pg_tde/src/access/pg_tde_xlog_encrypt.c | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index 0ab70b2827f96..5a6eee88040ef 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -359,34 +359,47 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, return readsz; } -/* IV: (TLI(uint32) + XLogRecPtr(uint64)) XOR BaseIV(uint8[12]) */ +union u128cast +{ + char a[16]; + unsigned __int128 i; +}; + +/* + * Calculate the start IV for an XLog segmenet. + * + * IV: (TLI(uint32) + XLogRecPtr(uint64)) + BaseIV(uint8[12]) + * + * TODO: Make the calculation more like OpenSSL's CTR withot any gaps and + * preferrably without zeroing the lowest bytes for the base IV. + * + * TODO: This code vectorizes poorly in both gcc and clang. + */ static void CalcXLogPageIVPrefix(TimeLineID tli, XLogRecPtr lsn, const unsigned char *base_iv, char *iv_prefix) { - /* Temporary variables to make GCC vectorize it */ - char a[16], - b[16]; - - memset(a, 0, 16); - a[0] = tli >> 24; - a[1] = tli >> 16; - a[2] = tli >> 8; - a[3] = tli; - - memset(b, 0, 16); - b[4] = lsn >> 56; - b[5] = lsn >> 48; - b[6] = lsn >> 40; - b[7] = lsn >> 32; - b[8] = lsn >> 24; - b[9] = lsn >> 16; - b[10] = lsn >> 8; - b[11] = lsn; + union u128cast base; + union u128cast iv; + unsigned __int128 offset; for (int i = 0; i < 16; i++) - iv_prefix[i] = (a[i] | b[i]) ^ base_iv[i]; +#ifdef WORDS_BIGENDIAN + base.a[i] = base_iv[i]; +#else + base.a[i] = base_iv[15 - i]; +#endif + + /* We do not support wrapping addition in Aes128EncryptedZeroBlocks() */ + base.i &= ~(((unsigned __int128) 1) << 32); + + offset = (((unsigned __int128) tli) << 112) | (((unsigned __int128) lsn) << 32); - /* Zero lowest 4 bytes */ - for (int i = 12; i < 16; i++) - iv_prefix[i] = 0; + iv.i = base.i + offset; + + for (int i = 0; i < 16; i++) +#ifdef WORDS_BIGENDIAN + iv_prefix[i] = iv.a[i]; +#else + iv_prefix[i] = iv.a[15 - i]; +#endif } From d7881854403996052ad660ed3c0de7dd300be312 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 14:23:01 +0200 Subject: [PATCH 006/796] PG-1437 Simplify pg_tde_read_one_map_entry2() While this does not merge the two similar functions it removes a lot of cruft from pg_tde_read_one_map_entry2() making the functions more similar and the code easier to read. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 61 +++++++---------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 6eb0b51757dfb..4e75aafbc30a0 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -115,7 +115,7 @@ static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEn static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); static bool pg_tde_read_one_map_entry(int fd, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset); -static TDEMapEntry *pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEPrincipalKey *principal_key); +static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); static int pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos); static InternalKey *pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type); static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); @@ -1129,46 +1129,27 @@ pg_tde_read_one_map_entry(File map_file, const RelFileLocator *rlocator, int fla /* * TODO: Unify with pg_tde_read_one_map_entry() */ -static TDEMapEntry * -pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEPrincipalKey *principal_key) +static void +pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId) { - TDEMapEntry *map_entry; - off_t read_pos = 0; + off_t read_pos; /* Calculate the reading position in the file. */ - read_pos += (key_index * MAP_ENTRY_SIZE) + TDE_FILE_HEADER_SIZE; - - /* Check if the file has a valid key */ - if ((read_pos + MAP_ENTRY_SIZE) > lseek(fd, 0, SEEK_END)) - { - char db_map_path[MAXPGPATH] = {0}; - - pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); - ereport(FATAL, - (errcode(ERRCODE_NO_DATA_FOUND), - errmsg("could not find the required key at index %d in tde data file \"%s\": %m", - key_index, db_map_path))); - } - - /* Allocate and fill in the structure */ - map_entry = palloc_object(TDEMapEntry); + read_pos = TDE_FILE_HEADER_SIZE + key_index * MAP_ENTRY_SIZE; /* Read the encrypted key */ if (pg_pread(fd, map_entry, MAP_ENTRY_SIZE, read_pos) != MAP_ENTRY_SIZE) { char db_map_path[MAXPGPATH] = {0}; - pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); + pg_tde_set_db_file_path(databaseId, db_map_path); ereport(FATAL, (errcode_for_file_access(), - errmsg("could not read key at index %d in tde key data file \"%s\": %m", + errmsg("could not find the required key at index %d in tde data file \"%s\": %m", key_index, db_map_path))); } - - return map_entry; } - /* * Get the principal key from the map file. The caller must hold * a LW_SHARED or higher lock on files before calling this function. @@ -1305,11 +1286,10 @@ pg_tde_read_last_wal_key(void) TDEPrincipalKey *principal_key; int fd = -1; int file_idx = 0; - TDEMapEntry *map_entry; + TDEMapEntry map_entry; InternalKey *rel_key_data; off_t fsize; - LWLockAcquire(lock_pk, LW_EXCLUSIVE); principal_key = GetPrincipalKey(rlocator.dbOid, LW_EXCLUSIVE); if (principal_key == NULL) @@ -1330,17 +1310,11 @@ pg_tde_read_last_wal_key(void) } file_idx = ((fsize - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE) - 1; - map_entry = pg_tde_read_one_map_entry2(fd, file_idx, principal_key); - if (!map_entry) - { - LWLockRelease(lock_pk); - return NULL; - } + pg_tde_read_one_map_entry2(fd, file_idx, &map_entry, rlocator.dbOid); - rel_key_data = tde_decrypt_rel_key(principal_key, map_entry); + rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); LWLockRelease(lock_pk); close(fd); - pfree(map_entry); return rel_key_data; } @@ -1396,26 +1370,27 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) for (int file_idx = 0; file_idx < keys_count; file_idx++) { - TDEMapEntry *map_entry = pg_tde_read_one_map_entry2(fd, file_idx, principal_key); + TDEMapEntry map_entry; + + pg_tde_read_one_map_entry2(fd, file_idx, &map_entry, rlocator.dbOid); /* * Skip new (just created but not updated by write) and invalid keys */ - if (map_entry->enc_key.start_lsn != InvalidXLogRecPtr && - WALKeyIsValid(&map_entry->enc_key) && - map_entry->enc_key.start_lsn >= start_lsn) + if (map_entry.enc_key.start_lsn != InvalidXLogRecPtr && + WALKeyIsValid(&map_entry.enc_key) && + map_entry.enc_key.start_lsn >= start_lsn) { - InternalKey *rel_key_data = tde_decrypt_rel_key(principal_key, map_entry); + InternalKey *rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); InternalKey *cached_key = pg_tde_put_key_into_cache(&rlocator, rel_key_data); WALKeyCacheRec *wal_rec; pfree(rel_key_data); - wal_rec = pg_tde_add_wal_key_to_cache(cached_key, map_entry->enc_key.start_lsn); + wal_rec = pg_tde_add_wal_key_to_cache(cached_key, map_entry.enc_key.start_lsn); if (!return_wal_rec) return_wal_rec = wal_rec; } - pfree(map_entry); } LWLockRelease(lock_pk); close(fd); From e9d4927b2c0aaea98764b876aa0ed6b768142f01 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 14:55:13 +0200 Subject: [PATCH 007/796] Use correct type for fd --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 4e75aafbc30a0..85e1c75c88dc5 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1099,7 +1099,7 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader * comparing old and new value of the offset. */ static bool -pg_tde_read_one_map_entry(File map_file, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset) +pg_tde_read_one_map_entry(int map_file, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset) { bool found; off_t bytes_read = 0; From 5514727353f621f30f1dc06c03aa9e2285bf1ea1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 19:41:38 +0200 Subject: [PATCH 008/796] PG-1416 Sign principal key info to protect against the wrong principal key We already had protection against decrypting relation keys with the wrong principal key but to properly protect us against new relation keys being encrypted with the wrong principal key we need to also verify that the principal key was correct when we fetch the principal key from the key provider. We do so by signing the principal key info header of the key map file using AES-128-GCM. This way we cannot get a jumbled mess of relation keys encrypted with multiple different principal keys. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 96 +++++++++++++------ contrib/pg_tde/src/access/pg_tde_xlog.c | 4 +- .../pg_tde/src/catalog/tde_principal_key.c | 22 +++-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 18 +++- contrib/pg_tde/t/010_change_key_provider.pl | 8 +- .../t/expected/010_change_key_provider.out | 10 +- 6 files changed, 103 insertions(+), 55 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 85e1c75c88dc5..4ec3a9c4a8e6b 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -66,7 +66,7 @@ typedef struct TDEFileHeader { int32 file_version; - TDEPrincipalKeyInfo principal_key_info; + TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; typedef struct RelKeyCacheRec @@ -125,13 +125,14 @@ static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, Int static InternalKey *pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type); static InternalKey *pg_tde_create_local_key(const RelFileLocator *newrlocator, uint32 entry_type); static void pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type); -static int pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo *principal_key_info, off_t *bytes_written); +static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); +static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static off_t pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog); static bool pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t offset); -static int keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated_filename, char *filename, off_t *curr_pos); +static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); -static int pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool truncate, off_t *curr_pos); +static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); static void update_wal_keys_cache(void); InternalKey * @@ -264,11 +265,18 @@ pg_tde_delete_tde_files(Oid dbOid) } void -pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info) +pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) { + int map_fd; + off_t curr_pos; + char db_map_path[MAXPGPATH] = {0}; + + pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - pg_tde_save_principal_key(principal_key_info); + map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, true, &curr_pos); + close(map_fd); LWLockRelease(tde_lwlock_enc_keys()); } @@ -282,18 +290,21 @@ pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info) * The caller must have an EXCLUSIVE LOCK on the files before calling this function. */ void -pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info) +pg_tde_save_principal_key(const TDEPrincipalKey *principal_key) { int map_fd = -1; off_t curr_pos = 0; char db_map_path[MAXPGPATH] = {0}; + TDESignedPrincipalKeyInfo signed_key_Info; /* Set the file paths */ - pg_tde_set_db_file_path(principal_key_info->databaseId, db_map_path); + pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); ereport(DEBUG2, (errmsg("pg_tde_save_principal_key"))); - map_fd = pg_tde_open_file_write(db_map_path, principal_key_info, true, &curr_pos); + pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + + map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, true, &curr_pos); close(map_fd); } @@ -301,17 +312,17 @@ pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info) * Write TDE file header to a TDE file. */ static int -pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo *principal_key_info, off_t *bytes_written) +pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written) { TDEFileHeader fheader; - Assert(principal_key_info); + Assert(signed_key_info); /* Create the header for this file. */ fheader.file_version = PG_TDE_FILEMAGIC; /* Fill in the data */ - fheader.principal_key_info = *principal_key_info; + fheader.signed_key_info = *signed_key_info; *bytes_written = pg_pwrite(fd, &fheader, TDE_FILE_HEADER_SIZE, 0); @@ -335,6 +346,19 @@ pg_tde_file_header_write(const char *tde_filename, int fd, TDEPrincipalKeyInfo * return fd; } +static void +pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) +{ + signed_key_info->data = principal_key->keyInfo; + + if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_EMPTY_IV_SIZE)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL)))); + + AesGcmEncrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag); +} + static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data) { @@ -399,14 +423,17 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ off_t curr_pos = 0; off_t prev_pos = 0; TDEMapEntry write_map_entry; + TDESignedPrincipalKeyInfo signed_key_Info; Assert(rlocator); /* Set the file paths */ pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); + pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + /* Open and validate file for basic correctness. */ - map_fd = pg_tde_open_file_write(db_map_path, &principal_key->keyInfo, false, &curr_pos); + map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, false, &curr_pos); prev_pos = curr_pos; /* @@ -438,7 +465,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ XLogRelKey xlrec; xlrec.mapEntry = write_map_entry; - xlrec.pkInfo = principal_key->keyInfo; + xlrec.pkInfo = signed_key_Info; XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(xlrec)); @@ -466,7 +493,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ * concurrent in place updates leading to data conflicts. */ void -pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipalKeyInfo *principal_key_info) +pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info) { char db_map_path[MAXPGPATH] = {0}; int map_fd = -1; @@ -474,12 +501,12 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipal off_t prev_pos = 0; /* Set the file paths */ - pg_tde_set_db_file_path(principal_key_info->databaseId, db_map_path); + pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); /* Open and validate file for basic correctness. */ - map_fd = pg_tde_open_file_write(db_map_path, principal_key_info, false, &curr_pos); + map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos); prev_pos = curr_pos; /* @@ -631,7 +658,7 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset) * No error checking by this function. */ static File -keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated_filename, char *filename, off_t *curr_pos) +keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos) { /* * Set the new filenames for the key rotation process - temporary at the @@ -640,7 +667,7 @@ keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated snprintf(rotated_filename, MAXPGPATH, "%s.r", filename); /* Create file, truncate if the rotate file already exits */ - return pg_tde_open_file_write(rotated_filename, new_principal_key_info, true, curr_pos); + return pg_tde_open_file_write(rotated_filename, signed_key_info, true, curr_pos); } /* @@ -666,14 +693,17 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p off_t curr_pos[PRINCIPAL_KEY_COUNT] = {0}; int fd[PRINCIPAL_KEY_COUNT]; char path[PRINCIPAL_KEY_COUNT][MAXPGPATH]; + TDESignedPrincipalKeyInfo new_signed_key_info; off_t map_size; XLogPrincipalKeyRotate *xlrec; off_t xlrec_size; pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, path[OLD_PRINCIPAL_KEY]); + pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); + fd[OLD_PRINCIPAL_KEY] = pg_tde_open_file_read(path[OLD_PRINCIPAL_KEY], &curr_pos[OLD_PRINCIPAL_KEY]); - fd[NEW_PRINCIPAL_KEY] = keyrotation_init_file(&new_principal_key->keyInfo, path[NEW_PRINCIPAL_KEY], path[OLD_PRINCIPAL_KEY], &curr_pos[NEW_PRINCIPAL_KEY]); + fd[NEW_PRINCIPAL_KEY] = keyrotation_init_file(&new_signed_key_info, path[NEW_PRINCIPAL_KEY], path[OLD_PRINCIPAL_KEY], &curr_pos[NEW_PRINCIPAL_KEY]); /* Read all entries until EOF */ while (1) @@ -763,10 +793,10 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) fheader = (TDEFileHeader *) file_data; /* Set the file paths */ - pg_tde_set_db_file_path(fheader->principal_key_info.databaseId, db_map_path); + pg_tde_set_db_file_path(fheader->signed_key_info.data.databaseId, db_map_path); /* Initialize the new file and set the name */ - fd_new = keyrotation_init_file(&fheader->principal_key_info, path_new, db_map_path, &curr_pos); + fd_new = keyrotation_init_file(&fheader->signed_key_info, path_new, db_map_path, &curr_pos); if (pg_pwrite(fd_new, file_data, file_size, 0) != file_size) { @@ -867,7 +897,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) * is raised. */ static int -pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool truncate, off_t *curr_pos) +pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos) { int fd; TDEFileHeader fheader; @@ -882,8 +912,8 @@ pg_tde_open_file_write(const char *tde_filename, TDEPrincipalKeyInfo *principal_ pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read); /* In case it's a new file, let's add the header now. */ - if (bytes_read == 0 && principal_key_info) - pg_tde_file_header_write(tde_filename, fd, principal_key_info, &bytes_written); + if (bytes_read == 0 && signed_key_info) + pg_tde_file_header_write(tde_filename, fd, signed_key_info, &bytes_written); *curr_pos = bytes_read + bytes_written; return fd; @@ -990,6 +1020,12 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_ } } +bool +pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) +{ + return AesGcmDecrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag); +} + /* * Decrypts a given key and returns the decrypted one. */ @@ -1154,12 +1190,12 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid * Get the principal key from the map file. The caller must hold * a LW_SHARED or higher lock on files before calling this function. */ -TDEPrincipalKeyInfo * +TDESignedPrincipalKeyInfo * pg_tde_get_principal_key_info(Oid dbOid) { int fd = -1; TDEFileHeader fheader; - TDEPrincipalKeyInfo *principal_key_info = NULL; + TDESignedPrincipalKeyInfo *signed_key_info = NULL; off_t bytes_read = 0; char db_map_path[MAXPGPATH] = {0}; @@ -1186,11 +1222,11 @@ pg_tde_get_principal_key_info(Oid dbOid) */ if (bytes_read > 0) { - principal_key_info = palloc_object(TDEPrincipalKeyInfo); - *principal_key_info = fheader.principal_key_info; + signed_key_info = palloc_object(TDESignedPrincipalKeyInfo); + *signed_key_info = fheader.signed_key_info; } - return principal_key_info; + return signed_key_info; } /* diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 9eea9e23a33da..b34801f34762f 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -61,7 +61,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { - TDEPrincipalKeyInfo *mkey = (TDEPrincipalKeyInfo *) XLogRecGetData(record); + TDESignedPrincipalKeyInfo *mkey = (TDESignedPrincipalKeyInfo *) XLogRecGetData(record); pg_tde_save_principal_key_redo(mkey); } @@ -107,7 +107,7 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.databaseId, xlrec->mapEntry.relNumber); + appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); } if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 3e7db9a9afe88..5539638683995 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -343,7 +343,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (!already_has_key) { /* First key created for the database */ - pg_tde_save_principal_key(&new_principal_key->keyInfo); + pg_tde_save_principal_key(new_principal_key); /* XLog the new key */ XLogBeginInsert(); @@ -703,7 +703,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) static TDEPrincipalKey * get_principal_key_from_keyring(Oid dbOid) { - TDEPrincipalKeyInfo *principalKeyInfo; + TDESignedPrincipalKeyInfo *principalKeyInfo; GenericKeyring *keyring; KeyInfo *keyInfo; KeyringReturnCodes keyring_ret; @@ -715,28 +715,34 @@ get_principal_key_from_keyring(Oid dbOid) if (principalKeyInfo == NULL) return NULL; - keyring = GetKeyProviderByID(principalKeyInfo->keyringId, dbOid); + keyring = GetKeyProviderByID(principalKeyInfo->data.keyringId, dbOid); if (keyring == NULL) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("keyring lookup failed for principal key %s, unknown keyring with ID %d", - principalKeyInfo->name, principalKeyInfo->keyringId))); + principalKeyInfo->data.name, principalKeyInfo->data.keyringId))); - keyInfo = KeyringGetKey(keyring, principalKeyInfo->name, &keyring_ret); + keyInfo = KeyringGetKey(keyring, principalKeyInfo->data.name, &keyring_ret); if (keyInfo == NULL) ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("failed to retrieve principal key %s from keyring with ID %d", - principalKeyInfo->name, principalKeyInfo->keyringId))); + principalKeyInfo->data.name, principalKeyInfo->data.keyringId))); principalKey = palloc_object(TDEPrincipalKey); - principalKey->keyInfo = *principalKeyInfo; + principalKey->keyInfo = principalKeyInfo->data; memcpy(principalKey->keyData, keyInfo->data.data, keyInfo->data.len); principalKey->keyLength = keyInfo->data.len; Assert(dbOid == principalKey->keyInfo.databaseId); + if (!pg_tde_verify_principal_key_info(principalKeyInfo, principalKey)) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file", + principalKeyInfo->data.name))); + pfree(keyInfo); pfree(keyring); pfree(principalKeyInfo); @@ -833,7 +839,7 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode) *newPrincipalKey = *principalKey; newPrincipalKey->keyInfo.databaseId = dbOid; - pg_tde_save_principal_key(&newPrincipalKey->keyInfo); + pg_tde_save_principal_key(newPrincipalKey); push_principal_key_to_cache(newPrincipalKey); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index acf00b603272a..b6788481275b9 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -46,6 +46,13 @@ typedef struct InternalKey #define MAP_ENTRY_EMPTY_IV_SIZE 16 #define MAP_ENTRY_EMPTY_AEAD_TAG_SIZE 16 +typedef struct +{ + TDEPrincipalKeyInfo data; + unsigned char sign_iv[16]; + unsigned char aead_tag[16]; +} TDESignedPrincipalKeyInfo; + /* We do not need the dbOid since the entries are stored in a file per db */ typedef struct TDEMapEntry { @@ -61,7 +68,7 @@ typedef struct TDEMapEntry typedef struct XLogRelKey { TDEMapEntry mapEntry; - TDEPrincipalKeyInfo pkInfo; + TDESignedPrincipalKeyInfo pkInfo; } XLogRelKey; /* @@ -95,7 +102,7 @@ extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset); -extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDEPrincipalKeyInfo *principal_key_info); +extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info); #define PG_TDE_MAP_FILENAME "pg_tde_%d_map" @@ -109,9 +116,10 @@ extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel); extern void pg_tde_delete_tde_files(Oid dbOid); -extern TDEPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); -extern void pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info); -extern void pg_tde_save_principal_key_redo(TDEPrincipalKeyInfo *principal_key_info); +extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); +extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); +extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key); +extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key); extern void pg_tde_write_map_keydata_file(off_t size, char *file_data); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index c7add5bb551bf..6efe58df9b8bb 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -174,15 +174,13 @@ (undef, $stdout, $stderr) = $node->psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); +(undef, $stdout, $stderr) = $node->psql('postgres', 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); +PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server -PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); -$rt_value = $node->start(); - # Verify $stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index 507b8799177da..5c4034d666c42 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -80,15 +80,15 @@ SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3 1 -- server restart SELECT pg_tde_verify_principal_key(); - - +psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT pg_tde_is_encrypted('test_enc'); -psql::1: ERROR: Failed to decrypt key, incorrect principal key or corrupted key file +psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT * FROM test_enc ORDER BY id; -psql::1: ERROR: Failed to decrypt key, incorrect principal key or corrupted key file +psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file +CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; +psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); 1 --- server restart SELECT pg_tde_verify_principal_key(); SELECT pg_tde_is_encrypted('test_enc'); From d83fc90ac5cddd3e9c6660f2ee4700e713fccc18 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 15:03:24 +0200 Subject: [PATCH 009/796] Use %m for error message in our modification of pg_regress This follows the pattern in the rest of the PostgreSQL source code. --- src/test/regress/pg_regress.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 4cc0c35418fc7..62c5b84a7f31b 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -1997,8 +1997,8 @@ create_database(const char *dbname) sql_file = fopen(exec_sql, "r"); if (sql_file == NULL) { - bail("could not open \"%s\" to read extra setup file: %s", - exec_sql, strerror(errno)); + bail("could not open \"%s\" to read extra setup file: %m", + exec_sql); } while (fgets(line_buf, sizeof(line_buf), sql_file) != NULL) psql_command(dbname, "%s", line_buf); From 07756f27345b3243c379b2fd10f5ec396e3fc94a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 5 Apr 2025 23:54:54 +0200 Subject: [PATCH 010/796] PG-1530 Fix off-by-one bug when determining which WAL key to use The end LSN of the current buffer to decript was exclusive while the end LSN of the key was inclusive which had led to confusion and an off-by-one bug. Also add a simple test case for the WAL encryption using the logical decoding's test plugin. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 +- .../pg_tde/src/access/pg_tde_xlog_encrypt.c | 2 +- contrib/pg_tde/t/009_wal_encrypt.pl | 47 ++++++++++++++++--- contrib/pg_tde/t/expected/009_wal_encrypt.out | 34 +++++++++++++- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 4ec3a9c4a8e6b..ccdc445ce2399 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1460,7 +1460,7 @@ pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn) else { tde_wal_key_last_rec->next = wal_rec; - tde_wal_key_last_rec->end_lsn = wal_rec->start_lsn - 1; + tde_wal_key_last_rec->end_lsn = wal_rec->start_lsn; tde_wal_key_last_rec = wal_rec; } diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index 5a6eee88040ef..42d6015076be0 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -320,7 +320,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, * Check if the key's range overlaps with the buffer's and decypt * the part that does. */ - if (data_start <= curr_key->end_lsn && curr_key->start_lsn <= data_end) + if (data_start < curr_key->end_lsn && data_end > curr_key->start_lsn) { char iv_prefix[16]; diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 77e9633ca41f5..1c5a0e7515d7a 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -19,6 +19,7 @@ # UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; +print $conf "wal_level = 'logical'\n"; # NOT testing that it can't start: the test framework doesn't have an easy way to do this # print $conf "pg_tde.wal_encrypt = 1\n"; close $conf; @@ -35,27 +36,61 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-010');", extra_params => ['-a']); PGTDE::append_to_file($stdout); +$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + # Restart the server, it should work with encryption now PGTDE::append_to_file("-- server restart with wal encryption"); $node->stop(); +$rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); -open $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "pg_tde.wal_encrypt = 1\n"; -close $conf; +$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding');", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id));', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (1), (2);', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = off;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); +PGTDE::append_to_file("-- server restart without wal encryption"); +$node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_wal(id SERIAL,k INTEGER,PRIMARY KEY (id));', extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (3), (4);', extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'CHECKPOINT;', extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# TODO: add WAL content testing after the wal rework + +PGTDE::append_to_file("-- server restart with wal encryption"); +$node->stop(); +$rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); + +$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "SELECT pg_drop_replication_slot('tde_slot');", extra_params => ['-a']); +PGTDE::append_to_file($stdout); # DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index 67b4e5e56d94e..f4a4b1922f806 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -3,9 +3,39 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_ -1 SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-010'); +ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption SHOW pg_tde.wal_encrypt; on -CREATE TABLE test_wal(id SERIAL,k INTEGER,PRIMARY KEY (id)); -CHECKPOINT; +SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding'); +tde_slot +CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id)); +INSERT INTO test_wal (k) VALUES (1), (2); +ALTER SYSTEM SET pg_tde.wal_encrypt = off; +-- server restart without wal encryption +SHOW pg_tde.wal_encrypt; +off +INSERT INTO test_wal (k) VALUES (3), (4); +ALTER SYSTEM SET pg_tde.wal_encrypt = on; +-- server restart with wal encryption +SHOW pg_tde.wal_encrypt; +on +INSERT INTO test_wal (k) VALUES (5), (6); +SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL); +BEGIN 739 +COMMIT 739 +BEGIN 740 +table public.test_wal: INSERT: id[integer]:1 k[integer]:1 +table public.test_wal: INSERT: id[integer]:2 k[integer]:2 +COMMIT 740 +BEGIN 741 +table public.test_wal: INSERT: id[integer]:3 k[integer]:3 +table public.test_wal: INSERT: id[integer]:4 k[integer]:4 +COMMIT 741 +BEGIN 742 +table public.test_wal: INSERT: id[integer]:5 k[integer]:5 +table public.test_wal: INSERT: id[integer]:6 k[integer]:6 +COMMIT 742 +SELECT pg_drop_replication_slot('tde_slot'); + DROP EXTENSION pg_tde; From b11d51ca85c2623626f796eec71acea411ee26e0 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 14:28:01 +0200 Subject: [PATCH 011/796] Use separate variables instead of an array when rotating files This makes the code easier to read plus gives us things like letting the compiler check for unused variables. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 48 +++++++++++------------ 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index ccdc445ce2399..0d978a92addf3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -686,40 +686,40 @@ finalize_key_rotation(const char *path_old, const char *path_new) void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key) { -#define OLD_PRINCIPAL_KEY 0 -#define NEW_PRINCIPAL_KEY 1 -#define PRINCIPAL_KEY_COUNT 2 - - off_t curr_pos[PRINCIPAL_KEY_COUNT] = {0}; - int fd[PRINCIPAL_KEY_COUNT]; - char path[PRINCIPAL_KEY_COUNT][MAXPGPATH]; TDESignedPrincipalKeyInfo new_signed_key_info; + off_t old_curr_pos, + new_curr_pos; + int old_fd, + new_fd; + char old_path[MAXPGPATH], + new_path[MAXPGPATH]; off_t map_size; XLogPrincipalKeyRotate *xlrec; off_t xlrec_size; - pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, path[OLD_PRINCIPAL_KEY]); - pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); - fd[OLD_PRINCIPAL_KEY] = pg_tde_open_file_read(path[OLD_PRINCIPAL_KEY], &curr_pos[OLD_PRINCIPAL_KEY]); - fd[NEW_PRINCIPAL_KEY] = keyrotation_init_file(&new_signed_key_info, path[NEW_PRINCIPAL_KEY], path[OLD_PRINCIPAL_KEY], &curr_pos[NEW_PRINCIPAL_KEY]); + pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); + + old_fd = pg_tde_open_file_read(old_path, &old_curr_pos); + new_fd = keyrotation_init_file(&new_signed_key_info, new_path, old_path, &new_curr_pos); /* Read all entries until EOF */ while (1) { InternalKey *rel_key_data; - off_t prev_pos[PRINCIPAL_KEY_COUNT]; + off_t old_prev_pos, + new_prev_pos; TDEMapEntry read_map_entry, write_map_entry; RelFileLocator rloc; bool found; - prev_pos[OLD_PRINCIPAL_KEY] = curr_pos[OLD_PRINCIPAL_KEY]; - found = pg_tde_read_one_map_entry(fd[OLD_PRINCIPAL_KEY], NULL, MAP_ENTRY_VALID, &read_map_entry, &curr_pos[OLD_PRINCIPAL_KEY]); + old_prev_pos = old_curr_pos; + found = pg_tde_read_one_map_entry(old_fd, NULL, MAP_ENTRY_VALID, &read_map_entry, &old_curr_pos); /* We either reach EOF */ - if (prev_pos[OLD_PRINCIPAL_KEY] == curr_pos[OLD_PRINCIPAL_KEY]) + if (old_prev_pos == old_curr_pos) break; /* We didn't find a valid entry */ @@ -735,16 +735,16 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p pg_tde_initialize_map_entry(&write_map_entry, new_principal_key, &rloc, rel_key_data); /* Write the given entry at the location pointed by prev_pos */ - prev_pos[NEW_PRINCIPAL_KEY] = curr_pos[NEW_PRINCIPAL_KEY]; - curr_pos[NEW_PRINCIPAL_KEY] = pg_tde_write_one_map_entry(fd[NEW_PRINCIPAL_KEY], &write_map_entry, &prev_pos[NEW_PRINCIPAL_KEY], path[NEW_PRINCIPAL_KEY]); + new_prev_pos = new_curr_pos; + new_curr_pos = pg_tde_write_one_map_entry(new_fd, &write_map_entry, &new_prev_pos, new_path); pfree(rel_key_data); } - close(fd[OLD_PRINCIPAL_KEY]); + close(old_fd); /* Let's calculate sizes */ - map_size = lseek(fd[NEW_PRINCIPAL_KEY], 0, SEEK_END); + map_size = lseek(new_fd, 0, SEEK_END); xlrec_size = map_size + SizeoOfXLogPrincipalKeyRotate; /* palloc and fill in the structure */ @@ -753,12 +753,12 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p xlrec->databaseId = principal_key->keyInfo.databaseId; xlrec->file_size = map_size; - if (pg_pread(fd[NEW_PRINCIPAL_KEY], xlrec->buff, xlrec->file_size, 0) == -1) + if (pg_pread(new_fd, xlrec->buff, xlrec->file_size, 0) == -1) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write WAL for key rotation: %m"))); - close(fd[NEW_PRINCIPAL_KEY]); + close(new_fd); /* Insert the XLog record */ XLogBeginInsert(); @@ -766,14 +766,10 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_KEY); /* Do the final steps */ - finalize_key_rotation(path[OLD_PRINCIPAL_KEY], path[NEW_PRINCIPAL_KEY]); + finalize_key_rotation(old_path, new_path); /* Free up the palloc'ed data */ pfree(xlrec); - -#undef OLD_PRINCIPAL_KEY -#undef NEW_PRINCIPAL_KEY -#undef PRINCIPAL_KEY_COUNT } /* From 2616e27844571b3dcebce54d4f2c598763dd5f87 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 5 Apr 2025 09:08:09 +0200 Subject: [PATCH 012/796] Clean up tdeheap_xlog_seg_read() Several variables were at a too long scope or were initialized when they should not be. Plus convert a while loop to a for loop. --- .../pg_tde/src/access/pg_tde_xlog_encrypt.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index 42d6015076be0..16f6e9c531f1d 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -252,11 +252,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, { ssize_t readsz; WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); - XLogRecPtr write_key_lsn = 0; - WALKeyCacheRec *curr_key = NULL; - off_t dec_off = 0; - off_t dec_end = 0; - size_t dec_sz = 0; + XLogRecPtr write_key_lsn; XLogRecPtr data_start; XLogRecPtr data_end; @@ -278,7 +274,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, #ifndef FRONTEND write_key_lsn = pg_atomic_read_u64(&EncryptionState->enc_key_lsn); -#endif if (write_key_lsn != 0) { @@ -295,6 +290,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, keys = pg_tde_get_wal_cache_keys(); } } +#endif XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start); XLogSegNoOffsetToRecPtr(segno, offset + count, segSize, data_end); @@ -303,8 +299,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, * TODO: this is higly ineffective. We should get rid of linked list and * search from the last key as this is what the walsender is useing. */ - curr_key = keys; - while (curr_key) + for (WALKeyCacheRec *curr_key = keys; curr_key != NULL; curr_key = curr_key->next) { #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "WAL key %X/%X-%X/%X, encrypted: %s", @@ -323,12 +318,12 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, if (data_start < curr_key->end_lsn && data_end > curr_key->start_lsn) { char iv_prefix[16]; + off_t dec_off = XLogSegmentOffset(Max(data_start, curr_key->start_lsn), segSize); + off_t dec_end = XLogSegmentOffset(Min(data_end, curr_key->end_lsn), segSize); + size_t dec_sz; CalcXLogPageIVPrefix(tli, segno, curr_key->key->base_iv, iv_prefix); - dec_off = XLogSegmentOffset(Max(data_start, curr_key->start_lsn), segSize); - dec_end = XLogSegmentOffset(Min(data_end, curr_key->end_lsn), segSize); - /* We have reached the end of the segment */ if (dec_end == 0) { @@ -352,8 +347,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, } } } - - curr_key = curr_key->next; } return readsz; From 7638ec548a2b36fb40830fd1de73d9074920350d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 20:17:32 +0200 Subject: [PATCH 013/796] Make comment about all-zero pages less scary The comment about all-zero pages created by smgrzeroextend() sounded much scarier than the reality. In fact trying to encrypt these all-zero pages might not only be a waste of CPU cycles but also could decrease security by making us re-use the same IV first with all zeros and then with the actual data. And the extra amount of protection we gain from encrypting them is minuscule since they are only added at the end of the table, soon overwritten and only gives the attacker a very slightly more accurate table size. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 0decca0e95a2b..1a3b9b3a3da83 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -164,24 +164,19 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber bn = blocknum + i; unsigned char iv[16]; + /* + * Detect unencrypted all-zero pages written by smgrzeroextend() by + * looking at the first 32 bytes of the page. + * + * Not encrypting all-zero pages is safe because they are only written + * at the end of the file when extending a table on disk so they tend + * to be short lived plus they only leak a slightly more accurate + * table size than one can glean from just the file size. + */ for (int j = 0; j < 32; ++j) { if (((char **) buffers)[i][j] != 0) { - /* - * Postgres creates all zero blocks in an optimized route, - * which we do not try - */ - /* to encrypt. */ - /* - * Instead we detect if a block is all zero at decryption - * time, and - */ - /* leave it as is. */ - /* - * This could be a security issue later, but it is a good - * first prototype - */ allZero = false; break; } From fef106d78581513a2ddb77d931bc4fbad8e9e72f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 11:23:29 +0200 Subject: [PATCH 014/796] Remove unnecessary seek when we already know the size There is no point in looking up the length of the file when we already know it. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 0d978a92addf3..61335dd362d7b 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -693,7 +693,6 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p new_fd; char old_path[MAXPGPATH], new_path[MAXPGPATH]; - off_t map_size; XLogPrincipalKeyRotate *xlrec; off_t xlrec_size; @@ -743,15 +742,12 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p close(old_fd); - /* Let's calculate sizes */ - map_size = lseek(new_fd, 0, SEEK_END); - xlrec_size = map_size + SizeoOfXLogPrincipalKeyRotate; + /* Build WAL record containing the new file */ + xlrec_size = SizeoOfXLogPrincipalKeyRotate + new_curr_pos; - /* palloc and fill in the structure */ xlrec = (XLogPrincipalKeyRotate *) palloc(xlrec_size); - xlrec->databaseId = principal_key->keyInfo.databaseId; - xlrec->file_size = map_size; + xlrec->file_size = new_curr_pos; if (pg_pread(new_fd, xlrec->buff, xlrec->file_size, 0) == -1) ereport(ERROR, @@ -765,11 +761,10 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p XLogRegisterData((char *) xlrec, xlrec_size); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_KEY); + pfree(xlrec); + /* Do the final steps */ finalize_key_rotation(old_path, new_path); - - /* Free up the palloc'ed data */ - pfree(xlrec); } /* From 3b8a234d3e29483e518f0ed7b188cd2ca71a41d6 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Tue, 8 Apr 2025 10:20:16 +0200 Subject: [PATCH 015/796] PG-1457 Key management funcs renaming (#126) * PG-1457 Rename some key management funcions * PG-1457 Fix some tests * PG-1457 Hit CI * PG-1457 Rename key in CI setup * PG-1457 Rename pg_tde_verify_global_principal_key to pg_tde_verify_server_principal_key * PG-1457 Rename keys in tests * PG-1457 Renaming * PG-1457 Renaming * PG-1457 Fix tests * PG-1457 Fix tests * PG-1457 Fix tabs * PG-1457 Fix tests * PG-1457 Fix tests * PG-1457 Fix * PG-1457 Fix test * PG-1457 Fix test * PG-1457 Hit CI * PG-1457 Fix after rebase * PG-1457 Fix * PG-1457 Fix * PG-1457 Fix * PG-1457 Fix test * PG-1457 Fix tests * PG-1457 Fix tests * PG-1457 Fix --- ci_scripts/backup/pg_basebackup_test.sh | 4 +- ci_scripts/tde_setup.sql | 4 +- ci_scripts/tde_setup_global.sql | 2 +- contrib/pg_tde/README.md | 12 +- .../documentation/docs/external-parameters.md | 4 +- .../pg_tde/documentation/docs/functions.md | 54 +++--- .../documentation/docs/multi-tenant-setup.md | 16 +- contrib/pg_tde/documentation/docs/setup.md | 4 +- .../documentation/docs/wal-encryption.md | 4 +- contrib/pg_tde/expected/access_control.out | 42 ++-- contrib/pg_tde/expected/alter_index.out | 14 +- contrib/pg_tde/expected/cache_alloc.out | 14 +- .../pg_tde/expected/change_access_method.out | 14 +- .../pg_tde/expected/default_principal_key.out | 12 +- .../expected/default_principal_key_1.out | 12 +- .../pg_tde/expected/delete_key_provider.out | 54 +++--- .../pg_tde/expected/insert_update_delete.out | 14 +- contrib/pg_tde/expected/key_provider.out | 68 +++---- contrib/pg_tde/expected/key_provider_1.out | 68 +++---- .../expected/keyprovider_dependency.out | 32 ++-- contrib/pg_tde/expected/kmip_test.out | 14 +- .../pg_tde/expected/pg_tde_is_encrypted.out | 14 +- contrib/pg_tde/expected/recreate_storage.out | 14 +- contrib/pg_tde/expected/relocate.out | 8 +- contrib/pg_tde/expected/subtransaction.out | 14 +- contrib/pg_tde/expected/tablespace.out | 14 +- contrib/pg_tde/expected/toast_decrypt.out | 14 +- contrib/pg_tde/expected/toast_decrypt_1.out | 14 +- contrib/pg_tde/expected/vault_v2_test.out | 24 +-- contrib/pg_tde/pg_tde--1.0-rc.sql | 179 +++++++++--------- contrib/pg_tde/sql/access_control.sql | 16 +- contrib/pg_tde/sql/alter_index.sql | 4 +- contrib/pg_tde/sql/cache_alloc.sql | 4 +- contrib/pg_tde/sql/change_access_method.sql | 4 +- contrib/pg_tde/sql/default_principal_key.sql | 4 +- contrib/pg_tde/sql/delete_key_provider.sql | 24 +-- contrib/pg_tde/sql/insert_update_delete.sql | 4 +- contrib/pg_tde/sql/key_provider.sql | 36 ++-- contrib/pg_tde/sql/keyprovider_dependency.sql | 10 +- contrib/pg_tde/sql/kmip_test.sql | 4 +- contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 4 +- contrib/pg_tde/sql/recreate_storage.sql | 4 +- contrib/pg_tde/sql/relocate.sql | 2 +- contrib/pg_tde/sql/subtransaction.sql | 4 +- contrib/pg_tde/sql/tablespace.sql | 4 +- contrib/pg_tde/sql/toast_decrypt.sql | 4 +- contrib/pg_tde/sql/vault_v2_test.sql | 8 +- contrib/pg_tde/src/catalog/tde_keyring.c | 24 +-- .../src/catalog/tde_keyring_parse_opts.c | 4 +- .../pg_tde/src/catalog/tde_principal_key.c | 36 ++-- contrib/pg_tde/t/001_basic.pl | 4 +- contrib/pg_tde/t/002_rotate_key.pl | 32 ++-- contrib/pg_tde/t/003_remote_config.pl | 4 +- contrib/pg_tde/t/004_file_config.pl | 4 +- contrib/pg_tde/t/005_multiple_extensions.pl | 4 +- contrib/pg_tde/t/006_remote_vault_config.pl | 4 +- contrib/pg_tde/t/007_tde_heap.pl | 4 +- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 6 +- contrib/pg_tde/t/009_wal_encrypt.pl | 2 +- contrib/pg_tde/t/010_change_key_provider.pl | 22 +-- contrib/pg_tde/t/expected/002_rotate_key.out | 28 +-- .../t/expected/008_key_rotate_tablespace.out | 8 +- contrib/pg_tde/t/expected/009_wal_encrypt.out | 2 +- .../t/expected/010_change_key_provider.out | 20 +- src/bin/pg_waldump/t/003_basic_encrypted.pl | 2 +- .../t/004_save_fullpage_encrypted.pl | 2 +- 66 files changed, 554 insertions(+), 555 deletions(-) diff --git a/ci_scripts/backup/pg_basebackup_test.sh b/ci_scripts/backup/pg_basebackup_test.sh index d9424be77c24a..a14a9836ec7c9 100755 --- a/ci_scripts/backup/pg_basebackup_test.sh +++ b/ci_scripts/backup/pg_basebackup_test.sh @@ -104,8 +104,8 @@ setup_tde_heap(){ sudo -u "$PG_USER" psql -p $PG_PORT -c "DROP DATABASE IF EXISTS $DB_NAME;" sudo -u "$PG_USER" psql -p $PG_PORT -c "CREATE DATABASE $DB_NAME;" sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "CREATE EXTENSION IF NOT EXISTS pg_tde;" - sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_add_key_provider_file('file-vault','$KEYLOCATION');" - sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_set_principal_key('test-db-master-key','file-vault');" + sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_add_database_key_provider_file('file-vault','$KEYLOCATION');" + sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-master-key','file-vault');" sudo -u "$PG_USER" psql -p $PG_PORT -c "ALTER DATABASE $DB_NAME SET default_table_access_method='tde_heap';" sudo -u "$PG_USER" psql -p $PG_PORT -c "SELECT pg_reload_conf();" } diff --git a/ci_scripts/tde_setup.sql b/ci_scripts/tde_setup.sql index 057119f86a5db..21584fd1d6960 100644 --- a/ci_scripts/tde_setup.sql +++ b/ci_scripts/tde_setup.sql @@ -1,4 +1,4 @@ CREATE SCHEMA IF NOT EXISTS tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; -SELECT tde.pg_tde_add_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_set_principal_key('test-db-principal-key', 'reg_file-vault'); +SELECT tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT tde.pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'reg_file-vault'); diff --git a/ci_scripts/tde_setup_global.sql b/ci_scripts/tde_setup_global.sql index 5b4a9629a63e5..4289b29c5baeb 100644 --- a/ci_scripts/tde_setup_global.sql +++ b/ci_scripts/tde_setup_global.sql @@ -2,7 +2,7 @@ CREATE SCHEMA tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; SELECT tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_set_server_principal_key('global-principal-key', 'reg_file-global'); +SELECT tde.pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'reg_file-global'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; ALTER SYSTEM SET default_table_access_method = 'tde_heap'; ALTER SYSTEM SET search_path = "$user",public,tde; diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index be689e7624fc8..bbc51a5fcf5ee 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -112,16 +112,16 @@ _See [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-bu ```sql -- For Vault-V2 key provider - -- pg_tde_add_key_provider_vault_v2(provider_name, vault_token, vault_url, vault_mount_path, vault_ca_path) - SELECT pg_tde_add_key_provider_vault_v2( + -- pg_tde_add_database_key_provider_vault_v2(provider_name, vault_token, vault_url, vault_mount_path, vault_ca_path) + SELECT pg_tde_add_database_key_provider_vault_v2( 'vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/url' ), to_json('secret'::text), NULL); -- For File key provider - -- pg_tde_add_key_provider_file(provider_name, file_path); - SELECT pg_tde_add_key_provider_file('file','/tmp/pgkeyring'); + -- pg_tde_add_database_key_provider_file(provider_name, file_path); + SELECT pg_tde_add_database_key_provider_file('file','/tmp/pgkeyring'); ``` **Note: The `File` provided is intended for development and stores the keys unencrypted in the specified data file.** @@ -129,8 +129,8 @@ _See [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-bu 5. Set the principal key for the database using the `pg_tde_set_principal_key` function. ```sql - -- pg_tde_set_principal_key(principal_key_name, provider_name); - SELECT pg_tde_set_principal_key('my-principal-key','file'); + -- pg_tde_set_principal_key_using_database_key_provider(principal_key_name, provider_name); + SELECT pg_tde_set_principal_key_using_database_key_provider('my-principal-key','file'); ``` 6. Specify `tde_heap` access method during table creation diff --git a/contrib/pg_tde/documentation/docs/external-parameters.md b/contrib/pg_tde/documentation/docs/external-parameters.md index a27e97b03127f..f68aee653e011 100644 --- a/contrib/pg_tde/documentation/docs/external-parameters.md +++ b/contrib/pg_tde/documentation/docs/external-parameters.md @@ -15,7 +15,7 @@ To use the file provider with a file location specified by the `remote` method, use the following command: ``` -SELECT pg_tde_add_key_provider_file( +SELECT pg_tde_add_database_key_provider_file( 'file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ) );" @@ -24,7 +24,7 @@ SELECT pg_tde_add_key_provider_file( Or to use the `file` method, use the following command: ``` -SELECT pg_tde_add_key_provider_file( +SELECT pg_tde_add_database_key_provider_file( 'file-provider', json_object( 'type' VALUE 'remote', 'path' VALUE '/tmp/datafile-location' ) );" diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index a59e1000bb6f7..64c9aa62960df 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -14,8 +14,8 @@ The following functions are also provided for easier management of functionality Use these functions to grant or revoke permissions to manage permissions for the current database. They enable or disable all functions related to the providers and keys on the current database: -* `pg_tde_grant_local_key_management_to_role(role)` -* `pg_tde_revoke_local_key_management_from_role(role)` +* `pg_tde_grant_database_key_management_to_role(role)` +* `pg_tde_revoke_database_key_management_from_role(role)` ### Global scope key management @@ -72,7 +72,7 @@ You can change an existing key provider using the provided functions, which are There are two functions to change existing providers: one to change a provider in the current database, and another one to change a provider in the global scope. -* `pg_tde_change_key_provider_('provider-name', )` +* `pg_tde_change_database_key_provider_('provider-name', )` * `pg_tde_change_global_key_provider_('provider-name', )` When you change a provider, the referred name must exist in the database local or a global scope. @@ -90,14 +90,14 @@ The Vault provider connects to a HashiCorp Vault or an OpenBao server, and store Use the following functions to add the Vault provider: ``` -SELECT pg_tde_add_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); +SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); SELECT pg_tde_add_global_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` These functions change the Vault provider: ``` -SELECT pg_tde_change_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); +SELECT pg_tde_change_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); SELECT pg_tde_change_global_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` @@ -121,14 +121,14 @@ The KMIP provider uses a remote KMIP server. Use these functions to add a KMIP provider: ``` -SELECT pg_tde_add_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); +SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); SELECT pg_tde_add_global_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` These functions change the KMIP provider: ``` -SELECT pg_tde_change_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); +SELECT pg_tde_change_database_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); SELECT pg_tde_change_global_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` @@ -156,14 +156,14 @@ This function is intended for development or quick testing, and stores the keys Add a local keyfile provider: ``` -SELECT pg_tde_add_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); +SELECT pg_tde_add_database_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); SELECT pg_tde_add_global_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); ``` Change a local keyfile provider: ``` -SELECT pg_tde_change_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); +SELECT pg_tde_change_database_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); SELECT pg_tde_change_global_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); ``` @@ -178,7 +178,7 @@ All parameters can be either strings, or JSON objects [referencing remote parame These functions delete an existing provider in the current database or in the global scope: -* `pg_tde_delete_key_provider('provider-name)` +* `pg_tde_delete_database_key_provider('provider-name)` * `pg_tde_delete_global_key_provider('provider-name)` You can only delete key providers that are not currently in use. An error is returned if the current principal key is using the provider you are trying to delete. @@ -189,7 +189,7 @@ If the use of global key providers is enabled via the `pg_tde.inherit_global` GU These functions list the details of all key providers for the current database or for the global scope, including all configuration values: -* `pg_tde_list_all_key_providers()` +* `pg_tde_list_all_database_key_providers()` * `pg_tde_list_all_global_key_providers()` **All configuration values include possibly sensitive values, such as passwords. Never specify these directly, use the remote configuration option instead.** @@ -201,12 +201,12 @@ Use these functions to create a new principal key for a specific scope such as a Princial keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. -### pg_tde_set_principal_key +### pg_tde_set_principal_key_using_database_key_provider Creates or rotates the principal key for the current database using the specified database key provider and key name. ``` -SELECT pg_tde_set_principal_key('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_principal_key_using_database_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -215,12 +215,12 @@ SELECT pg_tde_set_principal_key('name-of-the-principal-key','provider-name','ens If the provider already stores a key by that name, the function returns an error. * If set to `false`, an existing principal key may be reused. -### pg_tde_set_global_principal_key +### pg_tde_set_principal_key_using_global_key_provider Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. ``` -SELECT pg_tde_set_global_principal_key('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -229,12 +229,12 @@ SELECT pg_tde_set_global_principal_key('name-of-the-principal-key','provider-nam If the provider already stores a key by that name, the function returns an error. * If set to `false`, an existing principal key may be reused. -### pg_tde_set_server_principal_key +### pg_tde_set_server_principal_key_using_global_key_provider -Creates or rotates the global principal key using the specified key provider. Use this function to set a principal key for WAL encryption. +Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. ``` -SELECT pg_tde_set_server_principal_key('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_server_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -244,14 +244,14 @@ The `ensure_new_key` parameter instructs the function how to handle a principal * If set to `false`, an existing principal key may be reused. -### pg_tde_set_default_principal_key +### pg_tde_set_default_principal_key_using_global_key_provider -Creates or rotates the default principal key for the server using the specified key provider. +Creates or rotates the default principal key for the server using the specified global key provider. The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. ``` -SELECT pg_tde_set_default_principal_key('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_default_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -290,12 +290,12 @@ Displays information about the principal key for the current database, if it exi SELECT pg_tde_principal_key_info() ``` -### pg_tde_global_principal_key_info +### pg_tde_server_principal_key_info -Displays information about the principal key for the global scope, if exists. +Displays information about the principal key for the server scope, if exists. ``` -SELECT pg_tde_global_principal_key_info() +SELECT pg_tde_server_principal_key_info() ``` ### pg_tde_verify_principal_key @@ -314,9 +314,9 @@ If any of the above checks fail, the function reports an error. SELECT pg_tde_verify_principal_key() ``` -### pg_tde_verify_global_principal_key +### pg_tde_verify_server_principal_key -This function checks that the global scope has a properly functional encryption setup, which means: +This function checks that the server scope has a properly functional encryption setup, which means: * A key provider is configured * The key provider is accessible using the specified configuration @@ -327,5 +327,5 @@ This function checks that the global scope has a properly functional encryption If any of the above checks fail, the function reports an error. ``` -SELECT pg_tde_verify_principal_key() +SELECT pg_tde_verify_server_principal_key() ``` diff --git a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md index 6d8b0f09300c6..8df576dd0d632 100644 --- a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md @@ -61,7 +61,7 @@ You must do these steps for every database where you have created the extension. For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ``` - SELECT pg_tde_add_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); + SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` where: @@ -75,7 +75,7 @@ You must do these steps for every database where you have created the extension. :material-information: Warning: This example is for testing purposes only: ``` - SELECT pg_tde_add_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); + SELECT pg_tde_add_database_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); ``` === "With HashiCorp Vault" @@ -83,7 +83,7 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_key_provider_vault_v2('provider-name','root_token','url','mount','ca_path'); + SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','root_token','url','mount','ca_path'); ``` where: @@ -96,7 +96,7 @@ You must do these steps for every database where you have created the extension. :material-information: Warning: This example is for testing purposes only: ``` - SELECT pg_tde_add_key_provider_file_vault_v2('my-vault','http://vault.vault.svc.cluster.local:8200,'secret/data','hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', NULL); + SELECT pg_tde_add_database_key_provider_file_vault_v2('my-vault','http://vault.vault.svc.cluster.local:8200,'secret/data','hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', NULL); ``` === "With a keyring file" @@ -104,20 +104,20 @@ You must do these steps for every database where you have created the extension. This setup is intended for development and stores the keys unencrypted in the specified data file. ```sql - SELECT pg_tde_add_key_provider_file('provider-name','/path/to/the/keyring/data.file'); + SELECT pg_tde_add_database_key_provider_file('provider-name','/path/to/the/keyring/data.file'); ``` :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_add_key_provider_file('file-keyring','/tmp/pg_tde_test_local_keyring.per'); + SELECT pg_tde_add_database_key_provider_file('file-keyring','/tmp/pg_tde_test_local_keyring.per'); ``` 2. Add a principal key ```sql - SELECT pg_tde_set_principal_key('name-of-the-principal-key', 'provider-name','ensure_new_key'); + SELECT pg_tde_set_principal_key_using_database_key_provider('name-of-the-principal-key', 'provider-name','ensure_new_key'); ``` where: @@ -129,7 +129,7 @@ You must do these steps for every database where you have created the extension. :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_set_principal_key('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` The key is auto-generated. diff --git a/contrib/pg_tde/documentation/docs/setup.md b/contrib/pg_tde/documentation/docs/setup.md index 20c3514e50e20..fb2dde759f4ad 100644 --- a/contrib/pg_tde/documentation/docs/setup.md +++ b/contrib/pg_tde/documentation/docs/setup.md @@ -112,7 +112,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo 2. Add a default principal key ```sql - SELECT pg_tde_set_default_principal_key('name-of-the-principal-key','provider-name','ensure_new_key'); + SELECT pg_tde_set_default_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); ``` where: @@ -124,7 +124,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo :material-information: Warning: This example is for testing purposes only. Replace the key name and provider name with your values: ```sql - SELECT pg_tde_set_global_principal_key('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` The key is auto-generated. diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 9abdddd72d685..950ff970c0969 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -32,7 +32,7 @@ Here's what to do: :material-information: Warning: This example is for testing purposes only: ``` - SELECT pg_tde_add_key_global_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); + SELECT pg_tde_add_key_using_global_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); ``` === "With HashiCorp Vault" @@ -61,7 +61,7 @@ Here's what to do: 3. Create principal key ```sql - SELECT pg_tde_set_server_principal_key('principal-key', 'provider-name'); + SELECT pg_tde_set_server_principal_key_using_global_key_provider('principal-key', 'provider-name'); ``` 4. Enable WAL level encryption using the `ALTER SYSTEM` command. You need the privileges of the superuser to run this command: diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index e2df4fe87bb87..220460438cd1b 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -2,14 +2,14 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -ERROR: permission denied for function pg_tde_add_key_provider_file -SELECT pg_tde_set_principal_key('test-db-principal-key', 'file-vault'); -ERROR: permission denied for function pg_tde_set_principal_key +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +ERROR: permission denied for function pg_tde_add_database_key_provider_file +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); +ERROR: permission denied for function pg_tde_set_principal_key_using_database_key_provider RESET ROLE; -SELECT pg_tde_grant_local_key_management_to_role('regress_pg_tde_access_control'); - pg_tde_grant_local_key_management_to_role -------------------------------------------- +SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); + pg_tde_grant_database_key_management_to_role +---------------------------------------------- (1 row) @@ -21,25 +21,25 @@ SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_add_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key', 'file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+-------------------------------------------------------------- 1 | file-vault | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} @@ -61,8 +61,8 @@ SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked -SELECT * FROM pg_tde_list_all_key_providers(); -ERROR: permission denied for function pg_tde_list_all_key_providers +SELECT * FROM pg_tde_list_all_database_key_providers(); +ERROR: permission denied for function pg_tde_list_all_database_key_providers SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); ERROR: permission denied for function pg_tde_principal_key_info RESET ROLE; diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index d1d343a448e1f..4e9f247e91f42 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index 096d557f73614..9469ee2ec150e 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -1,14 +1,14 @@ -- Just checking there are no mem debug WARNINGs during the cache population CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index 1e66f894466d4..0ed80be28665b 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index ff23a39dea7d5..08a355b267fbc 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi -3 (1 row) -SELECT pg_tde_set_default_principal_key('default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key ----------------------------------- +SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); + pg_tde_set_default_principal_key_using_global_key_provider +------------------------------------------------------------ (1 row) @@ -67,9 +67,9 @@ SELECT key_provider_id, key_provider_name, principal_key_name (1 row) \c :regress_database -SELECT pg_tde_set_default_principal_key('new-default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key ----------------------------------- +SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); + pg_tde_set_default_principal_key_using_global_key_provider +------------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out index f8a6b17056c33..5280f4ab6b547 100644 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ b/contrib/pg_tde/expected/default_principal_key_1.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi -4 (1 row) -SELECT pg_tde_set_default_principal_key('default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key ----------------------------------- +SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); + pg_tde_set_default_principal_key_using_global_key_provider +------------------------------------------------------------ (1 row) @@ -68,9 +68,9 @@ SELECT key_provider_id, key_provider_name, principal_key_name (1 row) \c :regress_database -SELECT pg_tde_set_default_principal_key('new-default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key ----------------------------------- +SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); + pg_tde_set_default_principal_key_using_global_key_provider +------------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/delete_key_provider.out b/contrib/pg_tde/expected/delete_key_provider.out index f4f4ed109db53..1c0a7afb375ae 100644 --- a/contrib/pg_tde/expected/delete_key_provider.out +++ b/contrib/pg_tde/expected/delete_key_provider.out @@ -2,71 +2,71 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) -SELECT pg_tde_delete_key_provider('file-provider'); - pg_tde_delete_key_provider ----------------------------- +SELECT pg_tde_delete_database_key_provider('file-provider'); + pg_tde_delete_database_key_provider +------------------------------------- (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- (0 rows) -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+------------------------------------------------------------ 2 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) -SELECT pg_tde_delete_key_provider('file-provider'); - pg_tde_delete_key_provider ----------------------------- +SELECT pg_tde_delete_database_key_provider('file-provider'); + pg_tde_delete_database_key_provider +------------------------------------- (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- (0 rows) -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 3 +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 3 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+------------------------------------------------------------ 3 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) -SELECT pg_tde_delete_key_provider('file-provider'); - pg_tde_delete_key_provider ----------------------------- +SELECT pg_tde_delete_database_key_provider('file-provider'); + pg_tde_delete_database_key_provider +------------------------------------- (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- (0 rows) diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index f9ac74fbf988c..275fc2fffd297 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 47930b72efe5f..cb1c94e59f10f 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -2,32 +2,32 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_add_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- (0 rows) -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) -SELECT pg_tde_add_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------- 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} @@ -36,9 +36,9 @@ SELECT * FROM pg_tde_list_all_key_providers(); SELECT pg_tde_verify_principal_key(); ERROR: principal key not configured for current database -SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) @@ -48,23 +48,23 @@ SELECT pg_tde_verify_principal_key(); (1 row) -SELECT pg_tde_change_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); ERROR: key provider "not-existent-provider" does not exists -HINT: Use pg_tde_add_key_provider interface to create the key provider -SELECT * FROM pg_tde_list_all_key_providers(); +HINT: Create the key provider +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------- 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_change_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); - pg_tde_change_key_provider_file ---------------------------------- - 1 +SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} @@ -73,9 +73,9 @@ SELECT * FROM pg_tde_list_all_key_providers(); SELECT pg_tde_verify_principal_key(); ERROR: failed to retrieve principal key test-db-principal-key from keyring with ID 1 -SELECT pg_tde_change_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} @@ -103,9 +103,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- TODO: verify that we can also can change the type of it -- fails -SELECT pg_tde_delete_key_provider('file-provider'); +SELECT pg_tde_delete_database_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); id | provider_name ----+---------------- 1 | file-provider @@ -113,13 +113,13 @@ SELECT id, provider_name FROM pg_tde_list_all_key_providers(); (2 rows) -- works -SELECT pg_tde_delete_key_provider('file-provider2'); - pg_tde_delete_key_provider ----------------------------- +SELECT pg_tde_delete_database_key_provider('file-provider2'); + pg_tde_delete_database_key_provider +------------------------------------- (1 row) -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); id | provider_name ----+--------------- 1 | file-provider @@ -132,9 +132,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -2 | file-keyring2 (2 rows) -SELECT pg_tde_set_global_principal_key('test-db-principal-key', 'file-keyring', false); - pg_tde_set_global_principal_key ---------------------------------- +SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); + pg_tde_set_principal_key_using_global_key_provider +---------------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out index 18ba31455eba3..af5559bb47aef 100644 --- a/contrib/pg_tde/expected/key_provider_1.out +++ b/contrib/pg_tde/expected/key_provider_1.out @@ -2,32 +2,32 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_add_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- (0 rows) -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) -SELECT pg_tde_add_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------- 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} @@ -36,9 +36,9 @@ SELECT * FROM pg_tde_list_all_key_providers(); SELECT pg_tde_verify_principal_key(); ERROR: principal key not configured for current database -SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) @@ -48,23 +48,23 @@ SELECT pg_tde_verify_principal_key(); (1 row) -SELECT pg_tde_change_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); ERROR: key provider "not-existent-provider" does not exists -HINT: Use pg_tde_add_key_provider interface to create the key provider -SELECT * FROM pg_tde_list_all_key_providers(); +HINT: Create the key provider +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------- 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_change_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); - pg_tde_change_key_provider_file ---------------------------------- - 1 +SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} @@ -73,9 +73,9 @@ SELECT * FROM pg_tde_list_all_key_providers(); SELECT pg_tde_verify_principal_key(); ERROR: failed to retrieve principal key test-db-principal-key from keyring with ID 1 -SELECT pg_tde_change_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------------------------ 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} @@ -104,9 +104,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- TODO: verify that we can also can change the type of it -- fails -SELECT pg_tde_delete_key_provider('file-provider'); +SELECT pg_tde_delete_database_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); id | provider_name ----+---------------- 1 | file-provider @@ -114,13 +114,13 @@ SELECT id, provider_name FROM pg_tde_list_all_key_providers(); (2 rows) -- works -SELECT pg_tde_delete_key_provider('file-provider2'); - pg_tde_delete_key_provider ----------------------------- +SELECT pg_tde_delete_database_key_provider('file-provider2'); + pg_tde_delete_database_key_provider +------------------------------------- (1 row) -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); id | provider_name ----+--------------- 1 | file-provider @@ -134,9 +134,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -3 | file-keyring2 (3 rows) -SELECT pg_tde_set_global_principal_key('test-db-principal-key', 'file-keyring', false); - pg_tde_set_global_principal_key ---------------------------------- +SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); + pg_tde_set_principal_key_using_global_key_provider +---------------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/keyprovider_dependency.out b/contrib/pg_tde/expected/keyprovider_dependency.out index e9133e52f72d5..c3d36df527cad 100644 --- a/contrib/pg_tde/expected/keyprovider_dependency.out +++ b/contrib/pg_tde/expected/keyprovider_dependency.out @@ -1,23 +1,23 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_add_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT pg_tde_add_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); - pg_tde_add_key_provider_vault_v2 ----------------------------------- - 3 +SELECT pg_tde_add_database_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); + pg_tde_add_database_key_provider_vault_v2 +------------------------------------------- + 3 (1 row) -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+----------------------------------------------------------------------------------------------------------------------------------------------- 1 | mk-file | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} @@ -25,9 +25,9 @@ SELECT * FROM pg_tde_list_all_key_providers(); 3 | V2-vault | vault-v2 | {"type" : "vault-v2", "url" : "percona.com/vault-v2/percona", "token" : "vault-token", "mountPath" : "/mount/dev", "caPath" : "ca-cert-auth"} (3 rows) -SELECT pg_tde_set_principal_key('test-db-principal-key','mk-file'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','mk-file'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index 71f5ac083d84c..bcb708a299833 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -1,13 +1,13 @@ CREATE EXTENSION pg_tde; -SELECT pg_tde_add_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); - pg_tde_add_key_provider_kmip ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); + pg_tde_add_database_key_provider_kmip +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('kmip-principal-key','kmip-prov'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('kmip-principal-key','kmip-prov'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index f9607ae10a0ae..f8c17168c139f 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -2,15 +2,15 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index 0f8ce3d66c99d..e3ad3b0c66d87 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/relocate.out b/contrib/pg_tde/expected/relocate.out index fef8c84030243..af00e872ce9db 100644 --- a/contrib/pg_tde/expected/relocate.out +++ b/contrib/pg_tde/expected/relocate.out @@ -3,10 +3,10 @@ SET client_min_messages = 'warning'; DROP EXTENSION IF EXISTS pg_tde; CREATE SCHEMA other; CREATE EXTENSION pg_tde SCHEMA other; -SELECT other.pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) SELECT other.pg_tde_grant_key_viewer_to_role('public'); diff --git a/contrib/pg_tde/expected/subtransaction.out b/contrib/pg_tde/expected/subtransaction.out index 7508be79bcb17..5c08fca3c4751 100644 --- a/contrib/pg_tde/expected/subtransaction.out +++ b/contrib/pg_tde/expected/subtransaction.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index 6384afeaa4c66..353ef4dd1edd4 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index fff4e7744add7..014d98fc6a3e7 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/toast_decrypt_1.out b/contrib/pg_tde/expected/toast_decrypt_1.out index 6765617555537..774578aee406f 100644 --- a/contrib/pg_tde/expected/toast_decrypt_1.out +++ b/contrib/pg_tde/expected/toast_decrypt_1.out @@ -1,14 +1,14 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; NOTICE: extension "pg_tde" already exists, skipping -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_key_provider_file ------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 (1 row) -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 0629d847848bf..a88f5a4c75f4f 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; \getenv root_token ROOT_TOKEN -SELECT pg_tde_add_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); - pg_tde_add_key_provider_vault_v2 ----------------------------------- - 1 +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); + pg_tde_add_database_key_provider_vault_v2 +------------------------------------------- + 1 (1 row) -- FAILS -SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-incorrect'); +SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-incorrect'); ERROR: Invalid HTTP response from keyring provider "vault-incorrect": 404 CREATE TABLE test_enc( id SERIAL, @@ -16,15 +16,15 @@ CREATE TABLE test_enc( ) USING tde_heap; ERROR: principal key not configured HINT: create one using pg_tde_set_principal_key before using encrypted tables -SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); - pg_tde_add_key_provider_vault_v2 ----------------------------------- - 2 +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); + pg_tde_add_database_key_provider_vault_v2 +------------------------------------------- + 2 (1 row) -SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-v2'); - pg_tde_set_principal_key --------------------------- +SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-v2'); + pg_tde_set_principal_key_using_database_key_provider +------------------------------------------------------ (1 row) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 9cd513a0f2f2e..2f70630b0e385 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -4,32 +4,32 @@ \echo Use "CREATE EXTENSION pg_tde" to load this file. \quit -- Key Provider Management -CREATE FUNCTION pg_tde_add_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_add_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) RETURNS INT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_add_key_provider_file(provider_name TEXT, file_path TEXT) +CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS INT LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. - SELECT pg_tde_add_key_provider('file', provider_name, + SELECT pg_tde_add_database_key_provider('file', provider_name, json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); END; -CREATE FUNCTION pg_tde_add_key_provider_file(provider_name TEXT, file_path JSON) +CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path JSON) RETURNS INT LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. - SELECT pg_tde_add_key_provider('file', provider_name, + SELECT pg_tde_add_database_key_provider('file', provider_name, json_object('type' VALUE 'file', 'path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_add_key_provider_vault_v2(provider_name TEXT, +CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_token TEXT, vault_url TEXT, vault_mount_path TEXT, @@ -39,7 +39,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_add_key_provider('vault-v2', provider_name, + SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('type' VALUE 'vault-v2', 'url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), @@ -47,7 +47,7 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_add_key_provider_vault_v2(provider_name TEXT, +CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_token JSON, vault_url JSON, vault_mount_path JSON, @@ -57,7 +57,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_add_key_provider('vault-v2', provider_name, + SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('type' VALUE 'vault-v2', 'url' VALUE vault_url, 'token' VALUE vault_token, @@ -65,7 +65,7 @@ BEGIN ATOMIC 'caPath' VALUE vault_ca_path)); END; -CREATE FUNCTION pg_tde_add_key_provider_kmip(provider_name TEXT, +CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, kmip_ca_path TEXT, @@ -75,7 +75,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. - SELECT pg_tde_add_key_provider('kmip', provider_name, + SELECT pg_tde_add_database_key_provider('kmip', provider_name, json_object('type' VALUE 'kmip', 'host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, @@ -83,7 +83,7 @@ BEGIN ATOMIC 'certPath' VALUE COALESCE(kmip_cert_path, ''))); END; -CREATE FUNCTION pg_tde_add_key_provider_kmip(provider_name TEXT, +CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_host JSON, kmip_port JSON, kmip_ca_path JSON, @@ -93,7 +93,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. - SELECT pg_tde_add_key_provider('kmip', provider_name, + SELECT pg_tde_add_database_key_provider('kmip', provider_name, json_object('type' VALUE 'kmip', 'host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -101,12 +101,8 @@ BEGIN ATOMIC 'certPath' VALUE kmip_cert_path)); END; -CREATE FUNCTION pg_tde_set_default_principal_key(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) -RETURNS VOID -AS 'MODULE_PATHNAME' -LANGUAGE C; -CREATE FUNCTION pg_tde_list_all_key_providers +CREATE FUNCTION pg_tde_list_all_database_key_providers (OUT id INT, OUT provider_name TEXT, OUT provider_type TEXT, @@ -223,32 +219,32 @@ BEGIN ATOMIC END; -- Key Provider Management -CREATE FUNCTION pg_tde_change_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_change_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) RETURNS INT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_change_key_provider_file(provider_name TEXT, file_path TEXT) +CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS INT LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. - SELECT pg_tde_change_key_provider('file', provider_name, + SELECT pg_tde_change_database_key_provider('file', provider_name, json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); END; -CREATE FUNCTION pg_tde_change_key_provider_file(provider_name TEXT, file_path JSON) +CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path JSON) RETURNS INT LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. - SELECT pg_tde_change_key_provider('file', provider_name, + SELECT pg_tde_change_database_key_provider('file', provider_name, json_object('type' VALUE 'file', 'path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_change_key_provider_vault_v2(provider_name TEXT, +CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, vault_token TEXT, vault_url TEXT, vault_mount_path TEXT, @@ -258,7 +254,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_change_key_provider('vault-v2', provider_name, + SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('type' VALUE 'vault-v2', 'url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), @@ -266,7 +262,7 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_change_key_provider_vault_v2(provider_name TEXT, +CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, vault_token JSON, vault_url JSON, vault_mount_path JSON, @@ -276,7 +272,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_change_key_provider('vault-v2', provider_name, + SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('type' VALUE 'vault-v2', 'url' VALUE vault_url, 'token' VALUE vault_token, @@ -284,7 +280,7 @@ BEGIN ATOMIC 'caPath' VALUE vault_ca_path)); END; -CREATE FUNCTION pg_tde_change_key_provider_kmip(provider_name TEXT, +CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, kmip_ca_path TEXT, @@ -294,7 +290,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. - SELECT pg_tde_change_key_provider('kmip', provider_name, + SELECT pg_tde_change_database_key_provider('kmip', provider_name, json_object('type' VALUE 'kmip', 'host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, @@ -302,7 +298,7 @@ BEGIN ATOMIC 'certPath' VALUE COALESCE(kmip_cert_path, ''))); END; -CREATE FUNCTION pg_tde_change_key_provider_kmip(provider_name TEXT, +CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_host JSON, kmip_port JSON, kmip_ca_path JSON, @@ -312,7 +308,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. - SELECT pg_tde_change_key_provider('kmip', provider_name, + SELECT pg_tde_change_database_key_provider('kmip', provider_name, json_object('type' VALUE 'kmip', 'host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -461,21 +457,26 @@ STRICT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_principal_key(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_principal_key_using_database_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_global_principal_key(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_server_principal_key(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +CREATE FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +RETURNS VOID +AS 'MODULE_PATHNAME' +LANGUAGE C; + CREATE FUNCTION pg_tde_extension_initialize() RETURNS VOID LANGUAGE C @@ -486,7 +487,7 @@ RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_verify_global_principal_key() +CREATE FUNCTION pg_tde_verify_server_principal_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; @@ -499,7 +500,7 @@ RETURNS TABLE ( principal_key_name text, LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_global_principal_key_info() +CREATE FUNCTION pg_tde_server_principal_key_info() RETURNS TABLE ( principal_key_name text, key_provider_name text, key_provider_id integer, @@ -512,7 +513,7 @@ RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_delete_key_provider(provider_name TEXT) +CREATE FUNCTION pg_tde_delete_database_key_provider(provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; @@ -578,41 +579,40 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_global_principal_key(text, text, BOOLEAN) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_server_principal_key(text, text, BOOLEAN) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_default_principal_key(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); END; $$; -CREATE FUNCTION pg_tde_grant_local_key_management_to_role( +CREATE FUNCTION pg_tde_grant_database_key_management_to_role( target_role TEXT) RETURNS VOID LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider(text, text, JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(text, text, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_vault_v2(text, text, text, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, json) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, text, text, text, text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, text, int, text, text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider(text, text, JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(text, text, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_vault_v2(text, text, text,text,text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_vault_v2(text, JSON, JSON,JSON,JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, json) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, text, text,text,text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, JSON, JSON,JSON,JSON) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, text, int, text, text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_key_provider(text) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_principal_key(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_principal_key_using_database_key_provider(text, text, BOOLEAN) TO %I', target_role); END; $$; @@ -623,13 +623,13 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_key_providers() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_principal_key_info() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_global_principal_key_info() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_server_principal_key_info() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_principal_key() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_global_principal_key() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_server_principal_key() TO %I', target_role); END; $$; @@ -660,41 +660,40 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_global_principal_key(text, text, BOOLEAN) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_server_principal_key(text, text, BOOLEAN) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_default_principal_key(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); END; $$; -CREATE FUNCTION pg_tde_revoke_local_key_management_from_role( +CREATE FUNCTION pg_tde_revoke_database_key_management_from_role( target_role TEXT) RETURNS VOID LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider(text, text, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider(text, text, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, json) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider(text, text, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider(text, text, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, json) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_key_provider(text) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_principal_key(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_principal_key_using_database_key_provider(text, text, BOOLEAN) FROM %I', target_role); END; $$; @@ -705,13 +704,13 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_key_providers() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_principal_key_info() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_global_principal_key_info() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_server_principal_key_info() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_principal_key() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_global_principal_key() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_server_principal_key() FROM %I', target_role); END; $$; @@ -723,12 +722,12 @@ SET search_path = @extschema@ AS $$ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_global_key_management_to_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_local_key_management_to_role(TEXT) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_global_key_management_from_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_local_key_management_from_role(TEXT) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) TO %I', target_role); END; @@ -742,19 +741,19 @@ SET search_path = @extschema@ AS $$ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_global_key_management_to_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_local_key_management_to_role(TEXT) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_global_key_management_from_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_local_key_management_from_role(TEXT) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) FROM %I', target_role); END; $$; -- Revoking all the privileges from the public role -SELECT pg_tde_revoke_local_key_management_from_role('public'); +SELECT pg_tde_revoke_database_key_management_from_role('public'); SELECT pg_tde_revoke_global_key_management_from_role('public'); SELECT pg_tde_revoke_grant_management_from_role('public'); SELECT pg_tde_revoke_key_viewer_from_role('public'); diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index fc6d76ac1f026..20440d5b277ac 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -5,21 +5,21 @@ CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key', 'file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); RESET ROLE; -SELECT pg_tde_grant_local_key_management_to_role('regress_pg_tde_access_control'); +SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_add_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key', 'file-vault'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); +SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); RESET ROLE; @@ -29,7 +29,7 @@ SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); RESET ROLE; diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 7f578d3fb3a50..7589b0da49073 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/cache_alloc.sql b/contrib/pg_tde/sql/cache_alloc.sql index 9e89ba2efb180..59927ec0c362f 100644 --- a/contrib/pg_tde/sql/cache_alloc.sql +++ b/contrib/pg_tde/sql/cache_alloc.sql @@ -2,8 +2,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); do $$ DECLARE idx integer; diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index 34a0955679730..c1818c2888d9e 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 1f60541e05231..ee1193601f4c1 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_principal_key.per'); -SELECT pg_tde_set_default_principal_key('default-principal-key', 'file-provider', false); +SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); @@ -53,7 +53,7 @@ SELECT key_provider_id, key_provider_name, principal_key_name \c :regress_database -SELECT pg_tde_set_default_principal_key('new-default-principal-key', 'file-provider', false); +SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); diff --git a/contrib/pg_tde/sql/delete_key_provider.sql b/contrib/pg_tde/sql/delete_key_provider.sql index 431c97d6cc8fa..781297ee9b6b6 100644 --- a/contrib/pg_tde/sql/delete_key_provider.sql +++ b/contrib/pg_tde/sql/delete_key_provider.sql @@ -2,19 +2,19 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_key_providers(); -SELECT pg_tde_delete_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); +SELECT pg_tde_delete_database_key_provider('file-provider'); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_key_providers(); -SELECT pg_tde_delete_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); +SELECT pg_tde_delete_database_key_provider('file-provider'); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_key_providers(); -SELECT pg_tde_delete_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); +SELECT pg_tde_delete_database_key_provider('file-provider'); +SELECT * FROM pg_tde_list_all_database_key_providers(); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index 3231a220a7e36..1ca2535a26bda 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE albums ( id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 9732440bdb070..62a9e1d4a00f4 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -2,29 +2,29 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); -SELECT pg_tde_add_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_add_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_add_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_verify_principal_key(); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); SELECT pg_tde_verify_principal_key(); -SELECT pg_tde_change_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_change_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); +SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_verify_principal_key(); -SELECT pg_tde_change_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); @@ -35,16 +35,16 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- TODO: verify that we can also can change the type of it -- fails -SELECT pg_tde_delete_key_provider('file-provider'); -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_delete_database_key_provider('file-provider'); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); -- works -SELECT pg_tde_delete_key_provider('file-provider2'); -SELECT id, provider_name FROM pg_tde_list_all_key_providers(); +SELECT pg_tde_delete_database_key_provider('file-provider2'); +SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -SELECT pg_tde_set_global_principal_key('test-db-principal-key', 'file-keyring', false); +SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); diff --git a/contrib/pg_tde/sql/keyprovider_dependency.sql b/contrib/pg_tde/sql/keyprovider_dependency.sql index 2c56d2d9e38e2..35ae5770724fe 100644 --- a/contrib/pg_tde/sql/keyprovider_dependency.sql +++ b/contrib/pg_tde/sql/keyprovider_dependency.sql @@ -1,11 +1,11 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_add_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); -SELECT pg_tde_add_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); +SELECT pg_tde_add_database_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); +SELECT pg_tde_add_database_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); -SELECT * FROM pg_tde_list_all_key_providers(); +SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_set_principal_key('test-db-principal-key','mk-file'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','mk-file'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index 85db4e9766e53..79877c2debd98 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -1,7 +1,7 @@ CREATE EXTENSION pg_tde; -SELECT pg_tde_add_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); -SELECT pg_tde_set_principal_key('kmip-principal-key','kmip-prov'); +SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); +SELECT pg_tde_set_principal_key_using_database_key_provider('kmip-principal-key','kmip-prov'); CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index aa6d0c07ac17d..f11f50200295d 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -2,8 +2,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_principal_key_info(); -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 4389144e5de7e..778baed5b1b90 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/relocate.sql b/contrib/pg_tde/sql/relocate.sql index a18cb38095133..d9ce03b34f937 100644 --- a/contrib/pg_tde/sql/relocate.sql +++ b/contrib/pg_tde/sql/relocate.sql @@ -6,7 +6,7 @@ CREATE SCHEMA other; CREATE EXTENSION pg_tde SCHEMA other; -SELECT other.pg_tde_add_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); SELECT other.pg_tde_grant_key_viewer_to_role('public'); diff --git a/contrib/pg_tde/sql/subtransaction.sql b/contrib/pg_tde/sql/subtransaction.sql index 681d505092a7f..c93b2d67e7e6e 100644 --- a/contrib/pg_tde/sql/subtransaction.sql +++ b/contrib/pg_tde/sql/subtransaction.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); BEGIN; -- Nesting level 1 diff --git a/contrib/pg_tde/sql/tablespace.sql b/contrib/pg_tde/sql/tablespace.sql index 102e8b755c535..86888fbc97390 100644 --- a/contrib/pg_tde/sql/tablespace.sql +++ b/contrib/pg_tde/sql/tablespace.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE test(num1 bigint, num2 double precision, t text) USING tde_heap; INSERT INTO test(num1, num2, t) diff --git a/contrib/pg_tde/sql/toast_decrypt.sql b/contrib/pg_tde/sql/toast_decrypt.sql index 073e6bf27f83e..c97702c5fea2b 100644 --- a/contrib/pg_tde/sql/toast_decrypt.sql +++ b/contrib/pg_tde/sql/toast_decrypt.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE src (f1 TEXT STORAGE EXTERNAL) USING tde_heap; INSERT INTO src VALUES(repeat('abcdeF',1000)); diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 0e210dc1a65ba..1e4e9c9a1f3a6 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -2,9 +2,9 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; \getenv root_token ROOT_TOKEN -SELECT pg_tde_add_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); -- FAILS -SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-incorrect'); +SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-incorrect'); CREATE TABLE test_enc( id SERIAL, @@ -12,8 +12,8 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; -SELECT pg_tde_add_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); -SELECT pg_tde_set_principal_key('vault-v2-principal-key','vault-v2'); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-v2'); CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 7fd6649ee7358..87012f21d3327 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -76,22 +76,22 @@ static void simple_list_free(SimplePtrList *list); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); -PG_FUNCTION_INFO_V1(pg_tde_add_key_provider); -Datum pg_tde_add_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_add_database_key_provider); +Datum pg_tde_add_database_key_provider(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_tde_add_global_key_provider); Datum pg_tde_add_global_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_change_key_provider); -Datum pg_tde_change_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_change_database_key_provider); +Datum pg_tde_change_database_key_provider(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_tde_change_global_key_provider); Datum pg_tde_change_global_key_provider(PG_FUNCTION_ARGS); static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_list_all_key_providers); -Datum pg_tde_list_all_key_providers(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_list_all_database_key_providers); +Datum pg_tde_list_all_database_key_providers(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); Datum pg_tde_list_all_global_key_providers(PG_FUNCTION_ARGS); @@ -206,7 +206,7 @@ cleanup_key_provider_info(Oid databaseId) } Datum -pg_tde_change_key_provider(PG_FUNCTION_ARGS) +pg_tde_change_database_key_provider(PG_FUNCTION_ARGS) { return pg_tde_change_key_provider_internal(fcinfo, MyDatabaseId); } @@ -256,7 +256,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) } Datum -pg_tde_add_key_provider(PG_FUNCTION_ARGS) +pg_tde_add_database_key_provider(PG_FUNCTION_ARGS) { return pg_tde_add_key_provider_internal(fcinfo, MyDatabaseId); } @@ -301,15 +301,15 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) } Datum -pg_tde_list_all_key_providers(PG_FUNCTION_ARGS) +pg_tde_list_all_database_key_providers(PG_FUNCTION_ARGS) { - return pg_tde_list_all_key_providers_internal("pg_tde_list_all_key_providers", false, fcinfo); + return pg_tde_list_all_key_providers_internal("pg_tde_list_all_database_key_providers_database", false, fcinfo); } Datum pg_tde_list_all_global_key_providers(PG_FUNCTION_ARGS) { - return pg_tde_list_all_key_providers_internal("pg_tde_list_all_key_providers_global", true, fcinfo); + return pg_tde_list_all_key_providers_internal("pg_tde_list_all_database_key_providers_global", true, fcinfo); } static Datum @@ -921,7 +921,7 @@ GetKeyProviderByName(const char *provider_name, Oid dbOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key provider \"%s\" does not exists", provider_name), - errhint("Use pg_tde_add_key_provider interface to create the key provider"))); + errhint("Create the key provider"))); } return keyring; } diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 6512f260776cc..8085c34778586 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -86,8 +86,8 @@ static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { [JK_FIELD_PATH] = "path", /* - * These values should match pg_tde_add_key_provider_vault_v2 and - * pg_tde_add_key_provider_file SQL interfaces + * These values should match pg_tde_add_database_key_provider_vault_v2 and + * pg_tde_add_database_key_provider_file SQL interfaces */ [JF_FILE_PATH] = "path", [JK_VAULT_TOKEN] = "token", diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 5539638683995..e989bfe5d391f 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -49,11 +49,11 @@ #ifndef FRONTEND -PG_FUNCTION_INFO_V1(pg_tde_delete_key_provider); +PG_FUNCTION_INFO_V1(pg_tde_delete_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_verify_principal_key); -PG_FUNCTION_INFO_V1(pg_tde_verify_global_principal_key); +PG_FUNCTION_INFO_V1(pg_tde_verify_server_principal_key); typedef struct TdePrincipalKeySharedState { @@ -110,17 +110,17 @@ static bool pg_tde_verify_principal_key_internal(Oid databaseOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global); -PG_FUNCTION_INFO_V1(pg_tde_set_default_principal_key); -Datum pg_tde_set_default_principal_key(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_default_principal_key_using_global_key_provider); +Datum pg_tde_set_default_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_principal_key); -Datum pg_tde_set_principal_key(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_principal_key_using_database_key_provider); +Datum pg_tde_set_principal_key_using_database_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_global_principal_key); -Datum pg_tde_set_principal_key(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_principal_key_using_global_key_provider); +Datum pg_tde_set_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_server_principal_key); -Datum pg_tde_set_principal_key(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_server_principal_key_using_global_key_provider); +Datum pg_tde_set_server_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); enum global_status { @@ -485,7 +485,7 @@ clear_principal_key_cache(Oid databaseId) */ Datum -pg_tde_set_default_principal_key(PG_FUNCTION_ARGS) +pg_tde_set_default_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -497,7 +497,7 @@ pg_tde_set_default_principal_key(PG_FUNCTION_ARGS) } Datum -pg_tde_set_principal_key(PG_FUNCTION_ARGS) +pg_tde_set_principal_key_using_database_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -509,7 +509,7 @@ pg_tde_set_principal_key(PG_FUNCTION_ARGS) } Datum -pg_tde_set_global_principal_key(PG_FUNCTION_ARGS) +pg_tde_set_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -521,7 +521,7 @@ pg_tde_set_global_principal_key(PG_FUNCTION_ARGS) } Datum -pg_tde_set_server_principal_key(PG_FUNCTION_ARGS) +pg_tde_set_server_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -607,9 +607,9 @@ pg_tde_principal_key_info(PG_FUNCTION_ARGS) return pg_tde_get_key_info(fcinfo, MyDatabaseId); } -PG_FUNCTION_INFO_V1(pg_tde_global_principal_key_info); +PG_FUNCTION_INFO_V1(pg_tde_server_principal_key_info); Datum -pg_tde_global_principal_key_info(PG_FUNCTION_ARGS) +pg_tde_server_principal_key_info(PG_FUNCTION_ARGS) { return pg_tde_get_key_info(fcinfo, GLOBAL_DATA_TDE_OID); } @@ -621,7 +621,7 @@ pg_tde_verify_principal_key(PG_FUNCTION_ARGS) } Datum -pg_tde_verify_global_principal_key(PG_FUNCTION_ARGS) +pg_tde_verify_server_principal_key(PG_FUNCTION_ARGS) { return pg_tde_verify_principal_key_internal(GLOBAL_DATA_TDE_OID); } @@ -1030,7 +1030,7 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci } Datum -pg_tde_delete_key_provider(PG_FUNCTION_ARGS) +pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) { return pg_tde_delete_key_provider_internal(fcinfo, 0); } diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 8a6cf445f2cae..5b8b4ac958180 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -45,8 +45,8 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index ed744a578d6a1..be37c72b01d07 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -42,19 +42,19 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -67,7 +67,7 @@ PGTDE::append_to_file($stdout); #rotate key -$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key('rotated-principal-key1');", extra_params => ['-a']); +$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key1');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -79,7 +79,7 @@ $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -87,7 +87,7 @@ #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key('rotated-principal-key2','file-2');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -99,14 +99,14 @@ $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_global_principal_key('rotated-principal-key', 'file-3', false);", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-key', 'file-3', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -118,7 +118,7 @@ $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -128,7 +128,7 @@ # And maybe debug tools to show what's in a file keyring? #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_global_principal_key('rotated-principal-keyX', 'file-2', false);", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX', 'file-2', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -140,7 +140,7 @@ $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -156,19 +156,19 @@ $rt_value = $node->start(); # But now can't be changed to another global provider -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_global_principal_key('rotated-principal-keyX2', 'file-2', false);", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX2', 'file-2', false);", extra_params => ['-a']); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key('rotated-principal-key2','file-2');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 3c2394236e07f..51c9696bfe76c 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -70,8 +70,8 @@ sub resp_hello { ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 478d6bd177f4f..4da05cb4afe71 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -34,8 +34,8 @@ ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index e9aff281e4466..8e00e88b5c537 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -86,8 +86,8 @@ ok($cmdret == 0, "CREATE postgis_tiger_geocoder EXTENSION"); PGTDE::append_to_debug_file($stdout); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 336f4fd67691b..f8a1d0aeac779 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -78,8 +78,8 @@ sub resp_url { ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','vault-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','vault-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 77b7098bcbb47..e169a47bdb5b6 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -49,8 +49,8 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index c540276985b6a..d2c3f34200648 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -37,8 +37,8 @@ $stdout = $node->safe_psql('tbc', q{ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, @@ -57,7 +57,7 @@ PGTDE::append_to_file($stdout); -$cmdret = $node->psql('tbc', "SELECT pg_tde_set_principal_key('new-k', 'file-vault');", extra_params => ['-a']); +$cmdret = $node->psql('tbc', "SELECT pg_tde_set_principal_key_using_database_key_provider('new-k', 'file-vault');", extra_params => ['-a']); ok($cmdret == 0, "ROTATE KEY"); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 1c5a0e7515d7a..6885289080f49 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -33,7 +33,7 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-010');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-010');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 6efe58df9b8bb..83a831de7da2d 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -35,11 +35,11 @@ ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key('test-key', 'file-vault');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -57,9 +57,9 @@ # Change provider and move file PGTDE::append_to_file("-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); move('/tmp/change_key_provider_1.per', '/tmp/change_key_provider_2.per'); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); @@ -83,9 +83,9 @@ PGTDE::append_to_file($stdout); # Change provider and do not move file -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); (undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); @@ -139,9 +139,9 @@ PGTDE::append_to_file($stdout); # Change provider and generate a new principal key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-key', 'file-vault');", extra_params => ['-a']); +$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -156,7 +156,7 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); # Restart the server @@ -178,7 +178,7 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); # Verify diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 86b9646088a09..bfc3b67f4415d 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -1,17 +1,17 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -- server restart -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 -SELECT pg_tde_add_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per'); +SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per'); 2 SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per'); -1 SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per'); -2 -SELECT pg_tde_list_all_key_providers(); +SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}") (2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5),(6); @@ -25,13 +25,13 @@ SELECT * FROM test_enc ORDER BY id ASC; -- server restart SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); 1|file-vault|rotated-principal-key1 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_principal_key('rotated-principal-key2','file-2'); +SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2'); SELECT * FROM test_enc ORDER BY id ASC; 1|5 @@ -39,13 +39,13 @@ SELECT * FROM test_enc ORDER BY id ASC; -- server restart SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); 2|file-2|rotated-principal-key2 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_global_principal_key('rotated-principal-key', 'file-3', false); +SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-key', 'file-3', false); SELECT * FROM test_enc ORDER BY id ASC; 1|5 @@ -53,13 +53,13 @@ SELECT * FROM test_enc ORDER BY id ASC; -- server restart SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -2|file-3|rotated-principal-key -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_global_principal_key('rotated-principal-keyX', 'file-2', false); +SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX', 'file-2', false); SELECT * FROM test_enc ORDER BY id ASC; 1|5 @@ -67,7 +67,7 @@ SELECT * FROM test_enc ORDER BY id ASC; -- server restart SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -1|file-2|rotated-principal-keyX -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; @@ -78,14 +78,14 @@ ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF; psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -1|file-2|rotated-principal-keyX -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_set_principal_key('rotated-principal-key2','file-2'); +SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2'); SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); 2|file-2|rotated-principal-key2 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_global_principal_key_info(); +SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_principal_key interface to set the principal key DROP TABLE test_enc; diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index 5ed45191455e8..704d4de559b8e 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, @@ -17,9 +17,9 @@ SELECT * FROM country_table; 2|UK|Europe 3|USA|North America CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 -SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index f4a4b1922f806..9947d0f0a1c69 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per'); -1 -SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-010'); +SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-010'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index 5c4034d666c42..aa33f0f7ef642 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -1,9 +1,9 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); 1 -SELECT pg_tde_list_all_key_providers(); +SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_1.per""}") -SELECT pg_tde_set_principal_key('test-key', 'file-vault'); +SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault'); CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); @@ -15,9 +15,9 @@ SELECT * FROM test_enc ORDER BY id; 1|5 2|6 -- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per -SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_2.per'); +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per'); 1 -SELECT pg_tde_list_all_key_providers(); +SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_2.per""}") SELECT pg_tde_verify_principal_key(); @@ -34,9 +34,9 @@ t SELECT * FROM test_enc ORDER BY id; 1|5 2|6 -SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); 1 -SELECT pg_tde_list_all_key_providers(); +SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_3.per""}") SELECT pg_tde_verify_principal_key(); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 @@ -64,7 +64,7 @@ SELECT * FROM test_enc ORDER BY id; DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); 1 0 CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; @@ -76,7 +76,7 @@ t SELECT * FROM test_enc ORDER BY id; 1|5 2|6 -SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); 1 -- server restart SELECT pg_tde_verify_principal_key(); @@ -87,7 +87,7 @@ SELECT * FROM test_enc ORDER BY id; psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file -SELECT pg_tde_change_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); 1 SELECT pg_tde_verify_principal_key(); diff --git a/src/bin/pg_waldump/t/003_basic_encrypted.pl b/src/bin/pg_waldump/t/003_basic_encrypted.pl index 1fd4fad129ad9..85ce6d3b3f509 100644 --- a/src/bin/pg_waldump/t/003_basic_encrypted.pl +++ b/src/bin/pg_waldump/t/003_basic_encrypted.pl @@ -28,7 +28,7 @@ $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-wal');"); $node->append_conf( 'postgresql.conf', q{ diff --git a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl b/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl index 9b88ec89a9b1b..586c2454926b2 100644 --- a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl +++ b/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl @@ -42,7 +42,7 @@ sub get_block_lsn $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key('global-db-principal-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-wal');"); $node->append_conf( 'postgresql.conf', q{ From 3381f847838006ec23c309b7c3b1b17792a0ad13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 8 Apr 2025 08:45:51 +0200 Subject: [PATCH 016/796] Clarify some namings in keyring code It's not clear to someone new to the code that "key provider" in these files refers to what's called "key provider type" elsewhere. Rename these to make it easier for the next person. --- .../pg_tde/src/include/keyring/keyring_api.h | 2 +- contrib/pg_tde/src/keyring/keyring_api.c | 42 +++++++++---------- contrib/pg_tde/src/keyring/keyring_file.c | 2 +- contrib/pg_tde/src/keyring/keyring_kmip.c | 2 +- contrib/pg_tde/src/keyring/keyring_vault.c | 2 +- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index 6848c896bda4a..3e3819d3b9120 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -87,7 +87,7 @@ typedef struct KmipKeyring char *kmip_cert_path; } KmipKeyring; -extern void RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type); +extern void RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderType type); extern KeyInfo *KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *returnCode); extern KeyInfo *KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len); diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 15c9e93b2a83b..d2f119b1ba116 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -11,31 +11,31 @@ #include #include -typedef struct KeyProviders +typedef struct RegisteredKeyProviderType { TDEKeyringRoutine *routine; ProviderType type; -} KeyProviders; +} RegisteredKeyProviderType; #ifndef FRONTEND -static List *registeredKeyProviders = NIL; +static List *registeredKeyProviderTypes = NIL; #else -static SimplePtrList registeredKeyProviders = {NULL, NULL}; +static SimplePtrList registeredKeyProviderTypes = {NULL, NULL}; #endif -static KeyProviders *find_key_provider(ProviderType type); +static RegisteredKeyProviderType *find_key_provider_type(ProviderType type); static void KeyringStoreKey(GenericKeyring *keyring, KeyInfo *key); static KeyInfo *KeyringGenerateNewKey(const char *key_name, unsigned key_len); #ifndef FRONTEND -static KeyProviders * -find_key_provider(ProviderType type) +static RegisteredKeyProviderType * +find_key_provider_type(ProviderType type) { ListCell *lc; - foreach(lc, registeredKeyProviders) + foreach(lc, registeredKeyProviderTypes) { - KeyProviders *kp = (KeyProviders *) lfirst(lc); + RegisteredKeyProviderType *kp = (RegisteredKeyProviderType *) lfirst(lc); if (kp->type == type) { @@ -45,14 +45,14 @@ find_key_provider(ProviderType type) return NULL; } #else -static KeyProviders * -find_key_provider(ProviderType type) +static RegisteredKeyProviderType * +find_key_provider_type(ProviderType type) { SimplePtrListCell *lc; - for (lc = registeredKeyProviders.head; lc; lc = lc->next) + for (lc = registeredKeyProviderTypes.head; lc; lc = lc->next) { - KeyProviders *kp = (KeyProviders *) lc->ptr; + RegisteredKeyProviderType *kp = (RegisteredKeyProviderType *) lc->ptr; if (kp->type == type) { @@ -64,9 +64,9 @@ find_key_provider(ProviderType type) #endif /* !FRONTEND */ void -RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type) +RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderType type) { - KeyProviders *kp; + RegisteredKeyProviderType *kp; #ifndef FRONTEND MemoryContext oldcontext; #endif @@ -75,7 +75,7 @@ RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type) Assert(routine->keyring_get_key != NULL); Assert(routine->keyring_store_key != NULL); - kp = find_key_provider(type); + kp = find_key_provider_type(type); if (kp) ereport(ERROR, (errmsg("Key provider of type %d already registered", type))); @@ -83,21 +83,21 @@ RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type) #ifndef FRONTEND oldcontext = MemoryContextSwitchTo(TopMemoryContext); #endif - kp = palloc_object(KeyProviders); + kp = palloc_object(RegisteredKeyProviderType); kp->routine = (TDEKeyringRoutine *) routine; kp->type = type; #ifndef FRONTEND - registeredKeyProviders = lappend(registeredKeyProviders, kp); + registeredKeyProviderTypes = lappend(registeredKeyProviderTypes, kp); MemoryContextSwitchTo(oldcontext); #else - simple_ptr_list_append(®isteredKeyProviders, kp); + simple_ptr_list_append(®isteredKeyProviderTypes, kp); #endif } KeyInfo * KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *returnCode) { - KeyProviders *kp = find_key_provider(keyring->type); + RegisteredKeyProviderType *kp = find_key_provider_type(keyring->type); if (kp == NULL) { @@ -112,7 +112,7 @@ KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes static void KeyringStoreKey(GenericKeyring *keyring, KeyInfo *key) { - KeyProviders *kp = find_key_provider(keyring->type); + RegisteredKeyProviderType *kp = find_key_provider_type(keyring->type); if (kp == NULL) ereport(ERROR, diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 1abb93a650f64..ecaaae73d6a06 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -37,7 +37,7 @@ const TDEKeyringRoutine keyringFileRoutine = { void InstallFileKeyring(void) { - RegisterKeyProvider(&keyringFileRoutine, FILE_KEY_PROVIDER); + RegisterKeyProviderType(&keyringFileRoutine, FILE_KEY_PROVIDER); } static KeyInfo * diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index fc9a8735cf083..d360f003f5055 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -35,7 +35,7 @@ const TDEKeyringRoutine keyringKmipRoutine = { void InstallKmipKeyring(void) { - RegisterKeyProvider(&keyringKmipRoutine, KMIP_KEY_PROVIDER); + RegisterKeyProviderType(&keyringKmipRoutine, KMIP_KEY_PROVIDER); } typedef struct KmipCtx diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index c2d41323d512a..0a53677c5eff1 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -79,7 +79,7 @@ const TDEKeyringRoutine keyringVaultV2Routine = { void InstallVaultV2Keyring(void) { - RegisterKeyProvider(&keyringVaultV2Routine, VAULT_V2_KEY_PROVIDER); + RegisterKeyProviderType(&keyringVaultV2Routine, VAULT_V2_KEY_PROVIDER); } static bool From 6576f2a464fcede44393540dbebb05955bfb5dff Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 21:11:35 +0200 Subject: [PATCH 017/796] PG-1532 Rewrite seqeunces using lower level functions By using SPI and a SQL language function to force the rewrite of sequences we made life unnecessarily hard for ourselves and also intoduced a bug where ALTER TABLE could break if the pg_tde extension's functions were not in the search path. Instead we re-use the code for updating sequence persistence whe table persistance is changed. And we only need to look at the table's presistance since it is always the same as the one of the sequences. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 37 ----------------------- contrib/pg_tde/src/pg_tde_event_capture.c | 37 ++++++++--------------- 2 files changed, 13 insertions(+), 61 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 2f70630b0e385..086947fad2106 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -414,43 +414,6 @@ BEGIN ATOMIC 'certPath' VALUE kmip_cert_path)); END; - -CREATE FUNCTION pg_tde_internal_refresh_sequences(table_oid OID) -RETURNS VOID -AS -$BODY$ -DECLARE - rec RECORD; -BEGIN - FOR rec IN - SELECT s.relname AS sequence_name, - ns.nspname AS sequence_namespace, - se.seqstart AS sequence_start - FROM pg_class AS t - JOIN pg_attribute AS a - ON a.attrelid = t.oid - JOIN pg_depend AS d - ON d.refobjid = t.oid - AND d.refobjsubid = a.attnum - JOIN pg_class AS s - ON s.oid = d.objid - JOIN pg_sequence AS se - ON se.seqrelid = d.objid - JOIN pg_namespace AS ns - ON ns.oid = s.relnamespace - WHERE d.classid = 'pg_catalog.pg_class'::regclass - AND d.refclassid = 'pg_catalog.pg_class'::regclass - AND d.deptype IN ('i', 'a') - AND t.relkind IN ('r', 'P') - AND s.relkind = 'S' - AND t.oid = table_oid - LOOP - EXECUTE format('ALTER SEQUENCE %s.%s START %s', rec.sequence_namespace, rec.sequence_name, rec.sequence_start); - END LOOP; -END -$BODY$ -LANGUAGE plpgsql; - CREATE FUNCTION pg_tde_is_encrypted(relation regclass) RETURNS boolean STRICT diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 8274fa316c459..39a1c637c7776 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -16,6 +16,7 @@ #include "utils/builtins.h" #include "catalog/pg_class.h" #include "commands/defrem.h" +#include "commands/sequence.h" #include "access/table.h" #include "access/relation.h" #include "catalog/pg_event_trigger.h" @@ -28,7 +29,6 @@ #include "miscadmin.h" #include "access/tableam.h" #include "catalog/tde_global_space.h" -#include "executor/spi.h" /* Global variable that gets set at ddl start and cleard out at ddl end*/ static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0},.relation = NULL}; @@ -273,35 +273,24 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) if (IsA(parsetree, AlterTableStmt) && tdeCurrentCreateEvent.alterAccessMethodMode) { /* - * sequences are not updated automatically call a helper function that - * automatically alters all of them, forcing an update on the - * encryption status int ret; + * sequences are not updated automatically so force rewrite by + * updating their persistence to be the same as before. */ - char *sql = "SELECT pg_tde_internal_refresh_sequences($1);"; - Oid argtypes[1]; - SPIPlanPtr plan; - Datum args[1]; - char nulls[1]; - int ret; + List *seqlist = getOwnedSequences(tdeCurrentCreateEvent.baseTableOid); + ListCell *lc; + Relation rel = relation_open(tdeCurrentCreateEvent.baseTableOid, NoLock); + char persistence = rel->rd_rel->relpersistence; - SPI_connect(); + relation_close(rel, NoLock); - argtypes[0] = OIDOID; - plan = SPI_prepare(sql, 1, argtypes); - - args[0] = ObjectIdGetDatum(tdeCurrentCreateEvent.baseTableOid); - nulls[0] = ' '; + foreach(lc, seqlist) + { + Oid seq_relid = lfirst_oid(lc); - ret = SPI_execute_plan(plan, args, nulls, false, 0); + SequenceChangePersistence(seq_relid, persistence); + } tdeCurrentCreateEvent.alterAccessMethodMode = false; - - SPI_finish(); - - if (ret != SPI_OK_SELECT) - { - elog(ERROR, "Failed to update encryption status of sequences."); - } } /* From 0a54910d7a9643e83ba8c494db8321c2789c99c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 9 Apr 2025 17:48:16 +0200 Subject: [PATCH 018/796] Remove typedefs.list This file is no longer used by our scripts and is easy to generate when running pgindent. --- contrib/pg_tde/typedefs.list | 110 ----------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 contrib/pg_tde/typedefs.list diff --git a/contrib/pg_tde/typedefs.list b/contrib/pg_tde/typedefs.list deleted file mode 100644 index 7ff41d54e5fd9..0000000000000 --- a/contrib/pg_tde/typedefs.list +++ /dev/null @@ -1,110 +0,0 @@ -BulkInsertStateData -BulkInsertStateData -BulkInsertStateData -BulkInsertStateData -CurlString -FileKeyring -GenericKeyring -HeapPageFreeze -HeapPageFreeze -HeapScanDescData -HeapScanDescData -HeapScanDescData -HeapScanDescData -HeapTupleFreeze -HeapTupleFreeze -IndexDeleteCounts -IndexDeleteCounts -IndexFetchHeapData -IndexFetchHeapData -InternalKey -JsonKeringSemState -JsonKeyringField -JsonKeyringState -JsonVaultRespField -JsonVaultRespSemState -JsonVaultRespState -Keydata -KeyInfo -KeyProviders -KeyringProvideRecord -KeyringProviderXLRecord -KeyringReturnCode -LVPagePruneState -LVRelState -LVRelState -LVSavedErrInfo -LVSavedErrInfo -LogicalRewriteMappingData -LogicalRewriteMappingData -PendingMapEntryDelete -ProviderScanType -PruneFreezeResult -RelKeyCache -RelKeyCacheRec -RewriteMappingDataEntry -RewriteMappingDataEntry -RewriteMappingFile -RewriteMappingFile -RewriteStateData -RewriteStateData -RewriteStateData -RewriteStateData *RewriteState; -TDEBufferHeapTupleTableSlot -TDEFileHeader -TDEKeyringRoutine -TDELocalState -TDELockTypes -TDEMapEntry -TDEMapFilePath -TDEPrincipalKey -TDEPrincipalKeyInfo -TDEShmemSetupRoutine -TdeCreateEvent -TdeCreateEventType -TdeKeyProviderInfoSharedState -TdePrincipalKeySharedState -TdePrincipalKeylocalState -TdeSharedState -VaultV2Keyring -XLogExtensionInstall -XLogPrincipalKeyRotate -XLogRelKey -itemIdCompactData -itemIdCompactData -xl_multi_insert_tuple -xl_multi_insert_tuple -xl_tdeheap_confirm -xl_tdeheap_confirm -xl_tdeheap_delete -xl_tdeheap_delete -xl_tdeheap_freeze_page -xl_tdeheap_freeze_plan -xl_tdeheap_header -xl_tdeheap_header -xl_tdeheap_inplace -xl_tdeheap_inplace -xl_tdeheap_insert -xl_tdeheap_insert -xl_tdeheap_lock -xl_tdeheap_lock -xl_tdeheap_lock_updated -xl_tdeheap_lock_updated -xl_tdeheap_multi_insert -xl_tdeheap_multi_insert -xl_tdeheap_new_cid -xl_tdeheap_new_cid -xl_tdeheap_prune -xl_tdeheap_prune -xl_tdeheap_rewrite_mapping -xl_tdeheap_rewrite_mapping -xl_tdeheap_truncate -xl_tdeheap_truncate -xl_tdeheap_update -xl_tdeheap_update -xl_tdeheap_vacuum -xl_tdeheap_visible -xl_tdeheap_visible -xlhp_freeze_plan -xlhp_freeze_plans -xlhp_prune_items \ No newline at end of file From ab2e9b1ed233dc241ce2413b40db5cfeb88616a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 9 Apr 2025 12:39:32 +0200 Subject: [PATCH 019/796] Fix typo in KeyringProvideRecord typedef name The R was pulling double duty in the old name. --- contrib/pg_tde/src/catalog/tde_keyring.c | 38 +++++++++---------- .../pg_tde/src/include/catalog/tde_keyring.h | 10 ++--- .../pg_tde/src/pg_tde_change_key_provider.c | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 87012f21d3327..b4b1773e787fb 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -59,11 +59,11 @@ static GenericKeyring *load_keyring_provider_options(ProviderType provider_type, static VaultV2Keyring *load_vaultV2_keyring_provider_options(char *keyring_options); static KmipKeyring *load_kmip_keyring_provider_options(char *keyring_options); static void debug_print_kerying(GenericKeyring *keyring); -static GenericKeyring *load_keyring_provider_from_record(KeyringProvideRecord *provider); +static GenericKeyring *load_keyring_provider_from_record(KeyringProviderRecord *provider); static inline void get_keyring_infofile_path(char *resPath, Oid dbOid); -static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProvideRecord *provider); +static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider); -static uint32 write_key_provider_info(KeyringProvideRecord *provider, +static uint32 write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, off_t position, bool error_if_exists, bool write_xlog); @@ -225,7 +225,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) char *options = text_to_cstring(PG_GETARG_TEXT_PP(2)); int nlen, olen; - KeyringProvideRecord provider; + KeyringProviderRecord provider; /* reports error if not found */ GenericKeyring *keyring = GetKeyProviderByName(provider_name, dbOid); @@ -275,7 +275,7 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) char *options = text_to_cstring(PG_GETARG_TEXT_PP(2)); int nlen, olen; - KeyringProvideRecord provider; + KeyringProviderRecord provider; nlen = strlen(provider_name); if (nlen >= sizeof(provider.provider_name) - 1) @@ -386,7 +386,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid) #endif /* !FRONTEND */ static uint32 -write_key_provider_info(KeyringProvideRecord *provider, Oid database_id, +write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, off_t position, bool error_if_exists, bool write_xlog) { off_t bytes_written = 0; @@ -397,7 +397,7 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id, /* Named max, but global key provider oids are stored as negative numbers! */ int max_provider_id = 0; char kp_info_path[MAXPGPATH] = {0}; - KeyringProvideRecord existing_provider; + KeyringProviderRecord existing_provider; GenericKeyring *record; Assert(provider != NULL); @@ -521,8 +521,8 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id, /* * All good, Just add a new provider */ - bytes_written = pg_pwrite(fd, provider, sizeof(KeyringProvideRecord), curr_pos); - if (bytes_written != sizeof(KeyringProvideRecord)) + bytes_written = pg_pwrite(fd, provider, sizeof(KeyringProviderRecord), curr_pos); + if (bytes_written != sizeof(KeyringProviderRecord)) { close(fd); ereport(ERROR, @@ -548,13 +548,13 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id, * Save the key provider info to the file */ uint32 -save_new_key_provider_info(KeyringProvideRecord *provider, Oid databaseId, bool write_xlog) +save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { return write_key_provider_info(provider, databaseId, -1, true, write_xlog); } uint32 -modify_key_provider_info(KeyringProvideRecord *provider, Oid databaseId, bool write_xlog) +modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { return write_key_provider_info(provider, databaseId, -1, false, write_xlog); } @@ -562,9 +562,9 @@ modify_key_provider_info(KeyringProvideRecord *provider, Oid databaseId, bool wr uint32 delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog) { - KeyringProvideRecord kpr; + KeyringProviderRecord kpr; - memset(&kpr, 0, sizeof(KeyringProvideRecord)); + memset(&kpr, 0, sizeof(KeyringProviderRecord)); kpr.provider_id = provider_id; return modify_key_provider_info(&kpr, databaseId, write_xlog); @@ -616,7 +616,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) off_t curr_pos = 0; int fd; char kp_info_path[MAXPGPATH] = {0}; - KeyringProvideRecord provider; + KeyringProviderRecord provider; #ifndef FRONTEND List *providers_list = NIL; #else @@ -692,7 +692,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) } static GenericKeyring * -load_keyring_provider_from_record(KeyringProvideRecord *provider) +load_keyring_provider_from_record(KeyringProviderRecord *provider) { GenericKeyring *keyring = NULL; @@ -854,26 +854,26 @@ get_keyring_infofile_path(char *resPath, Oid dbOid) * Fetch the next key provider from the file and update the curr_pos */ static bool -fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProvideRecord *provider) +fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider) { off_t bytes_read = 0; Assert(provider != NULL); Assert(fd >= 0); - bytes_read = pg_pread(fd, provider, sizeof(KeyringProvideRecord), *curr_pos); + bytes_read = pg_pread(fd, provider, sizeof(KeyringProviderRecord), *curr_pos); *curr_pos += bytes_read; if (bytes_read == 0) return false; - if (bytes_read != sizeof(KeyringProvideRecord)) + if (bytes_read != sizeof(KeyringProviderRecord)) { close(fd); /* Corrupt file */ ereport(ERROR, (errcode_for_file_access(), errmsg("key provider info file is corrupted: %m"), - errdetail("invalid key provider record size %ld expected %lu", bytes_read, sizeof(KeyringProvideRecord)))); + errdetail("invalid key provider record size %ld expected %lu", bytes_read, sizeof(KeyringProviderRecord)))); } return true; } diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index f2d3521fc2df1..0dae8bc130e93 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -14,28 +14,28 @@ #include "keyring/keyring_api.h" /* This record goes into key provider info file */ -typedef struct KeyringProvideRecord +typedef struct KeyringProviderRecord { int provider_id; char provider_name[MAX_PROVIDER_NAME_LEN]; char options[MAX_KEYRING_OPTION_LEN]; ProviderType provider_type; -} KeyringProvideRecord; +} KeyringProviderRecord; typedef struct KeyringProviderXLRecord { Oid database_id; off_t offset_in_file; - KeyringProvideRecord provider; + KeyringProviderRecord provider; } KeyringProviderXLRecord; extern GenericKeyring *GetKeyProviderByName(const char *provider_name, Oid dbOid); extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); extern ProviderType get_keyring_provider_from_typename(char *provider_type); extern void InitializeKeyProviderInfo(void); -extern uint32 save_new_key_provider_info(KeyringProvideRecord *provider, +extern uint32 save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); -extern uint32 modify_key_provider_info(KeyringProvideRecord *provider, +extern uint32 modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); extern uint32 delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog); diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 0663c381f0a8f..87db2c5d819b9 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -116,7 +116,7 @@ main(int argc, char *argv[]) char *cptr = tdedir; bool provider_found = false; GenericKeyring *keyring = NULL; - KeyringProvideRecord provider; + KeyringProviderRecord provider; Oid db_oid; From e0a34487ebde7a1e555d2495584850a2e0c69d18 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Fri, 7 Mar 2025 00:15:48 +0000 Subject: [PATCH 020/796] Added high level overview to documentation This is just a conversion of the google doc into markdown, with actualizing some of the outdated details in the document. The last section (researc/investigation topics) is left out, as that doesn't make much sense in a public documentation. --- .../pg_tde/documentation/docs/architecture.md | 403 ++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 contrib/pg_tde/documentation/docs/architecture.md diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md new file mode 100644 index 0000000000000..6c675ff3ffd1b --- /dev/null +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -0,0 +1,403 @@ +# PG_TDE High Level Overview + + +## Goals + +Pg_tde aims to be a (1) customizable, (2) complete, (3) data at rest encryption (4) extension for PostgreSQL. + +Customizable (1) means that `pg_tde` aims to support many different use cases: + +* Encrypting every table in every database or only some tables in some databases +* Encryption keys can be stored on various external key storage servers (Hashicorp Vault, KMIP servers, ...) +* Using one key for everything or different keys for different databases +* Storing every key at the same key storage, or using different storages for different databases +* Handling permissions: who can manage database specific or global permissions, who can create encrypted or not encrypted tables + +Complete (2) means that `pg_tde` aims to encrypt everything written to the disk: data at rest (3). +This includes: + +* Table data files +* Indexes +* Temporary tables +* Write ahead log +* PG-994 System tables (not yet implemented) +* PG-993 Temporary files (not yet implemented) + +Extension (4) means that ideally `pg_tde` entirely should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. +This requires changes in the core (making it more extensible), which means currently `pg_tde` only works with the Percona Distribution. + +## Main components + +Pg_tde consist of 3 main components: + +Core server changes focus on making the server more extensible, allowing the main logic of pg_tde to remain separate as an extension. +Additionally, core changes also add encryption-awareness to some command line tools that have to work directly with encrypted tables or encrypted WAL. +Alternatively, these could be implemented as duplicated tools in part of the extension, but keeping everything in the original tool allows a better user experience, as currently `pg_tde` requires the Percona Distribution anyway. + +Our patched postgres can be found at the following location: +https://github.com/percona/postgres/tree/TDE_REL_17_STABLE + +The `pg_tde` extension implements the encryption code itself by hooking into the extension points introduced in the core changes, and the already existing extension points in the PostgreSQL server. +Everything is controllable with GUC variables and SQL statements, similar to other extensions. + +Finally, the keyring API / libraries implement actual key storage logic with different providers. +In the future these could be extracted into separate shared libraries with an open API, allowing third party providers. +Currently the API already exists, but it is internal only and keyring libraries are part of the main library for simplicity. + +## Encryption architecture + +### Two key hierarchy + +`Pg_tde` uses a two level key structure, also found commonly in other database servers: + +The higher level keys are called "principal keys". These are the keys that are stored externally using the keyring APIs, and these are used to encrypt the "internal keys". + +`Pg_tde` uses one principal key per database, every internal key for the given database is encrypted using this principal key. + +Internal keys are used for specific database files: each file that has a different Oid and has a different internal key. +This means that for example a table with 4 indexes will have at least 5 internal keys - one for the table, and one for each index. +If the table has additional files, such as sequence(s) or a toast table, those files also have separate keys. + +### Encryption algorithm + +`Pg_tde` currently uses a hardcoded AES-CBC-128 algorithm for encrypting most database files. +First the internal keys in the datafile are encrypted using the principal key with AES-CBC-128, then the file data itself is again encrypted using AES-CBC-128 with the internal key. + +WAL encryption is different, it uses AES-CTR-128. + +Support for other cipher lengths / algorithms is planned in the future (PG-1194). + +### Marking encrypted objects + +`Pg_tde` makes it possible to encrypt everything, or only some tables in some databases. +To support this without metadata changes, encrypted tables are marked with a marker access method: +`tde_heap` is the same as `heap`, it uses the same functions internally without any changes, but with the different name and Id, `pg_tde` knows that `tde_heap` tables are encrypted, and `heap` tables aren't. + +The initial decision is made using the postgres event trigger mechanism: +if a `CREATE TABLE` or `ALTER TABLE` statement uses `tde_heap`, the newly created data files are marked as encrypted, and then file operations encrypt/decrypt the data. + +Later decisions are made using a slightly modified SMGR API: +when a database file is recreated with a different Id, for example because of a `TRUNCATE` or `VACUUM FULL`, the new file is either encrypted or plain based on the encryption information of the previous file. + +### SMGR API + +`Pg_tde` relies on a slightly modified version of the SMGR API. +These modifications include: + +* Making the API generally extensible, where extensions can inject custom code into the storage manager +* Adding tracking information for files: + when a new file is created for an existing relation, references to the existing file are also passed to the smgr functions + +With these modifications, the `pg_tde` extension can implement an additional layer on top of the normal MD SMGR API, which encrypts a file before writing if the related table is encrypted, and similarly decrypts it after reading when needed. + +### Encrypting other access methods + +Currently `pg_tde` only encrypts heap tables and other files (indexes, toast tables, sequences) related to the heap tables. +Indexes include any kind of index that goes through the SMGR API, not just the built-in indexes in postgres. + +In theory, it is possible to also encrypt any other table access method that goes through the SMGR API, by similarly providing a marker access method to it and extending the event triggers. + +### WAL encryption + +WAL encryption is currently controlled globally, it's either turned on or off with a global GUC variable that requires a server restart. +The variable only controls writes, when it is turned on, WAL writes are encrypted. + +This means WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. +`pg_tde` keeps track of the encryption status of WAL records using internal keys: +every time the encryption status of WAL changes, it writes a new internal key for WAL. +When the encryption is turned on, this internal key contains a valid encryption key. +When the encrpytion is turned off, it only contains a flag signaling that WAL encryption ended. + +With this information, the WAL reader code can decide if a specific WAL records has to be decrypted or not. + +### Principal key rotation + +To comply with common policies, and to handle situations with potentially exposed principal keys, principal keys can be rotated. +Rotation means that `pg_tde` generates a new version of the principal key, and re-encrypts the associated internal keys with the new key. +The old key is kept as is at the same location, as it is potentially still needed to decrypt backups or other databases. + +It is also possible that the location of the keyring changes: +either the service is moved to a new address, or the keyring has to be moved to a different keyring provider type. Both of these situations are supported by `pg_tde` using simple SQL functions. + +In certain cases the SQL API can't be used for these operations: +if the server isn't running, and the old keyring is no longer available, startup can fail if it needs to access the encryption keys. +For these situations, `pg_tde` also provides command line tools to recover the database. + + +### Internal key regeneration + +Internal keys for files (tables, indexes, etc.) are fixed once the file is created, there's no way currently to re-encrypt a file. + +There are workarounds for this, because operations that move the table data to a new file, such as `VACUUM FULL` or an `ALTER TABLE` that rewrites the file will create a new key for the new file, essentially rotating the internal key. +This however means taking an exclusive lock on the table for the duration of the operation, which might not be desirable for huge tables. + +### Internal key storage + +Internal keys, and generally `pg_tde` metadata is kept in a single directory in `$PGDATA/pg_tde`. +In this directory each database has separate files, containing: + +* Encrypted internal keys and internal key mapping to tables +* Information about the key providers + +There's also a special global section marked with the OID 607, which includes the global key providers / global internal keys. + +This is used by the WAL encryption, and can optionally be used by specific databases too, if global provider inheritance is enabled. + +### Key providers (principal key storage) + +Principal keys are stored on external providers. +Currently pg_tde has 3 implementations: + +* A local file storage, intended for development and testing only +* Hashicorp Vault +* KMIP compatible servers + +In all cases, `pg_tde` requires a detailed configuration, including the address of the service and authentication information. +With these details, all operations are done by `pg_tde` based on user operations: + +* When a new principal key is created, it will communicate with the service and upload a fresh key to it +* When a principal key is required for decryption, it will try to get the key from the service + +### Sensitive key provider information + +Some of the keyring provider information (authentication details) is potentially sensitive, and it is not safe to store it together with the database in the `$PGDATA` directory, or even on the same server. + +For this purpose, `pg_tde` provides a mechanism where instead of directly specifying the parameter, users can specify an external service instead, from where it downloads the information. +This way the configuration itself only contains the reference, not the actual authentication key or password. + +Currently only HTTP or external file references are supported, but other possible mechanisms can be added later, such as kubernetes secrets. + +## User interface + +### Setting up pg_tde + +To use `pg_tde`, users are required to: + +* Add pg_tde to the `shared_preload_libraries` in `postgresql.conf`, as this is required for the SMGR extensions +* Execute `CREATE EXTENSION pg_tde` in the databases where they want to use encryption +* Optionally, enable `pg_tde.wal_encrypt` in `postgresql.conf` +* Optionally, disable `pg_tde.inherit_global_providers` in `postgresql.conf` (enabled by default) + +### Adding providers + +Keyring providers can be added to either the GLOBAL or to the database specific scope. + +If `pg_tde.inherit_global_providers` is `ON`, global providers are visible for all databases, and can be used. +If `pg_tde.inherit_global_providers` is `OFF`, global providers are only used for WAL encryption. + +To add a global provider: + +```sql +pg_tde_add_global_key_provider_(‘provider_name', ... details ...) +``` + +To add a database specific provider: + +```sql +pg_tde_add_key_provider_(‘provider_name', ... details ...) +``` + +Note that in these functions do not verify the parameters. +For that, see `pg_tde_verify_principal_key`. + +### Changing providers + +To change a value of a global provider: + +```sql +pg_tde_modify_global_key_provider_(‘provider_name', ... details ...) +``` + +To change a value of a database specific provider: + +```sql +pg_tde_modify_key_provider_(‘provider_name', ... details ...) +``` + +These functions also allow changing the type of a provider. + +The functions however do not migrate any data. +They are expected to be used during infrastructure migration, for example when the address of a server changes. + +Note that in these functions do not verify the parameters. +For that, see `pg_tde_verify_principal_key`. + +### Changing providers from the command line + +To change a provider from a command line, `pg_tde` provides the `pg_tde_modify_key_provider` command line tool. + +This tool work similarly to the above functions, with the following syntax: + +```bash +pg_tde_modify_key_provider ... details ... +``` + +Note that since this tool is expected to be offline, it bypasses all permission checks! + +This is also the reason why it requires a `dbOid` instead of a name, as it has no way to process the catalog and look up names. + +### Deleting providers + +Providers can be deleted by the + +```sql +pg_tde_delete_key_provider(provider_name) +pg_tde_delete_global_key_provider(provider_name) +``` + +functions. + +For database specific providers, the function first checks if the provider is used or not, and the provider is only deleted if it's not used. + +For global providers, the function checks if the provider is used anywhere, WAL or any specific database, and returns an error if it is. + +This somewhat goes against the principle that `pg_tde` shouldn't interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. +Making this check makes more sense than potentially making some databases inaccessible. + +### Listing/querying providers + +`Pg_tde` provides 2 functions to show providers: + +* `pg_tde_list_all_key_providers()` +* `pg_tde_list_all_global_key_providers()` + +These functions only return a list of provider names, without any details about the type/configuration. + +PG-??? There's also two function to query the details of providers: + +```sql +pg_tde_show_key_provider_configuration(‘provider-name') +pg_tde_show_global_key_provider_configuration(‘provider-name') +``` + +These functions display the provider type and configuration details, but won't show the sensitive parameters, such as passwords or authentication keys. + +### Provider permissions + +`Pg_tde` implements access control based on execution rights on the administration functions. + +For provider administration, it provides two pair of functions: + +```sql +pg_tde_(grant/revoke)_local_provider_management_to_role +pg_tde_(grant/revoke)_global_provider_management_to_role +``` + +There's one special behavior: +When `pg_tde.inherit_global_providers` is ON, users with database local permissions can list global providers, but can't use the show function to query configuration details. +When `pg_tde.inherit_global_providers` is OFF, they can't execute the function at all, it will return an error. + +### Creating and rotating keys + +Principal keys can be created or rotated using the following functions: + +```sql +pg_tde_set_principal_key(‘key-name', ‘provider-name', ensure_new_key) +pg_tde_set_global_principal_key(‘key-name', ‘provider-name', ensure_new_key) +pg_tde_set_server_principal_key(‘key-name', ‘provider-name', ensure_new_key) +``` + +`Ensure_new_key` is a boolean parameter defaulting to false. +If it is true, the function might return an error instead of setting the key, if it already exists on the provider. + +### Default principal key + +With `pg_tde.inherit_global_key_providers`, it is also possible to set up a default global principal key, which will be used by any database which has the `pg_tde` extension enabled, but doesn't have a database specific principal key configured using `pg_tde_set_(global_)principal_key`. + +With this feature, it is possible for the entire database server to easily use the same principal key for all databases, completely disabling multi-tenency. + +A default key can be managed with the following functions: + +```sql +pg_tde_set_default_principal_key(‘key-name', ‘provider-name', ensure_new_key) +pg_tde_drop_default_principal_key() -- not yet implemented +``` + +`DROP` is only possible if there's no table currently using the default principal key. + +Changing the default principal key will rotate the encryption of internal keys for all databases using the current default principal key. + +### Removing key (not yet implemented) + +`pg_tde_drop_principal_key` removes the principal key for the current database. +If the current database has any encrypted tables, and there isn't a default principal key configured, it reports an error instead. +If there are encrypted tables, but there's also a global default principal key, internal keys will be encrypted with the default key. + +It isn't possible to remove the WAL (server) principal key. + +### Current key details + +`pg_tde_principal_key_info()` returns the name of the current principal key, and the provider it uses. + +`pg_tde_global_principal_key_info(‘PG_TDE_GLOBAL')` does the same for the server key. + +`pg_tde_verify_principal_key()` checks that the key provider is accessible, that the current principal key can be downloaded from it, and that it is the same as the current key stored in memory - if any of these fail, it reports an appropriate error. + +### Listing all active keys (not yet implemented) + +SUPERusers are able to use the following function: + +`pg_tde_list_active_keys()` + +Which reports all the actively used keys by all databases on the current server. +Similarly to `pg_tde_show_current_principal_key`, it only shows names and associated providers, it doesn't reveal any sensitive information about the providers. + +### Key permissions + +Users with management permissions to a specific database `(pg_tde_(grant/revoke)_provider_management_to_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. + +Also, the `pg_tde_(grant/revoke)_key_management_to_role` function deals with only the specific permission for the above function: +it allows a user to change the key for the database, but not to modify the provider configuration. + +### Creating tables + +To create an encrypted table or modify an existing table to be encrypted, simply use `USING tde_heap` in the `CREATE` / `ALTER TABLE` statement. + +### Changing the pg_tde.inherit_global_keys setting + +It is possible for users to use pg_tde with `inherit_global_keys=ON`, refer to global keys / keyrings in databases, and then change this setting to OFF. + +In this case, existing references to global providers, or the global default principal key will remain working as before, but new references to the global scope can't be made. + +### Using command line tools with encrypted WAL + +TODO + +## Typical setup scenarios + +### Simple "one principal key" encryption + +1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` +2. `CREATE EXTENSION pg_tde;` in `template1` +3. Adding a global key provider +4. Adding a default principal key using the same global provider +5. Changing the WAL encryption to use the default principal key +6. Optionally: setting the `default_table_access_method` to `tde_heap` so that tables are encrypted by default + +Database users don't need permissions to any of the encryption functions: +encryption is managed by the admins, normal users only have to create tables with encryption, which requires no specific permissions. + +### One key storage, but different keys per database + +1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` +2. `CREATE EXTENSION pg_tde;` in `template1` +3. Adding a global key provider +4. Changing the WAL encryption to use the proper global key provider +5. Giving users that are expected to manage database keys permissions for database specific key management, but not database specific key provider management: + specific databases HAVE to use the global key provider + +Note: setting the `default_table_access_method` to `tde_heap` is possible, but instead of `ALTER SYSTEM` only per database using `ALTER DATABASE`, after a principal key is configured for that specific database. + +Alternatively, `ALTER SYSTEM` is possible, but table creation in the database will fail if there's no principal key for the database, that has to be created first. + +### Complete multi tenancy + +1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` (that's not multi tenant currently) +2. `CREATE EXTENSION pg_tde;` in any database +2. Adding a global key provider for WAL +3. Changing the WAL encryption to use the proper global key provider + +No default configuration: +key providers / principal keys are configured as a per database level, permissions are managed per database + +Same note about `default_table_access_method` as above - but in a multi tenant setup, `ALTER SYSTEM` doesn't make much sense. From afb71518600149b68920a0f044553d4400abf468 Mon Sep 17 00:00:00 2001 From: Anastasia Alexadrova Date: Mon, 10 Mar 2025 10:16:02 +0100 Subject: [PATCH 021/796] Improved readability --- .../pg_tde/documentation/docs/architecture.md | 182 +++++++++--------- contrib/pg_tde/documentation/docs/faq.md | 6 +- contrib/pg_tde/documentation/docs/index.md | 2 +- contrib/pg_tde/documentation/mkdocs.yml | 4 +- 4 files changed, 102 insertions(+), 92 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 6c675ff3ffd1b..916488fae55b9 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -1,171 +1,181 @@ -# PG_TDE High Level Overview +# Architecture +`pg_tde` is a **customizable, complete, data at rest encryption extension** for PostgreSQL. -## Goals +Let's break down what it means. -Pg_tde aims to be a (1) customizable, (2) complete, (3) data at rest encryption (4) extension for PostgreSQL. +**Customizable** means that `pg_tde` aims to support many different use cases: -Customizable (1) means that `pg_tde` aims to support many different use cases: - -* Encrypting every table in every database or only some tables in some databases -* Encryption keys can be stored on various external key storage servers (Hashicorp Vault, KMIP servers, ...) +* Encrypting either every table in every database or only some tables in some databases +* Encryption keys can be stored on various external key storage servers including Hashicorp Vault, KMIP servers. * Using one key for everything or different keys for different databases * Storing every key at the same key storage, or using different storages for different databases * Handling permissions: who can manage database specific or global permissions, who can create encrypted or not encrypted tables -Complete (2) means that `pg_tde` aims to encrypt everything written to the disk: data at rest (3). -This includes: +**Complete** means that `pg_tde` aims to encrypt data at rest. + +**Data at rest** means everything written to the disk. This includes the following: * Table data files * Indexes * Temporary tables -* Write ahead log +* Write Ahead Log (WAL) * PG-994 System tables (not yet implemented) * PG-993 Temporary files (not yet implemented) -Extension (4) means that ideally `pg_tde` entirely should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. -This requires changes in the core (making it more extensible), which means currently `pg_tde` only works with the Percona Distribution. +**Extension** means that `pg_tde` should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. This requires changes in the PostgreSQL core to make it more extensible. Therefore, `pg_tde` currently works only with the [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/index.html) - a binary replacement of community PostgreSQL and included in Percona Distribution for PostgreSQL. ## Main components -Pg_tde consist of 3 main components: +The main components of `pg_tde` are the following: -Core server changes focus on making the server more extensible, allowing the main logic of pg_tde to remain separate as an extension. -Additionally, core changes also add encryption-awareness to some command line tools that have to work directly with encrypted tables or encrypted WAL. -Alternatively, these could be implemented as duplicated tools in part of the extension, but keeping everything in the original tool allows a better user experience, as currently `pg_tde` requires the Percona Distribution anyway. +* **Core server changes** focus on making the server more extensible, allowing the main logic of `pg_tde` to remain separate, as an extension. Core changes also add encryption-awareness to some command line tools that have to work directly with encrypted tables or encrypted WAL files. -Our patched postgres can be found at the following location: -https://github.com/percona/postgres/tree/TDE_REL_17_STABLE + [Percona Server for PostgreSQL location](https://github.com/percona/postgres/tree/{{tdebranch}}) -The `pg_tde` extension implements the encryption code itself by hooking into the extension points introduced in the core changes, and the already existing extension points in the PostgreSQL server. -Everything is controllable with GUC variables and SQL statements, similar to other extensions. +* The **`pg_tde` extension itself** implements the encryption code by hooking into the extension points introduced in the core changes, and the already existing extension points in the PostgreSQL server. + + Everything is controllable with GUC variables and SQL statements, similar to other extensions. -Finally, the keyring API / libraries implement actual key storage logic with different providers. -In the future these could be extracted into separate shared libraries with an open API, allowing third party providers. -Currently the API already exists, but it is internal only and keyring libraries are part of the main library for simplicity. +* The **keyring API / libraries** implement the key storage logic with different key providers. The API is internal only, the keyring libraries are part of the main library for simplicity. +In the future these could be extracted into separate shared libraries with an open API, allowing the use of third-party providers. ## Encryption architecture -### Two key hierarchy +### Two-key hierarchy -`Pg_tde` uses a two level key structure, also found commonly in other database servers: +`pg_tde` uses two keys for encryption: -The higher level keys are called "principal keys". These are the keys that are stored externally using the keyring APIs, and these are used to encrypt the "internal keys". +* Internal keys to encrypt the data. They are stored internally near the data that they encrypt. +* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in the Key Management System (KMS) using the key provider API. -`Pg_tde` uses one principal key per database, every internal key for the given database is encrypted using this principal key. +`pg_tde` uses one principal key per database. Every internal key for the given database is encrypted using this principal key. + +Internal keys are used for specific database files: each file with a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) and has a different internal key. -Internal keys are used for specific database files: each file that has a different Oid and has a different internal key. This means that for example a table with 4 indexes will have at least 5 internal keys - one for the table, and one for each index. -If the table has additional files, such as sequence(s) or a toast table, those files also have separate keys. + +If a table has additional files, such as sequence(s) or a TOAST table, those files will also have separate keys. ### Encryption algorithm -`Pg_tde` currently uses a hardcoded AES-CBC-128 algorithm for encrypting most database files. -First the internal keys in the datafile are encrypted using the principal key with AES-CBC-128, then the file data itself is again encrypted using AES-CBC-128 with the internal key. +`pg_tde` currently uses the following encryption algorithms: -WAL encryption is different, it uses AES-CTR-128. +* `AES-CBC-128` algorithm for encrypting most database files. -Support for other cipher lengths / algorithms is planned in the future (PG-1194). + Here's how it works: -### Marking encrypted objects + First the internal keys for data files are encrypted using the principal key with the `AES-CBC-128` algorithm. Then the data file itself is again encrypted using `AES-CBC-128` with the internal key. -`Pg_tde` makes it possible to encrypt everything, or only some tables in some databases. -To support this without metadata changes, encrypted tables are marked with a marker access method: -`tde_heap` is the same as `heap`, it uses the same functions internally without any changes, but with the different name and Id, `pg_tde` knows that `tde_heap` tables are encrypted, and `heap` tables aren't. +* `AES-CTR-128` algorithm for WAL encryption. -The initial decision is made using the postgres event trigger mechanism: -if a `CREATE TABLE` or `ALTER TABLE` statement uses `tde_heap`, the newly created data files are marked as encrypted, and then file operations encrypt/decrypt the data. + The workflow is similar: WAL pages are first encrypted with the internal key. Then the internal key is encrypted with the global principal key. -Later decisions are made using a slightly modified SMGR API: -when a database file is recreated with a different Id, for example because of a `TRUNCATE` or `VACUUM FULL`, the new file is either encrypted or plain based on the encryption information of the previous file. -### SMGR API +The support for other cipher lengths / algorithms is planned in the future. -`Pg_tde` relies on a slightly modified version of the SMGR API. -These modifications include: +### Encryption workflow -* Making the API generally extensible, where extensions can inject custom code into the storage manager -* Adding tracking information for files: - when a new file is created for an existing relation, references to the existing file are also passed to the smgr functions +`pg_tde` makes it possible to encrypt everything or only some tables in some databases. -With these modifications, the `pg_tde` extension can implement an additional layer on top of the normal MD SMGR API, which encrypts a file before writing if the related table is encrypted, and similarly decrypts it after reading when needed. +To support this without metadata changes, encrypted tables are labeled with a `tde_heap` access method marker. -### Encrypting other access methods +The `tde_heap` access method is the same as the `heap` one. It uses the same functions internally without any changes, but with the different name and ID. In such a way `pg_tde` knows that `tde_heap` tables are encrypted and `heap` tables are not. -Currently `pg_tde` only encrypts heap tables and other files (indexes, toast tables, sequences) related to the heap tables. -Indexes include any kind of index that goes through the SMGR API, not just the built-in indexes in postgres. +The initial decision what to encrypt is made using the `postgres` event trigger mechanism: if a `CREATE TABLE` or `ALTER TABLE` statement uses the `tde_heap` clause, the newly created data files are marked as encrypted. Then file operations encrypt or decrypt the data. -In theory, it is possible to also encrypt any other table access method that goes through the SMGR API, by similarly providing a marker access method to it and extending the event triggers. +Later decisions are made using a slightly modified Storage Manager (SMGR) API: +when a database file is re-created with a different ID as a result of a `TRUNCATE` or a `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. ### WAL encryption -WAL encryption is currently controlled globally, it's either turned on or off with a global GUC variable that requires a server restart. -The variable only controls writes, when it is turned on, WAL writes are encrypted. +WAL encryption is controlled globally via a global GUC variable that requires a server restart. -This means WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. -`pg_tde` keeps track of the encryption status of WAL records using internal keys: -every time the encryption status of WAL changes, it writes a new internal key for WAL. -When the encryption is turned on, this internal key contains a valid encryption key. -When the encrpytion is turned off, it only contains a flag signaling that WAL encryption ended. +The variable only controls writes so that only WAL writes are encrypted when WAL encryption is enabled. This means that WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. -With this information, the WAL reader code can decide if a specific WAL records has to be decrypted or not. +`pg_tde` keeps track of the encryption status of WAL records using internal keys. Every time the encryption status of WAL changes, it writes a new internal key for WAL. When the encryption is enabled, this internal key contains a valid encryption key. When the encrpytion is disabled, it only contains a flag signaling that WAL encryption ended. -### Principal key rotation +With this information, the WAL reader code can decide if a specific WAL record has to be decrypted or not. + +### Encrypting other access methods + +Currently `pg_tde` only encrypts `heap` tables and other files such as indexes, TOAST tables, sequences that are related to the `heap` tables. -To comply with common policies, and to handle situations with potentially exposed principal keys, principal keys can be rotated. -Rotation means that `pg_tde` generates a new version of the principal key, and re-encrypts the associated internal keys with the new key. -The old key is kept as is at the same location, as it is potentially still needed to decrypt backups or other databases. +Indexes include any kind of index that goes through the SMGR API, not just the built-in indexes in PostgreSQL. -It is also possible that the location of the keyring changes: -either the service is moved to a new address, or the keyring has to be moved to a different keyring provider type. Both of these situations are supported by `pg_tde` using simple SQL functions. +In theory, it is also possible to encrypt any other table access method that goes through the SMGR API by similarly providing a marker access method to it and extending the event triggers. -In certain cases the SQL API can't be used for these operations: -if the server isn't running, and the old keyring is no longer available, startup can fail if it needs to access the encryption keys. -For these situations, `pg_tde` also provides command line tools to recover the database. +### Storage Manager (SMGR) API +`pg_tde` relies on a slightly modified version of the SMGR API. +These modifications include: + +* Making the API generally extensible, where extensions can inject custom code into the storage manager +* Adding tracking information for files. When a new file is created for an existing relation, references to the existing file are also passed to the SMGR functions + +With these modifications, the `pg_tde` extension can implement an additional layer on top of the normal Magnetic Disk SMGR API: if the related table is encrypted, `pg_tde` encrypts a file before writing it to the disk and, similarly, decrypts it after reading when needed. + +## Key and key providers management + +### Principal key rotation + +You can rotate principal keys to comply with common policies and to handle situations with potentially exposed principal keys. + +Rotation means that `pg_tde` generates a new version of the principal key, and re-encrypts the associated internal keys with the new key. The old key is kept as is at the same location, because it may still be needed to decrypt backups or other databases. ### Internal key regeneration -Internal keys for files (tables, indexes, etc.) are fixed once the file is created, there's no way currently to re-encrypt a file. +Internal keys for tables, indexes and other data files are fixed once a file is created. There's no way to re-encrypt a file. + +WAL internal keys are also fixed to the respective ranges. And there is no easy way to generate a new internal WAL key without turning off and on WAL encryption. There are workarounds for this, because operations that move the table data to a new file, such as `VACUUM FULL` or an `ALTER TABLE` that rewrites the file will create a new key for the new file, essentially rotating the internal key. This however means taking an exclusive lock on the table for the duration of the operation, which might not be desirable for huge tables. ### Internal key storage -Internal keys, and generally `pg_tde` metadata is kept in a single directory in `$PGDATA/pg_tde`. -In this directory each database has separate files, containing: +Internal keys and `pg_tde` metadata in general are kept in a single `$PGDATA/pg_tde` directory. This directory stores separate files for each database, such as: * Encrypted internal keys and internal key mapping to tables * Information about the key providers -There's also a special global section marked with the OID 607, which includes the global key providers / global internal keys. +Also, the `$PGDATA/pg_tde` directory has a special global section marked with the OID `607`, which includes the global key providers and global internal keys. -This is used by the WAL encryption, and can optionally be used by specific databases too, if global provider inheritance is enabled. +The global section is used for WAL encryption. Specific databases can use the global section too, for scenarios where users configure individual principal keys for databases but use the same global key provider. For this purpose, you must enable the global provider inheritance. ### Key providers (principal key storage) -Principal keys are stored on external providers. -Currently pg_tde has 3 implementations: +Principal keys are stored externally in Key Management Stores (KMS). In `pg_tde`a KMS is defined as an external key provider. -* A local file storage, intended for development and testing only -* Hashicorp Vault +The following key providers are supported: + +* [HashiCorp Vault](https://developer.hashicorp.com/vault/docs/what-is-vault) KV2 secrets engine +* [OpenBao](https://openbao.org/) implementation of Vault * KMIP compatible servers +* A local file storage. This storage is intended only for development and testing and is not recommended for production use. + +For each key provider, `pg_tde` requires a detailed configuration, including the address of the service and the authentication information. + +With these details, `pg_tde` does the following based on user operations: + +* Communicates with the service and uploads a new principal key to it after this key is created +* Retrieves the principal key from the service when it is required for decryption + +### Key provider management + +Key provider configuration or location may change. For example, a service is moved to a new address or the principal key must be moved to a different key provider type. `pg_tde` supports both these scenarios enabling you to manage principal keys using simple [SQL functions](functions.md#key-provider-management). -In all cases, `pg_tde` requires a detailed configuration, including the address of the service and authentication information. -With these details, all operations are done by `pg_tde` based on user operations: +In certain cases you can't use SQL functions to manage key providers. For example, if the key provider changed while the server wasn't running and is therefore unaware of these changes. The startup can fail if it needs to access the encryption keys. -* When a new principal key is created, it will communicate with the service and upload a fresh key to it -* When a principal key is required for decryption, it will try to get the key from the service +For such situations, `pg_tde` also provides [command line tools](command-line-tools.md) to recover the database. ### Sensitive key provider information -Some of the keyring provider information (authentication details) is potentially sensitive, and it is not safe to store it together with the database in the `$PGDATA` directory, or even on the same server. +Key provider information authentication details is a sensitive information. It is not safe to store it together with the database in the `$PGDATA` directory, or even on the same server. -For this purpose, `pg_tde` provides a mechanism where instead of directly specifying the parameter, users can specify an external service instead, from where it downloads the information. -This way the configuration itself only contains the reference, not the actual authentication key or password. +To safeguard key providers' sensitive information, `pg_tde` supports references to external services. Instead of specifying authentication details directly, users specify the reference to the external service where it is stored. `pg_tde` then downloads the provider's authentication details when needed. -Currently only HTTP or external file references are supported, but other possible mechanisms can be added later, such as kubernetes secrets. +The supported external services are HTTP and external file references. Upon request, other services such as Kubernetes secrets can be added. ## User interface diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index f7b951ba96218..a4842bdd306cd 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -79,9 +79,9 @@ Let's break the encryption into two parts: ### Encryption of data files -First, data files are encrypted with internal keys. Each file that has a different OID, has an internal key. For example, a table with 4 indexes will have 5 internal keys - one for the table and one for each index. +First, data files are encrypted with internal keys. Each file that has a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) has an internal key. For example, a table with 4 indexes will have 5 internal keys - one for the table and one for each index. -The initial decision on what file to encrypt is based on the table access method in PostgreSQL. When you run a `CREATE` or `ALTER TABLE` statement with the `USING tde_heap` clause, the newly created data files are marked as encrypted, and then file operations encrypt/decrypt the data. Later, if an initial file is re-created as a result of a `TRUNCATE` or `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. +The initial decision on what file to encrypt is based on the table access method in PostgreSQL. When you run a `CREATE` or `ALTER TABLE` statement with the `USING tde_heap` clause, the newly created data files are marked as encrypted, and then file operations encrypt or decrypt the data. Later, if an initial file is re-created as a result of a `TRUNCATE` or `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. The principal key is used to encrypt the internal keys. The principal key is stored in the key management store. When you query the table, the principal key is retrieved from the key store to decrypt the table. Then the internal key for that table is used to decrypt the data. @@ -91,7 +91,7 @@ WAL encryption is done globally for the entire database cluster. All modificatio When you turn on WAL encryption, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. -The same 2-tier approach is used with WAL as with the table data: WAL pages are first encrypted with the internal key. Then the internal key is encrypted with the global principal key. +The same 2-key approach is used with WAL as with the table data: WAL pages are first encrypted with the internal key. Then the internal key is encrypted with the global principal key. You can turn WAL encryption on and off so WAL can contain both encrypted and unencrypted data. The WAL encryption GUC variable influences only writes. diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 2a4095213bc1b..4bf8a3b9e0e7a 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -2,7 +2,7 @@ `pg_tde` is the open source PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. This ensures that the data stored on disk is encrypted, and no one can read it without the proper encryption keys, even if they gain access to the physical storage media. -Learn more [what is Transparent Data Encryption](tde.md#how-does-it-work) and [why you need it](tde.md#why-do-you-need-tde). +Learn more [what Transparent Data Encryption is](tde.md#how-does-it-work) and [why you need it](tde.md#why-do-you-need-tde). !!! important diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 3e03345be1daf..91ac69b6bfbe7 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -52,6 +52,7 @@ theme: - content.tabs.link - navigation.top - navigation.tracking + - navigation.indexes @@ -92,8 +93,6 @@ markdown_extensions: linenums: false - pymdownx.snippets: base_path: ["snippets"] -# auto_append: -# - services-banner.md - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg @@ -171,6 +170,7 @@ nav: - Concepts: - "What is TDE": tde.md - table-access-method.md + - architecture.md - How to: - Set up multi-tenancy: multi-tenant-setup.md - Use reference to external parameters: external-parameters.md From 9356a409807164bab9baa0726eca70f608af9ed8 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Mon, 17 Mar 2025 21:50:43 +0000 Subject: [PATCH 022/796] Updated review comments --- contrib/pg_tde/documentation/docs/architecture.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 916488fae55b9..573b3023c006b 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -20,8 +20,8 @@ Let's break down what it means. * Indexes * Temporary tables * Write Ahead Log (WAL) -* PG-994 System tables (not yet implemented) -* PG-993 Temporary files (not yet implemented) +* System tables (not yet implemented) +* Temporary files (not yet implemented) **Extension** means that `pg_tde` should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. This requires changes in the PostgreSQL core to make it more extensible. Therefore, `pg_tde` currently works only with the [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/index.html) - a binary replacement of community PostgreSQL and included in Percona Distribution for PostgreSQL. @@ -53,7 +53,7 @@ In the future these could be extracted into separate shared libraries with an op Internal keys are used for specific database files: each file with a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) and has a different internal key. -This means that for example a table with 4 indexes will have at least 5 internal keys - one for the table, and one for each index. +This means that, for example, a table with 4 indexes will have at least 5 internal keys - one for the table, and one for each index. If a table has additional files, such as sequence(s) or a TOAST table, those files will also have separate keys. @@ -131,6 +131,7 @@ WAL internal keys are also fixed to the respective ranges. And there is no easy There are workarounds for this, because operations that move the table data to a new file, such as `VACUUM FULL` or an `ALTER TABLE` that rewrites the file will create a new key for the new file, essentially rotating the internal key. This however means taking an exclusive lock on the table for the duration of the operation, which might not be desirable for huge tables. +WAL internal key are also fixed to the respective ranges. And there is no easy way to generate a new WAL key without turning off and on WAL encryption. ### Internal key storage @@ -273,7 +274,7 @@ Making this check makes more sense than potentially making some databases inacce These functions only return a list of provider names, without any details about the type/configuration. -PG-??? There's also two function to query the details of providers: +There's also two function to query the details of providers: ```sql pg_tde_show_key_provider_configuration(‘provider-name') @@ -307,7 +308,7 @@ pg_tde_set_global_principal_key(‘key-name', ‘provider-name', ensure_new_key) pg_tde_set_server_principal_key(‘key-name', ‘provider-name', ensure_new_key) ``` -`Ensure_new_key` is a boolean parameter defaulting to false. +`ensure_new_key` is a boolean parameter defaulting to false. If it is true, the function might return an error instead of setting the key, if it already exists on the provider. ### Default principal key @@ -359,7 +360,7 @@ Users with management permissions to a specific database `(pg_tde_(grant/revoke) Also, the `pg_tde_(grant/revoke)_key_management_to_role` function deals with only the specific permission for the above function: it allows a user to change the key for the database, but not to modify the provider configuration. -### Creating tables +### Creating encrypted tables To create an encrypted table or modify an existing table to be encrypted, simply use `USING tde_heap` in the `CREATE` / `ALTER TABLE` statement. From b83ff3d6847b1e765b69d21ade9a1335c7ae41b5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 10 Apr 2025 17:21:43 +0200 Subject: [PATCH 023/796] PG-1468 Fix issue with intolerably slow recovery When we did a recovery, even if tde_heap was not used, 98% of the time was spent in pg_tde_get_key_from_file() due to our SMGR missing the shortcicruit from mdcreate() which skips running if the fork already is open. The issue was made worse by us not caching negative SMGR key lookups but that is the subject of another commit. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 1a3b9b3a3da83..fd7d20e709262 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -195,7 +195,10 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool { TDESMgrRelation tdereln = (TDESMgrRelation) reln; InternalKey *key; - TdeCreateEvent *event = GetCurrentTdeCreateEvent(); + + /* Copied from mdcreate() in md.c */ + if (isRedo && tdereln->md_num_open_segs[forknum] > 0) + return; /* * Make sure that even if a statement failed, and an event trigger end @@ -215,6 +218,8 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool if (forknum == MAIN_FORKNUM || forknum == INIT_FORKNUM) { + TdeCreateEvent *event = GetCurrentTdeCreateEvent(); + /* * Only create keys when creating the main/init fork. Other forks can * be created later, even during tde creation events. We definitely do From 959a6b65c132a5fe428274771b34e215031c3762 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 10 Apr 2025 19:56:54 +0200 Subject: [PATCH 024/796] PG-1457 Rename principal key on user API level to just a key (#154) PG-1457 Replace `principal key` with just a `key` on user API level, as it's the only key that user can directly interact with. --- ci_scripts/backup/pg_basebackup_test.sh | 2 +- ci_scripts/tde_setup.sql | 2 +- ci_scripts/tde_setup_global.sql | 2 +- contrib/pg_tde/README.md | 6 +- .../pg_tde/documentation/docs/functions.md | 34 +++++----- .../documentation/docs/multi-tenant-setup.md | 6 +- contrib/pg_tde/documentation/docs/setup.md | 6 +- contrib/pg_tde/documentation/docs/test.md | 10 +-- .../documentation/docs/wal-encryption.md | 2 +- contrib/pg_tde/expected/access_control.out | 22 +++---- contrib/pg_tde/expected/alter_index.out | 6 +- contrib/pg_tde/expected/cache_alloc.out | 6 +- .../pg_tde/expected/change_access_method.out | 6 +- .../pg_tde/expected/default_principal_key.out | 66 +++++++++---------- .../expected/default_principal_key_1.out | 66 +++++++++---------- .../pg_tde/expected/delete_key_provider.out | 4 +- .../pg_tde/expected/insert_update_delete.out | 6 +- contrib/pg_tde/expected/key_provider.out | 28 ++++---- contrib/pg_tde/expected/key_provider_1.out | 28 ++++---- .../expected/keyprovider_dependency.out | 6 +- contrib/pg_tde/expected/kmip_test.out | 6 +- contrib/pg_tde/expected/no_provider_error.out | 2 +- .../pg_tde/expected/pg_tde_is_encrypted.out | 20 +++--- contrib/pg_tde/expected/recreate_storage.out | 6 +- contrib/pg_tde/expected/subtransaction.out | 6 +- contrib/pg_tde/expected/tablespace.out | 6 +- contrib/pg_tde/expected/toast_decrypt.out | 6 +- contrib/pg_tde/expected/toast_decrypt_1.out | 6 +- contrib/pg_tde/expected/vault_v2_test.out | 10 +-- contrib/pg_tde/pg_tde--1.0-rc.sql | 52 +++++++-------- contrib/pg_tde/sql/access_control.sql | 8 +-- contrib/pg_tde/sql/alter_index.sql | 2 +- contrib/pg_tde/sql/cache_alloc.sql | 2 +- contrib/pg_tde/sql/change_access_method.sql | 2 +- contrib/pg_tde/sql/default_principal_key.sql | 30 ++++----- contrib/pg_tde/sql/delete_key_provider.sql | 2 +- contrib/pg_tde/sql/insert_update_delete.sql | 2 +- contrib/pg_tde/sql/key_provider.sql | 12 ++-- contrib/pg_tde/sql/keyprovider_dependency.sql | 2 +- contrib/pg_tde/sql/kmip_test.sql | 2 +- contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 8 +-- contrib/pg_tde/sql/recreate_storage.sql | 2 +- contrib/pg_tde/sql/subtransaction.sql | 2 +- contrib/pg_tde/sql/tablespace.sql | 2 +- contrib/pg_tde/sql/toast_decrypt.sql | 2 +- contrib/pg_tde/sql/vault_v2_test.sql | 4 +- contrib/pg_tde/src/access/pg_tde_tdemap.c | 6 +- .../pg_tde/src/catalog/tde_principal_key.c | 48 +++++++------- contrib/pg_tde/src/pg_tde_event_capture.c | 3 +- contrib/pg_tde/t/001_basic.pl | 2 +- contrib/pg_tde/t/002_rotate_key.pl | 38 +++++------ contrib/pg_tde/t/003_remote_config.pl | 2 +- contrib/pg_tde/t/004_file_config.pl | 2 +- contrib/pg_tde/t/005_multiple_extensions.pl | 2 +- contrib/pg_tde/t/006_remote_vault_config.pl | 2 +- contrib/pg_tde/t/007_tde_heap.pl | 2 +- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 4 +- contrib/pg_tde/t/009_wal_encrypt.pl | 2 +- contrib/pg_tde/t/010_change_key_provider.pl | 22 +++---- contrib/pg_tde/t/expected/002_rotate_key.out | 58 ++++++++-------- .../t/expected/008_key_rotate_tablespace.out | 4 +- contrib/pg_tde/t/expected/009_wal_encrypt.out | 2 +- .../t/expected/010_change_key_provider.out | 20 +++--- src/bin/pg_waldump/t/003_basic_encrypted.pl | 2 +- .../t/004_save_fullpage_encrypted.pl | 2 +- 65 files changed, 366 insertions(+), 375 deletions(-) diff --git a/ci_scripts/backup/pg_basebackup_test.sh b/ci_scripts/backup/pg_basebackup_test.sh index a14a9836ec7c9..a9b4f4199fe95 100755 --- a/ci_scripts/backup/pg_basebackup_test.sh +++ b/ci_scripts/backup/pg_basebackup_test.sh @@ -105,7 +105,7 @@ setup_tde_heap(){ sudo -u "$PG_USER" psql -p $PG_PORT -c "CREATE DATABASE $DB_NAME;" sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "CREATE EXTENSION IF NOT EXISTS pg_tde;" sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_add_database_key_provider_file('file-vault','$KEYLOCATION');" - sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-master-key','file-vault');" + sudo -u "$PG_USER" psql -d "$DB_NAME" -p "$PG_PORT" -c "SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault');" sudo -u "$PG_USER" psql -p $PG_PORT -c "ALTER DATABASE $DB_NAME SET default_table_access_method='tde_heap';" sudo -u "$PG_USER" psql -p $PG_PORT -c "SELECT pg_reload_conf();" } diff --git a/ci_scripts/tde_setup.sql b/ci_scripts/tde_setup.sql index 21584fd1d6960..fd084c96462bd 100644 --- a/ci_scripts/tde_setup.sql +++ b/ci_scripts/tde_setup.sql @@ -1,4 +1,4 @@ CREATE SCHEMA IF NOT EXISTS tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; SELECT tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'reg_file-vault'); +SELECT tde.pg_tde_set_key_using_database_key_provider('test-db-key', 'reg_file-vault'); diff --git a/ci_scripts/tde_setup_global.sql b/ci_scripts/tde_setup_global.sql index 4289b29c5baeb..364b34c5f603a 100644 --- a/ci_scripts/tde_setup_global.sql +++ b/ci_scripts/tde_setup_global.sql @@ -2,7 +2,7 @@ CREATE SCHEMA tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; SELECT tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'reg_file-global'); +SELECT tde.pg_tde_set_server_key_using_global_key_provider('server-key', 'reg_file-global'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; ALTER SYSTEM SET default_table_access_method = 'tde_heap'; ALTER SYSTEM SET search_path = "$user",public,tde; diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index bbc51a5fcf5ee..aae4bc3fe2f20 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -126,11 +126,11 @@ _See [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-bu **Note: The `File` provided is intended for development and stores the keys unencrypted in the specified data file.** - 5. Set the principal key for the database using the `pg_tde_set_principal_key` function. + 5. Set the principal key for the database using the `pg_tde_set_key` function. ```sql - -- pg_tde_set_principal_key_using_database_key_provider(principal_key_name, provider_name); - SELECT pg_tde_set_principal_key_using_database_key_provider('my-principal-key','file'); + -- pg_tde_set_key_using_database_key_provider(key_name, provider_name); + SELECT pg_tde_set_key_using_database_key_provider('my-key','file'); ``` 6. Specify `tde_heap` access method during table creation diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 64c9aa62960df..80f858f4f4f9a 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -61,7 +61,7 @@ You can add a new key provider using the provided functions, which are implement There are two functions to add a key provider: one function adds it for the current database and another one - for the global scope. -* `pg_tde_add_key_provider_('provider-name', )` +* `pg_tde_add_database_key_provider_('provider-name', )` * `pg_tde_add_global_key_provider_('provider-name', )` When you add a new provider, the provider name must be unqiue in the scope. But a local database provider and a global provider can have the same name. @@ -201,12 +201,12 @@ Use these functions to create a new principal key for a specific scope such as a Princial keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. -### pg_tde_set_principal_key_using_database_key_provider +### pg_tde_set_key_using_database_key_provider Creates or rotates the principal key for the current database using the specified database key provider and key name. ``` -SELECT pg_tde_set_principal_key_using_database_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -215,12 +215,12 @@ SELECT pg_tde_set_principal_key_using_database_key_provider('name-of-the-princip If the provider already stores a key by that name, the function returns an error. * If set to `false`, an existing principal key may be reused. -### pg_tde_set_principal_key_using_global_key_provider +### pg_tde_set_key_using_global_key_provider Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. ``` -SELECT pg_tde_set_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -229,12 +229,12 @@ SELECT pg_tde_set_principal_key_using_global_key_provider('name-of-the-principal If the provider already stores a key by that name, the function returns an error. * If set to `false`, an existing principal key may be reused. -### pg_tde_set_server_principal_key_using_global_key_provider +### pg_tde_set_server_key_using_global_key_provider Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. ``` -SELECT pg_tde_set_server_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_server_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -244,14 +244,14 @@ The `ensure_new_key` parameter instructs the function how to handle a principal * If set to `false`, an existing principal key may be reused. -### pg_tde_set_default_principal_key_using_global_key_provider +### pg_tde_set_default_key_using_global_key_provider Creates or rotates the default principal key for the server using the specified global key provider. The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. ``` -SELECT pg_tde_set_default_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); +SELECT pg_tde_set_default_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: @@ -282,23 +282,23 @@ SELECT pg_tde_is_encrypted('schema.table_name'); This can additionally be used to verify that indexes and sequences are encrypted. -### pg_tde_principal_key_info +### pg_tde_key_info Displays information about the principal key for the current database, if it exists. ``` -SELECT pg_tde_principal_key_info() +SELECT pg_tde_key_info() ``` -### pg_tde_server_principal_key_info +### pg_tde_server_key_info Displays information about the principal key for the server scope, if exists. ``` -SELECT pg_tde_server_principal_key_info() +SELECT pg_tde_server_key_info() ``` -### pg_tde_verify_principal_key +### pg_tde_verify_key This function checks that the current database has a properly functional encryption setup, which means: @@ -311,10 +311,10 @@ This function checks that the current database has a properly functional encrypt If any of the above checks fail, the function reports an error. ``` -SELECT pg_tde_verify_principal_key() +SELECT pg_tde_verify_key() ``` -### pg_tde_verify_server_principal_key +### pg_tde_verify_server_key This function checks that the server scope has a properly functional encryption setup, which means: @@ -327,5 +327,5 @@ This function checks that the server scope has a properly functional encryption If any of the above checks fail, the function reports an error. ``` -SELECT pg_tde_verify_server_principal_key() +SELECT pg_tde_verify_server_key() ``` diff --git a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md index 8df576dd0d632..2cb9d7698000f 100644 --- a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md @@ -117,19 +117,19 @@ You must do these steps for every database where you have created the extension. 2. Add a principal key ```sql - SELECT pg_tde_set_principal_key_using_database_key_provider('name-of-the-principal-key', 'provider-name','ensure_new_key'); + SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key', 'provider-name','ensure_new_key'); ``` where: - * `name-of-the-principal-key` is the name of the principal key. You will use this name to identify the key. + * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. * `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` The key is auto-generated. diff --git a/contrib/pg_tde/documentation/docs/setup.md b/contrib/pg_tde/documentation/docs/setup.md index fb2dde759f4ad..814b8791a7c79 100644 --- a/contrib/pg_tde/documentation/docs/setup.md +++ b/contrib/pg_tde/documentation/docs/setup.md @@ -112,19 +112,19 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo 2. Add a default principal key ```sql - SELECT pg_tde_set_default_principal_key_using_global_key_provider('name-of-the-principal-key','provider-name','ensure_new_key'); + SELECT pg_tde_set_default_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` where: - * `name-of-the-principal-key` is the name of the principal key. You will use this name to identify the key. + * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. * `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. :material-information: Warning: This example is for testing purposes only. Replace the key name and provider name with your values: ```sql - SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_set_key_using_global_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` The key is auto-generated. diff --git a/contrib/pg_tde/documentation/docs/test.md b/contrib/pg_tde/documentation/docs/test.md index e848dd267faba..963ac360c1298 100644 --- a/contrib/pg_tde/documentation/docs/test.md +++ b/contrib/pg_tde/documentation/docs/test.md @@ -33,15 +33,7 @@ Here's how to do it: The function returns `t` if the table is encrypted and `f` - if not. -3. Rotate the principal key when needed: - - ``` - SELECT pg_tde_rotate_principal_key(); -- uses automatic key versionin - -- or - SELECT pg_tde_rotate_principal_key('new-principal-key', NULL); -- specify new key name - -- or - SELECT pg_tde_rotate_principal_key('new-principal-key', 'new-provider'); -- changeprovider - ``` +3. Rotate the principal key when needed, see [Principal key management](functions.md#principal-key-management)) ## Encrypt existing table diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 950ff970c0969..184ec61b3758c 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -61,7 +61,7 @@ Here's what to do: 3. Create principal key ```sql - SELECT pg_tde_set_server_principal_key_using_global_key_provider('principal-key', 'provider-name'); + SELECT pg_tde_set_server_key_using_global_key_provider('key', 'provider-name'); ``` 4. Enable WAL level encryption using the `ALTER SYSTEM` command. You need the privileges of the superuser to run this command: diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 220460438cd1b..4bb987955efac 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -4,8 +4,8 @@ SET ROLE regress_pg_tde_access_control; -- should throw access denied SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); ERROR: permission denied for function pg_tde_add_database_key_provider_file -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); -ERROR: permission denied for function pg_tde_set_principal_key_using_database_key_provider +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); +ERROR: permission denied for function pg_tde_set_key_using_database_key_provider RESET ROLE; SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); pg_tde_grant_database_key_management_to_role @@ -33,9 +33,9 @@ SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring 2 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) @@ -46,10 +46,10 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_2.per"} (2 rows) -SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); - principal_key_name | key_provider_name | key_provider_id ------------------------+-------------------+----------------- - test-db-principal-key | file-vault | 1 +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); + key_name | key_provider_name | key_provider_id +-------------+-------------------+----------------- + test-db-key | file-vault | 1 (1 row) RESET ROLE; @@ -63,7 +63,7 @@ SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked SELECT * FROM pg_tde_list_all_database_key_providers(); ERROR: permission denied for function pg_tde_list_all_database_key_providers -SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); -ERROR: permission denied for function pg_tde_principal_key_info +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +ERROR: permission denied for function pg_tde_key_info RESET ROLE; DROP EXTENSION pg_tde CASCADE; diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index 4e9f247e91f42..424c390204cc9 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index 9469ee2ec150e..215fa628deeec 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -6,9 +6,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index 0ed80be28665b..ecef6d96b4e7e 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 08a355b267fbc..f77ba113959d6 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_principal_key.per'); +SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- -3 (1 row) -SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key_using_global_key_provider ------------------------------------------------------------- +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- (1 row) @@ -21,10 +21,10 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); (1 row) -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( @@ -34,11 +34,11 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+----------------------- - -3 | file-provider | default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -3 | file-provider | default-key (1 row) SELECT current_database() AS regress_database @@ -47,10 +47,10 @@ CREATE DATABASE regress_pg_tde_other; \c regress_pg_tde_other CREATE EXTENSION pg_tde; -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, @@ -59,33 +59,33 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+----------------------- - -3 | file-provider | default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -3 | file-provider | default-key (1 row) \c :regress_database -SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key_using_global_key_provider ------------------------------------------------------------- +SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- (1 row) -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+--------------------------- - -3 | file-provider | new-default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+----------------- + -3 | file-provider | new-default-key (1 row) \c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+--------------------------- - -3 | file-provider | new-default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+----------------- + -3 | file-provider | new-default-key (1 row) DROP TABLE test_enc; diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out index 5280f4ab6b547..871fdd9cbd849 100644 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ b/contrib/pg_tde/expected/default_principal_key_1.out @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_principal_key.per'); +SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- -4 (1 row) -SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key_using_global_key_provider ------------------------------------------------------------- +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- (1 row) @@ -22,10 +22,10 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); (2 rows) -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( @@ -35,11 +35,11 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+----------------------- - -4 | file-provider | default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -4 | file-provider | default-key (1 row) SELECT current_database() AS regress_database @@ -48,10 +48,10 @@ CREATE DATABASE regress_pg_tde_other; \c regress_pg_tde_other CREATE EXTENSION pg_tde; -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, @@ -60,33 +60,33 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+----------------------- - -4 | file-provider | default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -4 | file-provider | default-key (1 row) \c :regress_database -SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); - pg_tde_set_default_principal_key_using_global_key_provider ------------------------------------------------------------- +SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- (1 row) -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+--------------------------- - -4 | file-provider | new-default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+----------------- + -4 | file-provider | new-default-key (1 row) \c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+--------------------------- - -4 | file-provider | new-default-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+----------------- + -4 | file-provider | new-default-key (1 row) DROP TABLE test_enc; diff --git a/contrib/pg_tde/expected/delete_key_provider.out b/contrib/pg_tde/expected/delete_key_provider.out index 1c0a7afb375ae..d5724272619d0 100644 --- a/contrib/pg_tde/expected/delete_key_provider.out +++ b/contrib/pg_tde/expected/delete_key_provider.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index 275fc2fffd297..144c5bc49e6ad 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index cb1c94e59f10f..2725b4e108aa1 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -34,17 +34,17 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); ERROR: principal key not configured for current database -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) -SELECT pg_tde_verify_principal_key(); - pg_tde_verify_principal_key ------------------------------ +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- (1 row) @@ -71,8 +71,8 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_verify_principal_key(); -ERROR: failed to retrieve principal key test-db-principal-key from keyring with ID 1 +SELECT pg_tde_verify_key(); +ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -132,9 +132,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -2 | file-keyring2 (2 rows) -SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); - pg_tde_set_principal_key_using_global_key_provider ----------------------------------------------------- +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); + pg_tde_set_key_using_global_key_provider +------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out index af5559bb47aef..39005687d6765 100644 --- a/contrib/pg_tde/expected/key_provider_1.out +++ b/contrib/pg_tde/expected/key_provider_1.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -34,17 +34,17 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); ERROR: principal key not configured for current database -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) -SELECT pg_tde_verify_principal_key(); - pg_tde_verify_principal_key ------------------------------ +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- (1 row) @@ -71,8 +71,8 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_verify_principal_key(); -ERROR: failed to retrieve principal key test-db-principal-key from keyring with ID 1 +SELECT pg_tde_verify_key(); +ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -134,9 +134,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -3 | file-keyring2 (3 rows) -SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); - pg_tde_set_principal_key_using_global_key_provider ----------------------------------------------------- +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); + pg_tde_set_key_using_global_key_provider +------------------------------------------ (1 row) diff --git a/contrib/pg_tde/expected/keyprovider_dependency.out b/contrib/pg_tde/expected/keyprovider_dependency.out index c3d36df527cad..01ae95d792645 100644 --- a/contrib/pg_tde/expected/keyprovider_dependency.out +++ b/contrib/pg_tde/expected/keyprovider_dependency.out @@ -25,9 +25,9 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 3 | V2-vault | vault-v2 | {"type" : "vault-v2", "url" : "percona.com/vault-v2/percona", "token" : "vault-token", "mountPath" : "/mount/dev", "caPath" : "ca-cert-auth"} (3 rows) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','mk-file'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','mk-file'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index bcb708a299833..c590a25be972e 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tm 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('kmip-principal-key','kmip-prov'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/no_provider_error.out b/contrib/pg_tde/expected/no_provider_error.out index e4eb1693d21e3..434781a6c778e 100644 --- a/contrib/pg_tde/expected/no_provider_error.out +++ b/contrib/pg_tde/expected/no_provider_error.out @@ -1,7 +1,7 @@ CREATE EXTENSION pg_tde; -- should fail CREATE TABLE t1 (n INT) USING tde_heap; -ERROR: failed to retrieve principal key. Create one using pg_tde_set_principal_key before using encrypted tables. +ERROR: failed to retrieve principal key. Create one using pg_tde_set_key before using encrypted tables. -- should work CREATE TABLE t2 (n INT) USING heap; DROP TABLE t2; diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index f8c17168c139f..b3f3812ac98c0 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -1,16 +1,16 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) @@ -67,11 +67,11 @@ SELECT pg_tde_is_encrypted(NULL); (1 row) -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); - key_provider_id | key_provider_name | principal_key_name ------------------+-------------------+----------------------- - 1 | file-vault | test-db-principal-key +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + 1 | file-vault | test-db-key (1 row) DROP TABLE test_part; diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index e3ad3b0c66d87..58c3b3a055752 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/subtransaction.out b/contrib/pg_tde/expected/subtransaction.out index 5c08fca3c4751..ebfde89210a5b 100644 --- a/contrib/pg_tde/expected/subtransaction.out +++ b/contrib/pg_tde/expected/subtransaction.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index 353ef4dd1edd4..4d7bffce68de6 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index 014d98fc6a3e7..4ca99eea46751 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -5,9 +5,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 1 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/toast_decrypt_1.out b/contrib/pg_tde/expected/toast_decrypt_1.out index 774578aee406f..3fc3a66ef1483 100644 --- a/contrib/pg_tde/expected/toast_decrypt_1.out +++ b/contrib/pg_tde/expected/toast_decrypt_1.out @@ -6,9 +6,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr 2 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index a88f5a4c75f4f..8943184e4bfb6 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -7,7 +7,7 @@ SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token' (1 row) -- FAILS -SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-incorrect'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); ERROR: Invalid HTTP response from keyring provider "vault-incorrect": 404 CREATE TABLE test_enc( id SERIAL, @@ -15,16 +15,16 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; ERROR: principal key not configured -HINT: create one using pg_tde_set_principal_key before using encrypted tables +HINT: create one using pg_tde_set_key before using encrypted tables SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- 2 (1 row) -SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-v2'); - pg_tde_set_principal_key_using_database_key_provider ------------------------------------------------------- +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- (1 row) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 086947fad2106..7531578bdfe41 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -420,22 +420,22 @@ STRICT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_principal_key_using_database_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(principal_key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C; @@ -445,26 +445,26 @@ RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_verify_principal_key() +CREATE FUNCTION pg_tde_verify_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_verify_server_principal_key() +CREATE FUNCTION pg_tde_verify_server_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_principal_key_info() -RETURNS TABLE ( principal_key_name text, +CREATE FUNCTION pg_tde_key_info() +RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, key_createion_time timestamp with time zone) LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_server_principal_key_info() -RETURNS TABLE ( principal_key_name text, +CREATE FUNCTION pg_tde_server_key_info() +RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, key_createion_time timestamp with time zone) @@ -542,9 +542,9 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); END; $$; @@ -575,7 +575,7 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_principal_key_using_database_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) TO %I', target_role); END; $$; @@ -589,10 +589,10 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_principal_key_info() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_server_principal_key_info() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_principal_key() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_server_principal_key() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_key_info() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_server_key_info() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_key() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_server_key() TO %I', target_role); END; $$; @@ -623,9 +623,9 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_server_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_default_principal_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); END; $$; @@ -656,7 +656,7 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_principal_key_using_database_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) FROM %I', target_role); END; $$; @@ -670,10 +670,10 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_principal_key_info() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_server_principal_key_info() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_principal_key() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_server_principal_key() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_key_info() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_server_key_info() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_key() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_server_key() FROM %I', target_role); END; $$; diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 20440d5b277ac..6ead98a608c1e 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -6,7 +6,7 @@ SET ROLE regress_pg_tde_access_control; -- should throw access denied SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); RESET ROLE; @@ -18,9 +18,9 @@ SET ROLE regress_pg_tde_access_control; -- should now be allowed SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key', 'file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); RESET ROLE; @@ -30,7 +30,7 @@ SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT principal_key_name, key_provider_name, key_provider_id FROM pg_tde_principal_key_info(); +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); RESET ROLE; diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 7589b0da49073..9dac7bea58338 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/cache_alloc.sql b/contrib/pg_tde/sql/cache_alloc.sql index 59927ec0c362f..745fdacfc18d8 100644 --- a/contrib/pg_tde/sql/cache_alloc.sql +++ b/contrib/pg_tde/sql/cache_alloc.sql @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); do $$ DECLARE idx integer; diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index c1818c2888d9e..105b44d81bf0e 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index ee1193601f4c1..37adab9418bb7 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -1,16 +1,16 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_principal_key.per'); +SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); -SELECT pg_tde_set_default_principal_key_using_global_key_provider('default-principal-key', 'file-provider', false); +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( @@ -22,8 +22,8 @@ CREATE TABLE test_enc( INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); SELECT current_database() AS regress_database \gset @@ -35,8 +35,8 @@ CREATE DATABASE regress_pg_tde_other; CREATE EXTENSION pg_tde; -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( @@ -48,20 +48,20 @@ CREATE TABLE test_enc( INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); \c :regress_database -SELECT pg_tde_set_default_principal_key_using_global_key_provider('new-default-principal-key', 'file-provider', false); +SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); \c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); DROP TABLE test_enc; diff --git a/contrib/pg_tde/sql/delete_key_provider.sql b/contrib/pg_tde/sql/delete_key_provider.sql index 781297ee9b6b6..935782f2656cd 100644 --- a/contrib/pg_tde/sql/delete_key_provider.sql +++ b/contrib/pg_tde/sql/delete_key_provider.sql @@ -1,6 +1,6 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index 1ca2535a26bda..a586bf302f451 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE albums ( id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 62a9e1d4a00f4..ad0bb048d4af6 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -1,6 +1,6 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -11,17 +11,17 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider'); -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); +SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -44,7 +44,7 @@ SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -SELECT pg_tde_set_principal_key_using_global_key_provider('test-db-principal-key', 'file-keyring', false); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); diff --git a/contrib/pg_tde/sql/keyprovider_dependency.sql b/contrib/pg_tde/sql/keyprovider_dependency.sql index 35ae5770724fe..f8243a5e6bf54 100644 --- a/contrib/pg_tde/sql/keyprovider_dependency.sql +++ b/contrib/pg_tde/sql/keyprovider_dependency.sql @@ -6,6 +6,6 @@ SELECT pg_tde_add_database_key_provider_vault_v2('V2-vault','vault-token','perco SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','mk-file'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','mk-file'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index 79877c2debd98..9be6b8f4e2e83 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -1,7 +1,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); -SELECT pg_tde_set_principal_key_using_database_key_provider('kmip-principal-key','kmip-prov'); +SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index f11f50200295d..ceb0831c5c486 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -1,9 +1,9 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_principal_key_info(); +SELECT * FROM pg_tde_key_info(); SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE test_enc( id SERIAL, @@ -33,8 +33,8 @@ SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('t SELECT pg_tde_is_encrypted(NULL); -SELECT key_provider_id, key_provider_name, principal_key_name - FROM pg_tde_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_key_info(); DROP TABLE test_part; DROP TABLE test_norm; diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 778baed5b1b90..3296a87152767 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/subtransaction.sql b/contrib/pg_tde/sql/subtransaction.sql index c93b2d67e7e6e..121f1b67c3ab1 100644 --- a/contrib/pg_tde/sql/subtransaction.sql +++ b/contrib/pg_tde/sql/subtransaction.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); BEGIN; -- Nesting level 1 diff --git a/contrib/pg_tde/sql/tablespace.sql b/contrib/pg_tde/sql/tablespace.sql index 86888fbc97390..7e2abce87ca13 100644 --- a/contrib/pg_tde/sql/tablespace.sql +++ b/contrib/pg_tde/sql/tablespace.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE test(num1 bigint, num2 double precision, t text) USING tde_heap; INSERT INTO test(num1, num2, t) diff --git a/contrib/pg_tde/sql/toast_decrypt.sql b/contrib/pg_tde/sql/toast_decrypt.sql index c97702c5fea2b..4cd6cf513f618 100644 --- a/contrib/pg_tde/sql/toast_decrypt.sql +++ b/contrib/pg_tde/sql/toast_decrypt.sql @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE src (f1 TEXT STORAGE EXTERNAL) USING tde_heap; INSERT INTO src VALUES(repeat('abcdeF',1000)); diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 1e4e9c9a1f3a6..f50aff68c59c6 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -4,7 +4,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); -- FAILS -SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-incorrect'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); CREATE TABLE test_enc( id SERIAL, @@ -13,7 +13,7 @@ CREATE TABLE test_enc( ) USING tde_heap; SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); -SELECT pg_tde_set_principal_key_using_database_key_provider('vault-v2-principal-key','vault-v2'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 61335dd362d7b..85d84752eb969 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -162,7 +162,7 @@ pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type { ereport(ERROR, (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_principal_key before using encrypted tables"))); + errhint("create one using pg_tde_set_key before using encrypted tables"))); } /* @@ -235,7 +235,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat { ereport(ERROR, (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_server_principal_key before using encrypted WAL"))); + errhint("create one using pg_tde_set_server_key before using encrypted WAL"))); } /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ @@ -960,7 +960,7 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) if (principal_key == NULL) ereport(ERROR, (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_principal_key before using encrypted tables"))); + errhint("create one using pg_tde_set_key before using encrypted tables"))); rel_key = tde_decrypt_rel_key(principal_key, map_entry); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index e989bfe5d391f..7007f00218dee 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -52,8 +52,8 @@ PG_FUNCTION_INFO_V1(pg_tde_delete_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); -PG_FUNCTION_INFO_V1(pg_tde_verify_principal_key); -PG_FUNCTION_INFO_V1(pg_tde_verify_server_principal_key); +PG_FUNCTION_INFO_V1(pg_tde_verify_key); +PG_FUNCTION_INFO_V1(pg_tde_verify_server_key); typedef struct TdePrincipalKeySharedState { @@ -110,17 +110,17 @@ static bool pg_tde_verify_principal_key_internal(Oid databaseOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global); -PG_FUNCTION_INFO_V1(pg_tde_set_default_principal_key_using_global_key_provider); -Datum pg_tde_set_default_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); +Datum pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_principal_key_using_database_key_provider); -Datum pg_tde_set_principal_key_using_database_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); +Datum pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_principal_key_using_global_key_provider); -Datum pg_tde_set_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); +Datum pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS); -PG_FUNCTION_INFO_V1(pg_tde_set_server_principal_key_using_global_key_provider); -Datum pg_tde_set_server_principal_key_using_global_key_provider(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); +Datum pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS); enum global_status { @@ -485,7 +485,7 @@ clear_principal_key_cache(Oid databaseId) */ Datum -pg_tde_set_default_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) +pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -497,7 +497,7 @@ pg_tde_set_default_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) } Datum -pg_tde_set_principal_key_using_database_key_provider(PG_FUNCTION_ARGS) +pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -509,7 +509,7 @@ pg_tde_set_principal_key_using_database_key_provider(PG_FUNCTION_ARGS) } Datum -pg_tde_set_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) +pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -521,7 +521,7 @@ pg_tde_set_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) } Datum -pg_tde_set_server_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) +pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); @@ -533,14 +533,14 @@ pg_tde_set_server_principal_key_using_global_key_provider(PG_FUNCTION_ARGS) } static void -pg_tde_set_principal_key_internal(char *principal_key_name, enum global_status global, char *provider_name, bool ensure_new_key) +pg_tde_set_principal_key_internal(char *key_name, enum global_status global, char *provider_name, bool ensure_new_key) { Oid providerOid = MyDatabaseId; Oid dbOid = MyDatabaseId; TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; - ereport(LOG, (errmsg("Setting principal key [%s : %s] for the database", principal_key_name, provider_name))); + ereport(LOG, (errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name))); if (global == GS_GLOBAL) /* using a global provider for the current * database */ @@ -570,7 +570,7 @@ pg_tde_set_principal_key_internal(char *principal_key_name, enum global_status g LWLockRelease(tde_lwlock_enc_keys()); } - set_principal_key_with_keyring(principal_key_name, + set_principal_key_with_keyring(key_name, provider_name, providerOid, dbOid, @@ -600,28 +600,28 @@ pg_tde_set_principal_key_internal(char *principal_key_name, enum global_status g } } -PG_FUNCTION_INFO_V1(pg_tde_principal_key_info); +PG_FUNCTION_INFO_V1(pg_tde_key_info); Datum -pg_tde_principal_key_info(PG_FUNCTION_ARGS) +pg_tde_key_info(PG_FUNCTION_ARGS) { return pg_tde_get_key_info(fcinfo, MyDatabaseId); } -PG_FUNCTION_INFO_V1(pg_tde_server_principal_key_info); +PG_FUNCTION_INFO_V1(pg_tde_server_key_info); Datum -pg_tde_server_principal_key_info(PG_FUNCTION_ARGS) +pg_tde_server_key_info(PG_FUNCTION_ARGS) { return pg_tde_get_key_info(fcinfo, GLOBAL_DATA_TDE_OID); } Datum -pg_tde_verify_principal_key(PG_FUNCTION_ARGS) +pg_tde_verify_key(PG_FUNCTION_ARGS) { return pg_tde_verify_principal_key_internal(MyDatabaseId); } Datum -pg_tde_verify_server_principal_key(PG_FUNCTION_ARGS) +pg_tde_verify_server_key(PG_FUNCTION_ARGS) { return pg_tde_verify_principal_key_internal(GLOBAL_DATA_TDE_OID); } @@ -650,7 +650,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) { ereport(ERROR, (errmsg("Principal key does not exists for the database"), - errhint("Use set_principal_key interface to set the principal key"))); + errhint("Use set_key interface to set the principal key"))); } keyring = GetKeyProviderByID(principal_key->keyInfo.keyringId, principal_key->keyInfo.databaseId); diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 39a1c637c7776..4c55178976525 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -64,8 +64,7 @@ checkEncryptionClause(const char *accessMethod) { ereport(ERROR, (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_principal_key before using encrypted tables"))); - + errhint("create one using pg_tde_set_key before using encrypted tables"))); } } diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 5b8b4ac958180..4740992f03257 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -46,7 +46,7 @@ ok($rt_value == 1, "Restart Server"); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index be37c72b01d07..31b99250dadec 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -54,7 +54,7 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -67,7 +67,7 @@ PGTDE::append_to_file($stdout); #rotate key -$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key1');", extra_params => ['-a']); +$stdout = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -77,9 +77,9 @@ $rt_value = $node->stop(); $rt_value = $node->start(); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -87,7 +87,7 @@ #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -97,16 +97,16 @@ $rt_value = $node->stop(); $rt_value = $node->start(); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-key', 'file-3', false);", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -116,9 +116,9 @@ $rt_value = $node->stop(); $rt_value = $node->start(); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -128,7 +128,7 @@ # And maybe debug tools to show what's in a file keyring? #Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX', 'file-2', false);", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -138,9 +138,9 @@ $rt_value = $node->stop(); $rt_value = $node->start(); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); @@ -156,19 +156,19 @@ $rt_value = $node->start(); # But now can't be changed to another global provider -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX2', 'file-2', false);", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);", extra_params => ['-a']); PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info();", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 51c9696bfe76c..40fb1be7c0d72 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -71,7 +71,7 @@ sub resp_hello { PGTDE::append_to_file($stdout); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 4da05cb4afe71..4bf359d04d5df 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -35,7 +35,7 @@ PGTDE::append_to_file($stdout); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 8e00e88b5c537..d3beecb8843fb 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -87,7 +87,7 @@ PGTDE::append_to_debug_file($stdout); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index f8a1d0aeac779..c0fb84b3d159c 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -79,7 +79,7 @@ sub resp_url { PGTDE::append_to_file($stdout); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','vault-provider');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index e169a47bdb5b6..be006eb7a3898 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -50,7 +50,7 @@ ok($rt_value == 1, "Restart Server"); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index d2c3f34200648..3203da3db72bd 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -38,7 +38,7 @@ q{ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, @@ -57,7 +57,7 @@ PGTDE::append_to_file($stdout); -$cmdret = $node->psql('tbc', "SELECT pg_tde_set_principal_key_using_database_key_provider('new-k', 'file-vault');", extra_params => ['-a']); +$cmdret = $node->psql('tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');", extra_params => ['-a']); ok($cmdret == 0, "ROTATE KEY"); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 6885289080f49..481f83d85413b 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -33,7 +33,7 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-010');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 83a831de7da2d..a59ecab9d7dad 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -39,7 +39,7 @@ PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -47,7 +47,7 @@ $stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);', extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -62,7 +62,7 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -75,7 +75,7 @@ $rt_value = $node->start(); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -88,7 +88,7 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); PGTDE::append_to_file($stdout); -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); @@ -102,7 +102,7 @@ $rt_value = $node->start(); # Verify -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); (undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); @@ -121,7 +121,7 @@ $rt_value = $node->start(); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -141,7 +141,7 @@ # Change provider and generate a new principal key $stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->psql('postgres', "SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); +$stdout = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -149,7 +149,7 @@ $stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);', extra_params => ['-a']); PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -165,7 +165,7 @@ $rt_value = $node->start(); # Verify -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); (undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); @@ -182,7 +182,7 @@ PGTDE::append_to_file($stdout); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_principal_key();", extra_params => ['-a']); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index bfc3b67f4415d..19f435e287eb8 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -11,7 +11,7 @@ SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3. SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}") (2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5),(6); @@ -23,71 +23,71 @@ SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -- server restart -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -1|file-vault|rotated-principal-key1 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +1|file-vault|rotated-key1 +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2'); +SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -- server restart -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -2|file-2|rotated-principal-key2 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +2|file-2|rotated-key2 +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-key', 'file-3', false); +SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false); SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -- server restart -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); --2|file-3|rotated-principal-key -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +-2|file-3|rotated-key +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -SELECT pg_tde_set_principal_key_using_global_key_provider('rotated-principal-keyX', 'file-2', false); +SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false); SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -- server restart -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); --1|file-2|rotated-principal-keyX -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +-1|file-2|rotated-keyX +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF; -- server restart psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); --1|file-2|rotated-principal-keyX -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +-1|file-2|rotated-keyX +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key -SELECT pg_tde_set_principal_key_using_database_key_provider('rotated-principal-key2','file-2'); +HINT: Use set_key interface to set the principal key +SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_principal_key_info(); -2|file-2|rotated-principal-key2 -SELECT key_provider_id, key_provider_name, principal_key_name FROM pg_tde_server_principal_key_info(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +2|file-2|rotated-key2 +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_principal_key interface to set the principal key +HINT: Use set_key interface to set the principal key DROP TABLE test_enc; ALTER SYSTEM RESET pg_tde.inherit_global_providers; DROP EXTENSION pg_tde CASCADE; diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index 704d4de559b8e..58600feeeb367 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, @@ -19,7 +19,7 @@ SELECT * FROM country_table; CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 -SELECT pg_tde_set_principal_key_using_database_key_provider('test-db-principal-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE country_table ( country_id serial primary key, diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index 9947d0f0a1c69..64fb19fa21bc4 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -1,7 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per'); -1 -SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-010'); +SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index aa33f0f7ef642..d7cfbd2a394b0 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -3,11 +3,11 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov 1 SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_1.per""}") -SELECT pg_tde_set_principal_key_using_database_key_provider('test-key', 'file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t @@ -19,7 +19,7 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p 1 SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_2.per""}") -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t @@ -27,7 +27,7 @@ SELECT * FROM test_enc ORDER BY id; 1|5 2|6 -- server restart -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t @@ -38,7 +38,7 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p 1 SELECT pg_tde_list_all_database_key_providers(); (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_3.per""}") -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 SELECT pg_tde_is_encrypted('test_enc'); t @@ -46,7 +46,7 @@ SELECT * FROM test_enc ORDER BY id; 1|5 2|6 -- server restart -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 SELECT pg_tde_is_encrypted('test_enc'); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 @@ -54,7 +54,7 @@ SELECT * FROM test_enc ORDER BY id; psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 -- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per -- server restart -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t @@ -69,7 +69,7 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov 0 CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t @@ -79,7 +79,7 @@ SELECT * FROM test_enc ORDER BY id; SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); 1 -- server restart -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT pg_tde_is_encrypted('test_enc'); psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file @@ -89,7 +89,7 @@ CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); 1 -SELECT pg_tde_verify_principal_key(); +SELECT pg_tde_verify_key(); SELECT pg_tde_is_encrypted('test_enc'); t diff --git a/src/bin/pg_waldump/t/003_basic_encrypted.pl b/src/bin/pg_waldump/t/003_basic_encrypted.pl index 85ce6d3b3f509..9f064f0c205fa 100644 --- a/src/bin/pg_waldump/t/003_basic_encrypted.pl +++ b/src/bin/pg_waldump/t/003_basic_encrypted.pl @@ -28,7 +28,7 @@ $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"); $node->append_conf( 'postgresql.conf', q{ diff --git a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl b/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl index 586c2454926b2..f737daa4232a4 100644 --- a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl +++ b/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl @@ -42,7 +42,7 @@ sub get_block_lsn $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_principal_key_using_global_key_provider('server-principal-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"); $node->append_conf( 'postgresql.conf', q{ From d158ba58ac6e21a6f63c4374fad1069667d0e39e Mon Sep 17 00:00:00 2001 From: Anastasia Alexandrova Date: Thu, 10 Apr 2025 19:57:23 +0200 Subject: [PATCH 025/796] Update doc link readme (#175) Co-authored-by: Andrew Pogrebnoi --- contrib/pg_tde/README.md | 4 ++-- contrib/pg_tde/documentation/docs/release-notes/rc.md | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index aae4bc3fe2f20..437388716c5bb 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -27,11 +27,11 @@ Transparent Data Encryption offers encryption at the file level and solves the p ## Documentation -Full and comprehensive documentation about `pg_tde` is available at https://percona.github.io/pg_tde/. +Full and comprehensive documentation about `pg_tde` is available at https://docs.percona.com/pg-tde/index.html. ## Percona Server for PostgreSQL -Percona provides binary packages of `pg_tde` extension only for Percona Server for PostgreSQL. Learn how to install them or build `pg_tde` from sources for PSPG in the [documentation](https://percona.github.io/pg_tde/main/install.html). +Percona provides binary packages of `pg_tde` extension only for Percona Server for PostgreSQL. Learn how to install them or build `pg_tde` from sources for PSPG in the [documentation](https://docs.percona.com/pg-tde/install.html). ## Building from sources for community PostgreSQL 1. Install required dependencies (replace XX with 16 or 17) diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc.md b/contrib/pg_tde/documentation/docs/release-notes/rc.md index 9b07d45121f1d..9ee5fc16d73e5 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc.md @@ -39,20 +39,19 @@ This release provides the following features and improvements: ## Known issues -* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures is 64 Kb. The internal `pg_tde` key size is 40 bytes, which results in about 1600 keys that fall into the limit. When the `mlock` limit is reached, `pg_tde` cannot lock memory for more keys and can fail with the error. +* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. - To prevent this, you can change the `mlock` limit: + To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: - * temporarily for the current session using the `ulimit -l ` command. - * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + * temporarily for the current session using the `ulimit -l ` command. + * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. - Adjust the limits with caution since it affects other processes running in your system. + Adjust the limits with caution since it affects other processes running in your system. * You can now delete global key providers even when their associated principal key is still in use. This known issue will be fixed in the next release. For now, avoid deleting global key providers. - ## Changelog ### New Features From 725c34da520cca4597f6751d1d671c6191466200 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 10 Apr 2025 11:27:52 +0200 Subject: [PATCH 026/796] Modernize our way to call ereport() Commit e3a87b4991cc2d00b7a3082abb54c5f12baedfd1 added a way to call ereport() without any extra parentheses which was backported all the way back to PostgreSQL 12. The new modern way improves code readability slightly. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 118 +++++++++--------- contrib/pg_tde/src/catalog/tde_keyring.c | 95 +++++++------- .../src/catalog/tde_keyring_parse_opts.c | 4 +- .../pg_tde/src/catalog/tde_principal_key.c | 62 +++++---- contrib/pg_tde/src/common/pg_tde_shmem.c | 6 +- contrib/pg_tde/src/common/pg_tde_utils.c | 4 +- contrib/pg_tde/src/encryption/enc_aes.c | 10 +- contrib/pg_tde/src/encryption/enc_tde.c | 8 +- contrib/pg_tde/src/keyring/keyring_file.c | 26 ++-- contrib/pg_tde/src/keyring/keyring_kmip.c | 18 +-- contrib/pg_tde/src/keyring/keyring_vault.c | 24 ++-- contrib/pg_tde/src/pg_tde.c | 12 +- contrib/pg_tde/src/pg_tde_event_capture.c | 12 +- .../pg_tde/src/transam/pg_tde_xact_handler.c | 11 +- 14 files changed, 201 insertions(+), 209 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 85d84752eb969..c1b1324ecfe2f 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -161,8 +161,8 @@ pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type if (principal_key == NULL) { ereport(ERROR, - (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables"))); + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); } /* @@ -192,14 +192,14 @@ pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type) if (!RAND_bytes(int_key->key, INTERNAL_KEY_LEN)) ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate internal key: %s", - ERR_error_string(ERR_get_error(), NULL)))); + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate internal key: %s", + ERR_error_string(ERR_get_error(), NULL))); if (!RAND_bytes(int_key->base_iv, INTERNAL_KEY_IV_LEN)) ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate IV: %s", - ERR_error_string(ERR_get_error(), NULL)))); + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate IV: %s", + ERR_error_string(ERR_get_error(), NULL))); } const char * @@ -234,8 +234,8 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat if (principal_key == NULL) { ereport(ERROR, - (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_server_key before using encrypted WAL"))); + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_server_key before using encrypted WAL")); } /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ @@ -300,7 +300,7 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key) /* Set the file paths */ pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); - ereport(DEBUG2, (errmsg("pg_tde_save_principal_key"))); + ereport(DEBUG2, errmsg("pg_tde_save_principal_key")); pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); @@ -329,19 +329,18 @@ pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrinci if (*bytes_written != TDE_FILE_HEADER_SIZE) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write tde file \"%s\": %m", - tde_filename))); + errcode_for_file_access(), + errmsg("could not write tde file \"%s\": %m", tde_filename)); } if (pg_fsync(fd) != 0) { ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", tde_filename))); + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", tde_filename)); } - ereport(DEBUG2, - (errmsg("Wrote the header to %s", tde_filename))); + + ereport(DEBUG2, errmsg("Wrote the header to %s", tde_filename)); return fd; } @@ -353,8 +352,8 @@ pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_EMPTY_IV_SIZE)) ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL)))); + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); AesGcmEncrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag); } @@ -369,8 +368,8 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_EMPTY_IV_SIZE)) ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL)))); + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); AesGcmEncrypt(principal_key->keyData, map_entry->entry_iv, (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), rel_key_data->key, INTERNAL_KEY_LEN, map_entry->enc_key.key, map_entry->aead_tag); } @@ -389,15 +388,14 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, if (bytes_written != MAP_ENTRY_SIZE) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write tde map file \"%s\": %m", - db_map_path))); + errcode_for_file_access(), + errmsg("could not write tde map file \"%s\": %m", db_map_path)); } if (pg_fsync(fd) != 0) { ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", db_map_path))); + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", db_map_path)); } return (*offset + bytes_written); @@ -565,9 +563,9 @@ pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t if (curr_pos == -1) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not seek in tde map file \"%s\": %m", - db_map_path))); + errcode_for_file_access(), + errmsg("could not seek in tde map file \"%s\": %m", + db_map_path)); } } @@ -641,11 +639,11 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset) if (!found) { ereport(WARNING, - (errcode(ERRCODE_NO_DATA_FOUND), - errmsg("could not find the required map entry for deletion of relation %d in tablespace %d in tde map file \"%s\": %m", - rlocator->relNumber, - rlocator->spcOid, - db_map_path))); + errcode(ERRCODE_NO_DATA_FOUND), + errmsg("could not find the required map entry for deletion of relation %d in tablespace %d in tde map file \"%s\": %m", + rlocator->relNumber, + rlocator->spcOid, + db_map_path)); } } @@ -751,8 +749,8 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p if (pg_pread(new_fd, xlrec->buff, xlrec->file_size, 0) == -1) ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write WAL for key rotation: %m"))); + errcode_for_file_access(), + errmsg("could not write WAL for key rotation: %m")); close(new_fd); @@ -792,16 +790,16 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) if (pg_pwrite(fd_new, file_data, file_size, 0) != file_size) { ereport(WARNING, - (errcode_for_file_access(), - errmsg("could not write tde file \"%s\": %m", path_new))); + errcode_for_file_access(), + errmsg("could not write tde file \"%s\": %m", path_new)); is_err = true; goto FINALIZE; } if (pg_fsync(fd_new) != 0) { ereport(WARNING, - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", path_new))); + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", path_new)); is_err = true; goto FINALIZE; } @@ -835,8 +833,8 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) if (pg_pwrite(fd, &lsn, sizeof(XLogRecPtr), write_pos) != sizeof(XLogRecPtr)) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write tde key data file: %m"))); + errcode_for_file_access(), + errmsg("could not write tde key data file: %m")); } /* @@ -852,8 +850,8 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) if (pg_pread(fd, &prev_map_entry, MAP_ENTRY_SIZE, prev_key_pos) != MAP_ENTRY_SIZE) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not read previous WAL key: %m"))); + errcode_for_file_access(), + errmsg("could not read previous WAL key: %m")); } if (prev_map_entry.enc_key.start_lsn >= lsn) @@ -863,8 +861,8 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) if (pg_pwrite(fd, &prev_map_entry, MAP_ENTRY_SIZE, prev_key_pos) != MAP_ENTRY_SIZE) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not write invalidated key: %m"))); + errcode_for_file_access(), + errmsg("could not write invalidated key: %m")); } } } @@ -872,8 +870,8 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) if (pg_fsync(fd) != 0) { ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), - errmsg("could not fsync file: %m"))); + errcode_for_file_access(), + errmsg("could not fsync file: %m")); } LWLockRelease(lock_pk); @@ -959,8 +957,8 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) principal_key = GetPrincipalKey(rlocator->dbOid, LW_SHARED); if (principal_key == NULL) ereport(ERROR, - (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables"))); + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); rel_key = tde_decrypt_rel_key(principal_key, map_entry); @@ -1033,7 +1031,7 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) if (!AesGcmDecrypt(principal_key->keyData, map_entry->entry_iv, (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), map_entry->enc_key.key, INTERNAL_KEY_LEN, rel_key_data->key, map_entry->aead_tag)) ereport(ERROR, - (errmsg("Failed to decrypt key, incorrect principal key or corrupted key file"))); + errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); return rel_key_data; @@ -1078,9 +1076,8 @@ pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_miss if (fd < 0 && !(errno == ENOENT && ignore_missing == true)) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open tde file \"%s\": %m", - tde_filename))); + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", tde_filename)); } return fd; @@ -1107,9 +1104,8 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader /* Corrupt file */ close(fd); ereport(FATAL, - (errcode_for_file_access(), - errmsg("TDE map file \"%s\" is corrupted: %m", - tde_filename))); + errcode_for_file_access(), + errmsg("TDE map file \"%s\" is corrupted: %m", tde_filename)); } } @@ -1171,9 +1167,9 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid pg_tde_set_db_file_path(databaseId, db_map_path); ereport(FATAL, - (errcode_for_file_access(), - errmsg("could not find the required key at index %d in tde data file \"%s\": %m", - key_index, db_map_path))); + errcode_for_file_access(), + errmsg("could not find the required key at index %d in tde data file \"%s\": %m", + key_index, db_map_path)); } } diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b4b1773e787fb..ebeb6444ddf73 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -149,7 +149,7 @@ tde_provider_info_lock(void) void InitializeKeyProviderInfo(void) { - ereport(LOG, (errmsg("initializing TDE key provider info"))); + ereport(LOG, errmsg("initializing TDE key provider info")); RegisterShmemRequest(&key_provider_info_shmem_routine); on_ext_install(key_provider_startup_cleanup, NULL); } @@ -160,7 +160,7 @@ key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, if (tde_tbl_count > 0) { ereport(WARNING, - (errmsg("failed to perform initialization. database already has %d TDE tables", tde_tbl_count))); + errmsg("failed to perform initialization. database already has %d TDE tables", tde_tbl_count)); return; } cleanup_key_provider_info(ext_info->database_id); @@ -235,14 +235,14 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) nlen = strlen(provider_name); if (nlen >= sizeof(provider.provider_name)) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1)); olen = strlen(options); if (olen >= sizeof(provider.options)) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too large provider options, maximum size is %ld bytes", sizeof(provider.options) - 1))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too large provider options, maximum size is %ld bytes", sizeof(provider.options) - 1)); /* Struct will be saved to disk so keep clean */ memset(&provider, 0, sizeof(provider)); @@ -280,14 +280,14 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) nlen = strlen(provider_name); if (nlen >= sizeof(provider.provider_name) - 1) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1)); olen = strlen(options); if (olen >= sizeof(provider.options)) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too large provider options, maximum size is %ld bytes", sizeof(provider.options) - 1))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too large provider options, maximum size is %ld bytes", sizeof(provider.options) - 1)); /* Struct will be saved to disk so keep clean */ memset(&provider, 0, sizeof(provider)); @@ -327,12 +327,12 @@ pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTI /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s: set-valued function called in context that cannot accept a set", fname))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s: set-valued function called in context that cannot accept a set", fname)); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("%s: materialize mode required, but it is not allowed in this context", fname))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s: materialize mode required, but it is not allowed in this context", fname)); /* Switch into long-lived context to construct returned data structures */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; @@ -405,7 +405,7 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, if (error_if_exists && provider->provider_id != 0) { ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid write provider call"))); + errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid write provider call")); } /* Try to parse the JSON data first: if it doesn't work, don't save it! */ @@ -415,7 +415,7 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, if (record == NULL) { ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid provider options"))); + errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid provider options")); } else { @@ -431,8 +431,8 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, if (fd < 0) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open tde file \"%s\": %m", kp_info_path))); + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", kp_info_path)); } if (position == -1) { @@ -455,8 +455,8 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, { close(fd); ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("key provider \"%s\" already exists", provider->provider_name))); + errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("key provider \"%s\" already exists", provider->provider_name)); } else { @@ -526,17 +526,16 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, { close(fd); ereport(ERROR, - (errcode_for_file_access(), - errmsg("key provider info file \"%s\" can't be written: %m", - kp_info_path))); + errcode_for_file_access(), + errmsg("key provider info file \"%s\" can't be written: %m", + kp_info_path)); } if (pg_fsync(fd) != 0) { close(fd); ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", - kp_info_path))); + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", kp_info_path)); } close(fd); LWLockRelease(tde_provider_info_lock()); @@ -635,8 +634,8 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) { LWLockRelease(tde_provider_info_lock()); ereport(DEBUG2, - (errcode_for_file_access(), - errmsg("could not open tde file \"%s\": %m", kp_info_path))); + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", kp_info_path)); return providers_list; } while (fetch_next_key_provider(fd, &curr_pos, &provider)) @@ -650,7 +649,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) } ereport(DEBUG2, - (errmsg("read key provider ID=%d %s", provider.provider_id, provider.provider_name))); + errmsg("read key provider ID=%d %s", provider.provider_id, provider.provider_name)); if (scanType == PROVIDER_SCAN_BY_NAME) { @@ -745,8 +744,8 @@ load_file_keyring_provider_options(char *keyring_options) if (file_keyring->file_name == NULL || file_keyring->file_name[0] == '\0') { ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("file path is missing in the keyring options"))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("file path is missing in the keyring options")); return NULL; } @@ -771,11 +770,11 @@ load_vaultV2_keyring_provider_options(char *keyring_options) vaultV2_keyring->vault_mount_path == NULL || vaultV2_keyring->vault_mount_path[0] == '\0') { ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("missing in the keyring options:%s%s%s", - (vaultV2_keyring->vault_token != NULL && vaultV2_keyring->vault_token[0] != '\0') ? "" : " token", - (vaultV2_keyring->vault_url != NULL && vaultV2_keyring->vault_url[0] != '\0') ? "" : " url", - (vaultV2_keyring->vault_mount_path != NULL && vaultV2_keyring->vault_mount_path[0] != '\0') ? "" : " mountPath"))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing in the keyring options:%s%s%s", + (vaultV2_keyring->vault_token != NULL && vaultV2_keyring->vault_token[0] != '\0') ? "" : " token", + (vaultV2_keyring->vault_url != NULL && vaultV2_keyring->vault_url[0] != '\0') ? "" : " url", + (vaultV2_keyring->vault_mount_path != NULL && vaultV2_keyring->vault_mount_path[0] != '\0') ? "" : " mountPath")); return NULL; } @@ -801,12 +800,12 @@ load_kmip_keyring_provider_options(char *keyring_options) strlen(kmip_keyring->kmip_cert_path) == 0) { ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("missing in the keyring options:%s%s%s%s", - (kmip_keyring->kmip_host != NULL && kmip_keyring->kmip_host[0] != '\0') ? "" : " host", - (kmip_keyring->kmip_port != NULL && kmip_keyring->kmip_port[0] != '\0') ? "" : " port", - (kmip_keyring->kmip_ca_path != NULL && kmip_keyring->kmip_ca_path[0] != '\0') ? "" : " caPath", - (kmip_keyring->kmip_cert_path != NULL && kmip_keyring->kmip_cert_path[0] != '\0') ? "" : " certPath"))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing in the keyring options:%s%s%s%s", + (kmip_keyring->kmip_host != NULL && kmip_keyring->kmip_host[0] != '\0') ? "" : " host", + (kmip_keyring->kmip_port != NULL && kmip_keyring->kmip_port[0] != '\0') ? "" : " port", + (kmip_keyring->kmip_ca_path != NULL && kmip_keyring->kmip_ca_path[0] != '\0') ? "" : " caPath", + (kmip_keyring->kmip_cert_path != NULL && kmip_keyring->kmip_cert_path[0] != '\0') ? "" : " certPath")); return NULL; } @@ -871,9 +870,9 @@ fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider close(fd); /* Corrupt file */ ereport(ERROR, - (errcode_for_file_access(), - errmsg("key provider info file is corrupted: %m"), - errdetail("invalid key provider record size %ld expected %lu", bytes_read, sizeof(KeyringProviderRecord)))); + errcode_for_file_access(), + errmsg("key provider info file is corrupted: %m"), + errdetail("invalid key provider record size %ld expected %lu", bytes_read, sizeof(KeyringProviderRecord))); } return true; } @@ -919,9 +918,9 @@ GetKeyProviderByName(const char *provider_name, Oid dbOid) else { ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("key provider \"%s\" does not exists", provider_name), - errhint("Create the key provider"))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key provider \"%s\" does not exists", provider_name), + errhint("Create the key provider")); } return keyring; } diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 8085c34778586..e811040d82976 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -189,8 +189,8 @@ ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf if (jerr != JSON_SUCCESS) { ereport(ERROR, - (errmsg("parsing of keyring options failed: %s", - json_errdetail(jerr, jlex)))); + errmsg("parsing of keyring options failed: %s", + json_errdetail(jerr, jlex))); } #if PG_VERSION_NUM >= 170000 diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 7007f00218dee..26af5bf67ea15 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -142,7 +142,7 @@ static const TDEShmemSetupRoutine principal_key_info_shmem_routine = { void InitializePrincipalKeyInfo(void) { - ereport(LOG, (errmsg("Initializing TDE principal key info"))); + ereport(LOG, errmsg("Initializing TDE principal key info")); RegisterShmemRequest(&principal_key_info_shmem_routine); on_ext_install(principal_key_startup_cleanup, NULL); } @@ -188,7 +188,7 @@ initialize_shared_state(void *start_address) { TdePrincipalKeySharedState *sharedState = (TdePrincipalKeySharedState *) start_address; - ereport(LOG, (errmsg("initializing shared state for principal key"))); + ereport(LOG, errmsg("initializing shared state for principal key")); principalKeyLocalState.dsa = NULL; principalKeyLocalState.sharedHash = NULL; @@ -204,7 +204,7 @@ initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area) dshash_table *dsh; TdePrincipalKeySharedState *sharedState = principalKeyLocalState.sharedPrincipalKeyState; - ereport(LOG, (errmsg("initializing dsa area objects for principal key"))); + ereport(LOG, errmsg("initializing dsa area objects for principal key")); Assert(sharedState != NULL); @@ -271,7 +271,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (AllowInheritGlobalProviders == false && providerOid != dbOid) { ereport(ERROR, - (errmsg("Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON"))); + errmsg("Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON")); } /* @@ -285,7 +285,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (provider_name == NULL && !already_has_key) { ereport(ERROR, - (errmsg("provider_name is a required parameter when creating the first principal key for a database"))); + errmsg("provider_name is a required parameter when creating the first principal key for a database")); } if (provider_name != NULL) @@ -306,29 +306,28 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE) { ereport(ERROR, - (errmsg("failed to retrieve principal key from keyring provider :\"%s\"", new_keyring->provider_name), - errdetail("Error code: %d", kr_ret))); + errmsg("failed to retrieve principal key from keyring provider :\"%s\"", new_keyring->provider_name), + errdetail("Error code: %d", kr_ret)); } } if (keyInfo != NULL && ensure_new_key) { ereport(ERROR, - (errmsg("failed to create principal key: already exists"))); + errmsg("failed to create principal key: already exists")); } if (strlen(key_name) >= sizeof(keyInfo->name)) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long principal key name, maximum lenght is %ld bytes", sizeof(keyInfo->name) - 1))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("too long principal key name, maximum lenght is %ld bytes", sizeof(keyInfo->name) - 1)); if (keyInfo == NULL) keyInfo = KeyringGenerateNewKeyAndStore(new_keyring, key_name, PRINCIPAL_KEY_LEN); if (keyInfo == NULL) { - ereport(ERROR, - (errmsg("failed to retrieve/create principal key."))); + ereport(ERROR, errmsg("failed to retrieve/create principal key.")); } new_principal_key = palloc_object(TDEPrincipalKey); @@ -456,7 +455,7 @@ principal_key_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, if (tde_tbl_count > 0) { ereport(WARNING, - (errmsg("Failed to perform initialization. database already has %d TDE tables", tde_tbl_count))); + errmsg("Failed to perform initialization. database already has %d TDE tables", tde_tbl_count)); return; } @@ -540,7 +539,7 @@ pg_tde_set_principal_key_internal(char *key_name, enum global_status global, cha TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; - ereport(LOG, (errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name))); + ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); if (global == GS_GLOBAL) /* using a global provider for the current * database */ @@ -641,16 +640,16 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context that cannot accept type record"))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context that cannot accept type record")); LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); principal_key = GetPrincipalKeyNoDefault(dbOid, LW_SHARED); if (principal_key == NULL) { ereport(ERROR, - (errmsg("Principal key does not exists for the database"), - errhint("Use set_key interface to set the principal key"))); + errmsg("Principal key does not exists for the database"), + errhint("Use set_key interface to set the principal key")); } keyring = GetKeyProviderByID(principal_key->keyInfo.keyringId, principal_key->keyInfo.databaseId); @@ -718,16 +717,16 @@ get_principal_key_from_keyring(Oid dbOid) keyring = GetKeyProviderByID(principalKeyInfo->data.keyringId, dbOid); if (keyring == NULL) ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("keyring lookup failed for principal key %s, unknown keyring with ID %d", - principalKeyInfo->data.name, principalKeyInfo->data.keyringId))); + errcode(ERRCODE_DATA_CORRUPTED), + errmsg("keyring lookup failed for principal key %s, unknown keyring with ID %d", + principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); keyInfo = KeyringGetKey(keyring, principalKeyInfo->data.name, &keyring_ret); if (keyInfo == NULL) ereport(ERROR, - (errcode(ERRCODE_NO_DATA_FOUND), - errmsg("failed to retrieve principal key %s from keyring with ID %d", - principalKeyInfo->data.name, principalKeyInfo->data.keyringId))); + errcode(ERRCODE_NO_DATA_FOUND), + errmsg("failed to retrieve principal key %s from keyring with ID %d", + principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); principalKey = palloc_object(TDEPrincipalKey); @@ -739,9 +738,9 @@ get_principal_key_from_keyring(Oid dbOid) if (!pg_tde_verify_principal_key_info(principalKeyInfo, principalKey)) ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file", - principalKeyInfo->data.name))); + errcode(ERRCODE_DATA_CORRUPTED), + errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file", + principalKeyInfo->data.name)); pfree(keyInfo); pfree(keyring); @@ -1052,8 +1051,7 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global) if (provider == NULL) { - ereport(ERROR, - (errmsg("Keyring provider not found"))); + ereport(ERROR, errmsg("Keyring provider not found")); } provider_id = provider->keyring_id; @@ -1064,7 +1062,7 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global) if (provider_used) { ereport(ERROR, - (errmsg("Can't delete a provider which is currently in use"))); + errmsg("Can't delete a provider which is currently in use")); } delete_key_provider_info(provider_id, db_oid, true); @@ -1086,13 +1084,13 @@ pg_tde_verify_principal_key_internal(Oid databaseOid) if (fromKeyring == NULL) { ereport(ERROR, - (errmsg("principal key not configured for current database"))); + errmsg("principal key not configured for current database")); } if (fromCache != NULL && (fromKeyring->keyLength != fromCache->keyLength || memcmp(fromKeyring->keyData, fromCache->keyData, fromCache->keyLength) != 0)) { ereport(ERROR, - (errmsg("key returned from keyring and cached in pg_tde differ"))); + errmsg("key returned from keyring and cached in pg_tde differ")); } LWLockRelease(tde_lwlock_enc_keys()); diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c index 9b511437bbf12..37ad019a5f459 100644 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ b/contrib/pg_tde/src/common/pg_tde_shmem.c @@ -77,7 +77,7 @@ TdeShmemInit(void) LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); /* Create or attach to the shared memory state */ - ereport(NOTICE, (errmsg("TdeShmemInit: requested %ld bytes", required_shmem_size))); + ereport(NOTICE, errmsg("TdeShmemInit: requested %ld bytes", required_shmem_size)); tdeState = ShmemInitStruct("pg_tde", required_shmem_size, &found); if (!found) @@ -110,7 +110,7 @@ TdeShmemInit(void) Assert(dsa_area_size > 0); tdeState->rawDsaArea = p; - ereport(LOG, (errmsg("creating DSA area of size %lu", dsa_area_size))); + ereport(LOG, errmsg("creating DSA area of size %lu", dsa_area_size)); dsa = dsa_create_in_place(tdeState->rawDsaArea, dsa_area_size, LWLockNewTrancheId(), 0); @@ -125,7 +125,7 @@ TdeShmemInit(void) if (routine->init_dsa_area_objects) routine->init_dsa_area_objects(dsa, tdeState->rawDsaArea); } - ereport(LOG, (errmsg("setting no limit to DSA area of size %lu", dsa_area_size))); + ereport(LOG, errmsg("setting no limit to DSA area of size %lu", dsa_area_size)); dsa_set_size_limit(dsa, -1); /* Let it grow outside the shared * memory */ diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 598c33d2ff6af..5067a8d04816e 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -47,8 +47,8 @@ pg_tde_is_encrypted(PG_FUNCTION_ARGS) if (RelFileLocatorBackendIsTemp(rlocator) && !rel->rd_islocaltemp) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("we cannot check if temporary relations from other backends are encrypted"))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("we cannot check if temporary relations from other backends are encrypted")); key = GetSMGRRelationKey(rlocator); diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index f450c317057f9..d878c6260d036 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -75,14 +75,14 @@ AesRunCtr(EVP_CIPHER_CTX **ctxPtr, int enc, const unsigned char *key, const unsi if (EVP_CipherInit_ex(*ctxPtr, cipher_ctr_ecb, NULL, key, iv, enc) == 0) ereport(ERROR, - (errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); EVP_CIPHER_CTX_set_padding(*ctxPtr, 0); } if (EVP_CipherUpdate(*ctxPtr, out, &out_len, in, in_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); Assert(out_len == in_len); } @@ -101,17 +101,17 @@ AesRunCbc(int enc, const unsigned char *key, const unsigned char *iv, const unsi if (EVP_CipherInit_ex(ctx, cipher_cbc, NULL, key, iv, enc) == 0) ereport(ERROR, - (errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); EVP_CIPHER_CTX_set_padding(ctx, 0); if (EVP_CipherUpdate(ctx, out, &out_len, in, in_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len_final) == 0) ereport(ERROR, - (errmsg("EVP_CipherFinal_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherFinal_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); /* * We encrypt one block (16 bytes) Our expectation is that the result diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index f4779037a207d..aafb3adb541b3 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -53,8 +53,8 @@ pg_tde_crypt_simple(const char *iv_prefix, uint32 start_offset, const char *data iv_prefix_debug(iv_prefix, ivp_debug); ereport(LOG, - (errmsg("%s: Start offset: %lu Data_Len: %u, aes_start_block: %lu, aes_end_block: %lu, IV prefix: %s", - context ? context : "", start_offset, data_len, aes_start_block, aes_end_block, ivp_debug))); + errmsg("%s: Start offset: %lu Data_Len: %u, aes_start_block: %lu, aes_end_block: %lu, IV prefix: %s", + context ? context : "", start_offset, data_len, aes_start_block, aes_end_block, ivp_debug)); } #endif @@ -94,8 +94,8 @@ pg_tde_crypt_complex(const char *iv_prefix, uint32 start_offset, const char *dat iv_prefix_debug(iv_prefix, ivp_debug); ereport(LOG, - (errmsg("%s: Batch-No:%d Start offset: %lu Data_Len: %u, batch_start_block: %lu, batch_end_block: %lu, IV prefix: %s", - context ? context : "", batch_no, start_offset, data_len, batch_start_block, batch_end_block, ivp_debug))); + errmsg("%s: Batch-No:%d Start offset: %lu Data_Len: %u, batch_start_block: %lu, batch_end_block: %lu, IV prefix: %s", + context ? context : "", batch_no, start_offset, data_len, batch_start_block, batch_end_block, ivp_debug)); } #endif diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index ecaaae73d6a06..e80372065d0d1 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -78,10 +78,10 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode /* Corrupt file */ *return_code = KEYRING_CODE_DATA_CORRUPTED; ereport(WARNING, - (errcode_for_file_access(), - errmsg("keyring file \"%s\" is corrupted: %m", - file_keyring->file_name), - errdetail("invalid key size %lu expected %lu", bytes_read, sizeof(KeyInfo)))); + errcode_for_file_access(), + errmsg("keyring file \"%s\" is corrupted: %m", + file_keyring->file_name), + errdetail("invalid key size %lu expected %lu", bytes_read, sizeof(KeyInfo))); return NULL; } if (strncasecmp(key->name, key_name, sizeof(key->name)) == 0) @@ -111,15 +111,15 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) if (existing_key) { ereport(ERROR, - (errmsg("Key with name %s already exists in keyring", key->name))); + errmsg("Key with name %s already exists in keyring", key->name)); } fd = BasicOpenFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); if (fd < 0) { ereport(ERROR, - (errcode_for_file_access(), - errmsg("Failed to open keyring file %s :%m", file_keyring->file_name))); + errcode_for_file_access(), + errmsg("Failed to open keyring file %s :%m", file_keyring->file_name)); } /* Write key to the end of file */ curr_pos = lseek(fd, 0, SEEK_END); @@ -128,18 +128,18 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) { close(fd); ereport(ERROR, - (errcode_for_file_access(), - errmsg("keyring file \"%s\" can't be written: %m", - file_keyring->file_name))); + errcode_for_file_access(), + errmsg("keyring file \"%s\" can't be written: %m", + file_keyring->file_name)); } if (pg_fsync(fd) != 0) { close(fd); ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", - file_keyring->file_name))); + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", + file_keyring->file_name)); } close(fd); } diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index d360f003f5055..fbceedd223cda 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -55,21 +55,21 @@ kmipSslConnect(KmipCtx *ctx, KmipKeyring *kmip_keyring, bool throw_error) if (SSL_CTX_use_certificate_file(ctx->ssl, kmip_keyring->kmip_cert_path, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx->ssl); - ereport(level, (errmsg("SSL error: Loading the client certificate failed"))); + ereport(level, errmsg("SSL error: Loading the client certificate failed")); return false; } if (SSL_CTX_use_PrivateKey_file(ctx->ssl, kmip_keyring->kmip_cert_path, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx->ssl); - ereport(level, (errmsg("SSL error: Loading the client key failed"))); + ereport(level, errmsg("SSL error: Loading the client key failed")); return false; } if (SSL_CTX_load_verify_locations(ctx->ssl, kmip_keyring->kmip_ca_path, NULL) != 1) { SSL_CTX_free(ctx->ssl); - ereport(level, (errmsg("SSL error: Loading the CA certificate failed"))); + ereport(level, errmsg("SSL error: Loading the CA certificate failed")); return false; } @@ -77,7 +77,7 @@ kmipSslConnect(KmipCtx *ctx, KmipKeyring *kmip_keyring, bool throw_error) if (ctx->bio == NULL) { SSL_CTX_free(ctx->ssl); - ereport(level, (errmsg("SSL error: BIO_new_ssl_connect failed"))); + ereport(level, errmsg("SSL error: BIO_new_ssl_connect failed")); return false; } @@ -89,7 +89,7 @@ kmipSslConnect(KmipCtx *ctx, KmipKeyring *kmip_keyring, bool throw_error) { BIO_free_all(ctx->bio); SSL_CTX_free(ctx->ssl); - ereport(level, (errmsg("SSL error: BIO_do_connect failed"))); + ereport(level, errmsg("SSL error: BIO_do_connect failed")); return false; } @@ -113,7 +113,7 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) SSL_CTX_free(ctx.ssl); if (result != 0) - ereport(ERROR, (errmsg("KMIP server reported error on register symmetric key: %i", result))); + ereport(ERROR, errmsg("KMIP server reported error on register symmetric key: %i", result)); } static KeyInfo * @@ -156,7 +156,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (ids_found > 1) { - ereport(WARNING, (errmsg("KMIP server contains multiple results for key, ignoring"))); + ereport(WARNING, errmsg("KMIP server contains multiple results for key, ignoring")); *return_code = KEYRING_CODE_RESOURCE_NOT_AVAILABLE; BIO_free_all(ctx.bio); SSL_CTX_free(ctx.ssl); @@ -174,7 +174,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (result != 0) { - ereport(WARNING, (errmsg("KMIP server LOCATEd key, but GET failed with %i", result))); + ereport(WARNING, errmsg("KMIP server LOCATEd key, but GET failed with %i", result)); *return_code = KEYRING_CODE_RESOURCE_NOT_AVAILABLE; pfree(key); BIO_free_all(ctx.bio); @@ -184,7 +184,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (key->data.len > sizeof(key->data.data)) { - ereport(WARNING, (errmsg("keyring provider returned invalid key size: %d", key->data.len))); + ereport(WARNING, errmsg("keyring provider returned invalid key size: %d", key->data.len)); *return_code = KEYRING_CODE_INVALID_KEY_SIZE; pfree(key); BIO_free_all(ctx.bio); diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 0a53677c5eff1..c302e5d550b72 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -197,8 +197,8 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) if (!curl_perform(vault_keyring, url, &str, &httpCode, jsonText)) { ereport(ERROR, - (errmsg("HTTP(S) request to keyring provider \"%s\" failed", - vault_keyring->keyring.provider_name))); + errmsg("HTTP(S) request to keyring provider \"%s\" failed", + vault_keyring->keyring.provider_name)); } if (str.ptr != NULL) @@ -206,8 +206,8 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) if (httpCode / 100 != 2) ereport(ERROR, - (errmsg("Invalid HTTP response from keyring provider \"%s\": %ld", - vault_keyring->keyring.provider_name, httpCode))); + errmsg("Invalid HTTP response from keyring provider \"%s\": %ld", + vault_keyring->keyring.provider_name, httpCode)); } static KeyInfo * @@ -232,8 +232,8 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode { *return_code = KEYRING_CODE_INVALID_KEY_SIZE; ereport(WARNING, - (errmsg("HTTP(S) request to keyring provider \"%s\" failed", - vault_keyring->keyring.provider_name))); + errmsg("HTTP(S) request to keyring provider \"%s\" failed", + vault_keyring->keyring.provider_name)); goto cleanup; } @@ -247,8 +247,8 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode { *return_code = KEYRING_CODE_INVALID_RESPONSE; ereport(WARNING, - (errmsg("HTTP(S) request to keyring provider \"%s\" returned invalid response %li", - vault_keyring->keyring.provider_name, httpCode))); + errmsg("HTTP(S) request to keyring provider \"%s\" returned invalid response %li", + vault_keyring->keyring.provider_name, httpCode)); goto cleanup; } @@ -263,8 +263,8 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode { *return_code = KEYRING_CODE_INVALID_RESPONSE; ereport(WARNING, - (errmsg("HTTP(S) request to keyring provider \"%s\" returned incorrect JSON: %s", - vault_keyring->keyring.provider_name, json_errdetail(json_error, jlex)))); + errmsg("HTTP(S) request to keyring provider \"%s\" returned incorrect JSON: %s", + vault_keyring->keyring.provider_name, json_errdetail(json_error, jlex))); goto cleanup; } @@ -283,8 +283,8 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode { *return_code = KEYRING_CODE_INVALID_KEY_SIZE; ereport(WARNING, - (errmsg("keyring provider \"%s\" returned invalid key size: %d", - vault_keyring->keyring.provider_name, key->data.len))); + errmsg("keyring provider \"%s\" returned invalid key size: %d", + vault_keyring->keyring.provider_name, key->data.len)); pfree(key); key = NULL; goto cleanup; diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index a6db0811449ca..e37e67abcd18f 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -79,7 +79,7 @@ tde_shmem_request(void) prev_shmem_request_hook(); RequestAddinShmemSpace(sz); RequestNamedLWLockTranche(TDE_TRANCHE_NAME, required_locks); - ereport(LOG, (errmsg("tde_shmem_request: requested %ld bytes", sz))); + ereport(LOG, errmsg("tde_shmem_request: requested %ld bytes", sz)); } static void @@ -171,8 +171,8 @@ on_ext_install(pg_tde_on_ext_install_callback function, void *arg) { if (on_ext_install_index >= MAX_ON_INSTALLS) ereport(FATAL, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg_internal("out of on extension install slots"))); + errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg_internal("out of on extension install slots")); on_ext_install_list[on_ext_install_index].function = function; on_ext_install_list[on_ext_install_index].arg = arg; @@ -190,9 +190,9 @@ pg_tde_init_data_dir(void) { if (MakePGDirectory(PG_TDE_DATA_DIR) < 0) ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not create tde directory \"%s\": %m", - PG_TDE_DATA_DIR))); + errcode_for_file_access(), + errmsg("could not create tde directory \"%s\": %m", + PG_TDE_DATA_DIR)); } } diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 4c55178976525..8b115307c836a 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -63,15 +63,15 @@ checkEncryptionClause(const char *accessMethod) if (!pg_tde_principal_key_configured(MyDatabaseId)) { ereport(ERROR, - (errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables"))); + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); } } if (EnforceEncryption && !tdeCurrentCreateEvent.encryptMode) { ereport(ERROR, - (errmsg("pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed."))); + errmsg("pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed.")); } } @@ -113,7 +113,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) /* Ensure this function is being called as an event trigger */ if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ ereport(ERROR, - (errmsg("Function can only be fired by event trigger manager"))); + errmsg("Function can only be fired by event trigger manager")); trigdata = (EventTriggerData *) fcinfo->context; parsetree = trigdata->parsetree; @@ -148,7 +148,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) } } else - ereport(DEBUG1, (errmsg("Failed to get relation Oid for relation:%s", stmt->relation->relname))); + ereport(DEBUG1, errmsg("Failed to get relation Oid for relation:%s", stmt->relation->relname)); } else if (IsA(parsetree, CreateStmt)) @@ -267,7 +267,7 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) /* Ensure this function is being called as an event trigger */ if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ ereport(ERROR, - (errmsg("Function can only be fired by event trigger manager"))); + errmsg("Function can only be fired by event trigger manager")); if (IsA(parsetree, AlterTableStmt) && tdeCurrentCreateEvent.alterAccessMethodMode) { diff --git a/contrib/pg_tde/src/transam/pg_tde_xact_handler.c b/contrib/pg_tde/src/transam/pg_tde_xact_handler.c index 3e7a8f2385c3f..3f0a84df69f9e 100644 --- a/contrib/pg_tde/src/transam/pg_tde_xact_handler.c +++ b/contrib/pg_tde/src/transam/pg_tde_xact_handler.c @@ -41,8 +41,7 @@ pg_tde_xact_callback(XactEvent event, void *arg) if (event == XACT_EVENT_PARALLEL_ABORT || event == XACT_EVENT_ABORT) { - ereport(DEBUG2, - (errmsg("pg_tde_xact_callback: aborting transaction"))); + ereport(DEBUG2, errmsg("pg_tde_xact_callback: aborting transaction")); do_pending_deletes(false); } else if (event == XACT_EVENT_COMMIT) @@ -64,13 +63,13 @@ pg_tde_subxact_callback(SubXactEvent event, SubTransactionId mySubid, if (event == SUBXACT_EVENT_ABORT_SUB) { ereport(DEBUG2, - (errmsg("pg_tde_subxact_callback: aborting subtransaction"))); + errmsg("pg_tde_subxact_callback: aborting subtransaction")); do_pending_deletes(false); } else if (event == SUBXACT_EVENT_COMMIT_SUB) { ereport(DEBUG2, - (errmsg("pg_tde_subxact_callback: committing subtransaction"))); + errmsg("pg_tde_subxact_callback: committing subtransaction")); reassign_pending_deletes_to_parent_xact(); } } @@ -131,8 +130,8 @@ do_pending_deletes(bool isCommit) if (pending->atCommit == isCommit) { ereport(LOG, - (errmsg("pg_tde_xact_callback: deleting entry at offset %d", - (int) (pending->map_entry_offset)))); + errmsg("pg_tde_xact_callback: deleting entry at offset %d", + (int) (pending->map_entry_offset))); pg_tde_free_key_map_entry(&pending->rlocator, pending->map_entry_offset); } pfree(pending); From 4c3b0f8dfbf15f5f1f06d8531cc065ef4ef20d89 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sun, 23 Mar 2025 11:01:58 +0100 Subject: [PATCH 027/796] PG-1480 Add regression tests for default key rotation We had a bug in the default key roitation which was fixed in commit cb06bea2537a7e9d354aeac0ddb24b3cddc4530f but that commit did not add any regression test so let's add one where we clear the buffer cache to make sure we can read the internal keys and use them to decrypt the table data. --- .../pg_tde/expected/default_principal_key.out | 32 +++++++++++++++++++ .../expected/default_principal_key_1.out | 32 +++++++++++++++++++ contrib/pg_tde/sql/default_principal_key.sql | 13 ++++++++ 3 files changed, 77 insertions(+) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index f77ba113959d6..75c64ffc02235 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -1,4 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- @@ -46,6 +47,7 @@ SELECT current_database() AS regress_database CREATE DATABASE regress_pg_tde_other; \c regress_pg_tde_other CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); @@ -67,6 +69,7 @@ SELECT key_provider_id, key_provider_name, key_name (1 row) \c :regress_database +CHECKPOINT; SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- @@ -88,9 +91,38 @@ SELECT key_provider_id, key_provider_name, key_name -3 | file-provider | new-default-key (1 row) +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + pg_buffercache_evict +---------------------- + t +(1 row) + +SELECT * FROM test_enc; + id | k +----+--- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; \c :regress_database +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + pg_buffercache_evict +---------------------- + t +(1 row) + +SELECT * FROM test_enc; + id | k +----+--- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; +DROP EXTENSION pg_buffercache; DROP DATABASE regress_pg_tde_other; diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out index 871fdd9cbd849..589b9f81228cc 100644 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ b/contrib/pg_tde/expected/default_principal_key_1.out @@ -1,4 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- @@ -47,6 +48,7 @@ SELECT current_database() AS regress_database CREATE DATABASE regress_pg_tde_other; \c regress_pg_tde_other CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); @@ -68,6 +70,7 @@ SELECT key_provider_id, key_provider_name, key_name (1 row) \c :regress_database +CHECKPOINT; SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- @@ -89,9 +92,38 @@ SELECT key_provider_id, key_provider_name, key_name -4 | file-provider | new-default-key (1 row) +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + pg_buffercache_evict +---------------------- + t +(1 row) + +SELECT * FROM test_enc; + id | k +----+--- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; \c :regress_database +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + pg_buffercache_evict +---------------------- + t +(1 row) + +SELECT * FROM test_enc; + id | k +----+--- + 1 | 1 + 2 | 2 + 3 | 3 +(3 rows) + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; +DROP EXTENSION pg_buffercache; DROP DATABASE regress_pg_tde_other; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 37adab9418bb7..e9c5a67a89a04 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -1,4 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); @@ -33,6 +34,7 @@ CREATE DATABASE regress_pg_tde_other; \c regress_pg_tde_other CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name @@ -53,6 +55,8 @@ SELECT key_provider_id, key_provider_name, key_name \c :regress_database +CHECKPOINT; + SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); SELECT key_provider_id, key_provider_name, key_name @@ -63,14 +67,23 @@ SELECT key_provider_id, key_provider_name, key_name SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + +SELECT * FROM test_enc; + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; \c :regress_database +SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); + +SELECT * FROM test_enc; + DROP TABLE test_enc; DROP EXTENSION pg_tde CASCADE; +DROP EXTENSION pg_buffercache; DROP DATABASE regress_pg_tde_other; From 065409d87c9404b74642bcf153627678dcb76b1b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 4 Apr 2025 14:45:28 +0200 Subject: [PATCH 028/796] PG-1437 Remove TODO about tests for changing key provider type Sinc we want to add validation for that the principal keys still all are there some simple test would not suffice so let's instead wait until we do that to write propoer tests for this. --- contrib/pg_tde/expected/key_provider.out | 1 - contrib/pg_tde/expected/key_provider_1.out | 1 - contrib/pg_tde/sql/key_provider.sql | 3 --- 3 files changed, 5 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 2725b4e108aa1..733efc46cd321 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -101,7 +101,6 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -2 | file-keyring2 (2 rows) --- TODO: verify that we can also can change the type of it -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out index 39005687d6765..3463d6a384f5c 100644 --- a/contrib/pg_tde/expected/key_provider_1.out +++ b/contrib/pg_tde/expected/key_provider_1.out @@ -102,7 +102,6 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -3 | file-keyring2 (3 rows) --- TODO: verify that we can also can change the type of it -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index ad0bb048d4af6..76eb036850a07 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -32,8 +32,6 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_key SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); --- TODO: verify that we can also can change the type of it - -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); @@ -55,4 +53,3 @@ SELECT pg_tde_delete_global_key_provider('file-keyring2'); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); DROP EXTENSION pg_tde; - From c717b69727278b81781fde68d1b0b9e6d00fcf30 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 31 Mar 2025 20:33:36 +0200 Subject: [PATCH 029/796] PG-1437 Clean up frontend and backend sources in meson.build While the frontend sources right now are a strict subset of the backend sources that might not always be the case so keep them as separate lists but clean them up. In the long term we want to refactor this entirely anyway so let's not overcomplicate this. --- contrib/pg_tde/meson.build | 87 ++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 70c8cbc52637c..2720e0f50c81c 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -1,34 +1,45 @@ - curldep = dependency('libcurl') pg_tde_sources = files( - 'src/pg_tde.c', - 'src/transam/pg_tde_xact_handler.c', - 'src/access/pg_tde_tdemap.c', - 'src/access/pg_tde_xlog.c', - 'src/access/pg_tde_xlog_encrypt.c', - - 'src/encryption/enc_tde.c', - 'src/encryption/enc_aes.c', - - 'src/keyring/keyring_curl.c', - 'src/keyring/keyring_file.c', - 'src/keyring/keyring_vault.c', - 'src/keyring/keyring_kmip.c', - 'src/keyring/keyring_kmip_impl.c', - 'src/keyring/keyring_api.c', - - 'src/smgr/pg_tde_smgr.c', - - 'src/catalog/tde_keyring.c', - 'src/catalog/tde_keyring_parse_opts.c', - 'src/catalog/tde_principal_key.c', - 'src/common/pg_tde_shmem.c', - 'src/common/pg_tde_utils.c', - 'src/pg_tde_defs.c', - 'src/pg_tde_event_capture.c', - 'src/pg_tde_guc.c', - 'src/pg_tde.c', + 'src/access/pg_tde_tdemap.c', + 'src/access/pg_tde_xlog.c', + 'src/access/pg_tde_xlog_encrypt.c', + 'src/catalog/tde_keyring.c', + 'src/catalog/tde_keyring_parse_opts.c', + 'src/catalog/tde_principal_key.c', + 'src/common/pg_tde_shmem.c', + 'src/common/pg_tde_utils.c', + 'src/encryption/enc_aes.c', + 'src/encryption/enc_tde.c', + 'src/keyring/keyring_api.c', + 'src/keyring/keyring_curl.c', + 'src/keyring/keyring_file.c', + 'src/keyring/keyring_kmip.c', + 'src/keyring/keyring_kmip_impl.c', + 'src/keyring/keyring_vault.c', + 'src/pg_tde.c', + 'src/pg_tde_defs.c', + 'src/pg_tde_event_capture.c', + 'src/pg_tde_guc.c', + 'src/smgr/pg_tde_smgr.c', + 'src/transam/pg_tde_xact_handler.c', +) + +tde_frontend_sources = files( + 'src/access/pg_tde_tdemap.c', + 'src/access/pg_tde_xlog_encrypt.c', + 'src/catalog/tde_keyring.c', + 'src/catalog/tde_keyring_parse_opts.c', + 'src/catalog/tde_principal_key.c', + 'src/common/pg_tde_utils.c', + 'src/encryption/enc_aes.c', + 'src/encryption/enc_tde.c', + 'src/keyring/keyring_api.c', + 'src/keyring/keyring_curl.c', + 'src/keyring/keyring_file.c', + 'src/keyring/keyring_kmip.c', + 'src/keyring/keyring_kmip_impl.c', + 'src/keyring/keyring_vault.c', ) incdir = include_directories('src/include', '.', 'src/libkmip/libkmip/include/') @@ -118,28 +129,10 @@ tests += { 'tests': tap_tests }, } -# TODO: do not duplicate -tde_decrypt_sources = files( - 'src/access/pg_tde_tdemap.c', - 'src/access/pg_tde_xlog_encrypt.c', - 'src/catalog/tde_keyring.c', - 'src/catalog/tde_keyring_parse_opts.c', - 'src/catalog/tde_principal_key.c', - 'src/common/pg_tde_utils.c', - 'src/encryption/enc_aes.c', - 'src/encryption/enc_tde.c', - 'src/keyring/keyring_api.c', - 'src/keyring/keyring_curl.c', - 'src/keyring/keyring_file.c', - 'src/keyring/keyring_vault.c', - 'src/keyring/keyring_kmip.c', - 'src/keyring/keyring_kmip_impl.c', - ) - pg_tde_inc = incdir pg_tde_frontend = static_library('pg_tde_frontend', - tde_decrypt_sources, + tde_frontend_sources, c_pch: pch_postgres_h, c_args: ['-DFRONTEND'], kwargs: mod_args, From a590cf0857087a29e9eb72724d55cc3abf24d98f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 2 Apr 2025 00:17:02 +0200 Subject: [PATCH 030/796] Fix indentation in pg_tde/meson.build --- contrib/pg_tde/meson.build | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 2720e0f50c81c..df587f4060750 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -46,12 +46,12 @@ incdir = include_directories('src/include', '.', 'src/libkmip/libkmip/include/') kmip = static_library( 'kmip', - files( - 'src/libkmip/libkmip/src/kmip.c', - 'src/libkmip/libkmip/src/kmip_bio.c', - 'src/libkmip/libkmip/src/kmip_locate.c', - 'src/libkmip/libkmip/src/kmip_memset.c' - ), + files( + 'src/libkmip/libkmip/src/kmip.c', + 'src/libkmip/libkmip/src/kmip_bio.c', + 'src/libkmip/libkmip/src/kmip_locate.c', + 'src/libkmip/libkmip/src/kmip_memset.c' + ), c_args: [ '-w' ], # This is a 3rd party, disable warnings completely include_directories: incdir ) @@ -85,35 +85,35 @@ install_data( # toast_descrypt needs to be the first test when running with pg_tde # preinstalled and default_principal_key needs to run after key_provider. sql_tests = [ - 'toast_decrypt', - 'access_control', - 'alter_index', - 'cache_alloc', - 'change_access_method', - 'insert_update_delete', - 'key_provider', - 'keyprovider_dependency', - 'kmip_test', - 'pg_tde_is_encrypted', - 'relocate', - 'recreate_storage', - 'subtransaction', - 'tablespace', - 'vault_v2_test', - 'default_principal_key', + 'toast_decrypt', + 'access_control', + 'alter_index', + 'cache_alloc', + 'change_access_method', + 'insert_update_delete', + 'key_provider', + 'keyprovider_dependency', + 'kmip_test', + 'pg_tde_is_encrypted', + 'relocate', + 'recreate_storage', + 'subtransaction', + 'tablespace', + 'vault_v2_test', + 'default_principal_key', ] tap_tests = [ - 't/001_basic.pl', - 't/002_rotate_key.pl', - 't/003_remote_config.pl', - 't/004_file_config.pl', - 't/005_multiple_extensions.pl', - 't/006_remote_vault_config.pl', - 't/007_tde_heap.pl', - 't/008_key_rotate_tablespace.pl', - 't/009_wal_encrypt.pl', - 't/010_change_key_provider.pl', + 't/001_basic.pl', + 't/002_rotate_key.pl', + 't/003_remote_config.pl', + 't/004_file_config.pl', + 't/005_multiple_extensions.pl', + 't/006_remote_vault_config.pl', + 't/007_tde_heap.pl', + 't/008_key_rotate_tablespace.pl', + 't/009_wal_encrypt.pl', + 't/010_change_key_provider.pl', ] tests += { From 93ef4510b44feaea20811f4a5030542f75c4a8c3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 11:19:10 +0200 Subject: [PATCH 031/796] Fix typo in comment of Makefile/meson.build --- contrib/pg_tde/Makefile | 2 +- contrib/pg_tde/meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 105f0523a4a77..cd206b877de59 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -6,7 +6,7 @@ EXTENSION = pg_tde DATA = pg_tde--1.0-rc.sql REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf -# toast_descrypt needs to be the first test when running with pg_tde +# toast_decrypt needs to be the first test when running with pg_tde # preinstalled and default_principal_key needs to run after key_provider. REGRESS = toast_decrypt \ access_control \ diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index df587f4060750..17974dcc05b79 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -82,7 +82,7 @@ install_data( kwargs: contrib_data_args, ) -# toast_descrypt needs to be the first test when running with pg_tde +# toast_decrypt needs to be the first test when running with pg_tde # preinstalled and default_principal_key needs to run after key_provider. sql_tests = [ 'toast_decrypt', From 19bef897e6afc06959a99ed697575ba3eb71b0f9 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 15:54:51 +0200 Subject: [PATCH 032/796] PG-1510 Use a unique IV per relation fork The security of the encryption is reduced if we reuse the same initiation vector more than necessary so we make sure to use a unique IV per relation fork, with the exception of the initialization fork which is used by unlogged indexes when restarting the server after a crash. It is copied with low-level file system functions to the main fork on crash recovery so it needs to use the same IV as the main fork. The init fork issue is in no way more a security issue than to the extent that ideally we should pick a new IV when truncating unlogged tables on crash recovery but to fix this we would need to change the SMGR API and moving the copying of the intialization fork into that. And in the long term this might be what we want to do. --- contrib/pg_tde/meson.build | 1 + contrib/pg_tde/src/smgr/pg_tde_smgr.c | 16 +++-- contrib/pg_tde/t/011_unlogged_tables.pl | 69 +++++++++++++++++++ .../pg_tde/t/expected/011_unlogged_tables.out | 12 ++++ 4 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 contrib/pg_tde/t/011_unlogged_tables.pl create mode 100644 contrib/pg_tde/t/expected/011_unlogged_tables.out diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 17974dcc05b79..5a95a4c8ba7f0 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -114,6 +114,7 @@ tap_tests = [ 't/008_key_rotate_tablespace.pl', 't/009_wal_encrypt.pl', 't/010_change_key_provider.pl', + 't/011_unlogged_tables.pl', ] tests += { diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index fd7d20e709262..a83fb9ebda82a 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -26,7 +26,7 @@ typedef struct TDESMgrRelationData typedef TDESMgrRelationData *TDESMgrRelation; -static void CalcBlockIv(BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); +static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); static InternalKey * tde_smgr_get_key(SMgrRelation reln, RelFileLocator *old_locator, bool can_create) @@ -102,7 +102,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, local_buffers[i] = &local_blocks_aligned[i * BLCKSZ]; - CalcBlockIv(bn, int_key->base_iv, iv); + CalcBlockIv(forknum, bn, int_key->base_iv, iv); AesEncrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); } @@ -134,7 +134,7 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, AesInit(); - CalcBlockIv(blocknum, int_key->base_iv, iv); + CalcBlockIv(forknum, blocknum, int_key->base_iv, iv); AesEncrypt(int_key->key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); @@ -184,7 +184,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (allZero) continue; - CalcBlockIv(bn, int_key->base_iv, iv); + CalcBlockIv(forknum, bn, int_key->base_iv, iv); AesDecrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); } @@ -297,13 +297,17 @@ RegisterStorageMgr(void) /* * The intialization vector of a block is its block number conmverted to a - * 128 bit big endian number XOR the base IV of the relation file. + * 128 bit big endian number plus the forknumber XOR the base IV of the + * relation file. */ static void -CalcBlockIv(BlockNumber bn, const unsigned char *base_iv, unsigned char *iv) +CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv) { memset(iv, 0, 16); + /* The init fork is copied to the main fork so we must use the same IV */ + iv[7] = forknum == INIT_FORKNUM ? MAIN_FORKNUM : forknum; + iv[12] = bn >> 24; iv[13] = bn >> 16; iv[14] = bn >> 8; diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl new file mode 100644 index 0000000000000..22e7c5f10c680 --- /dev/null +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -0,0 +1,69 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use File::Compare; +use File::Copy; +use Test::More; +use lib 't'; +use pgtde; + +# Get file name and CREATE out file name and dirs WHERE requried +PGTDE::setup_files_dir(basename($0)); + +# CREATE new PostgreSQL node and do initdb +my $node = PGTDE->pgtde_init_pg(); +my $pgdata = $node->data_dir; + +# UPDATE postgresql.conf to include/load pg_tde library +open my $conf, '>>', "$pgdata/postgresql.conf"; +print $conf "shared_preload_libraries = 'pg_tde'\n"; +close $conf; + +# Start server +my $rt_value = $node->start; +ok($rt_value == 1, "Start Server"); + +my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); +ok($cmdret == 0, "CREATE PGTDE EXTENSION"); +PGTDE::append_to_file($stdout); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');", extra_params => ['-a']); +PGTDE::append_to_file($stdout); +$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "INSERT INTO t SELECT generate_series(1, 4);", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "CHECKPOINT;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +PGTDE::append_to_file("-- kill -9"); +$node->kill9(); + +# Start server +PGTDE::append_to_file("-- server start"); +$rt_value = $node->start; +ok($rt_value == 1, "Start Server"); + +$stdout = $node->safe_psql('postgres', "TABLE t;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', "INSERT INTO t SELECT generate_series(1, 4);", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +# Stop the server +$node->stop(); + +# compare the expected and out file +my $compare = PGTDE->compare_results(); + +# Test/check if expected and result/out file match. If Yes, test passes. +is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); + +# Done testing for this testcase file. +done_testing(); diff --git a/contrib/pg_tde/t/expected/011_unlogged_tables.out b/contrib/pg_tde/t/expected/011_unlogged_tables.out new file mode 100644 index 0000000000000..031fd4f80b24d --- /dev/null +++ b/contrib/pg_tde/t/expected/011_unlogged_tables.out @@ -0,0 +1,12 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + +CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap; +INSERT INTO t SELECT generate_series(1, 4); +CHECKPOINT; +-- kill -9 +-- server start +TABLE t; +INSERT INTO t SELECT generate_series(1, 4); From 5afe678b60c75a7a9bcc02fb43129a379cdb3f4d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 17:58:41 +0200 Subject: [PATCH 033/796] PG-1504 Do not abuse checkEncryptionClause() for just one feature The only part of the code which makes sense to call when we know the table is ecrypted is the check for if a principal key is configured. --- contrib/pg_tde/src/pg_tde_event_capture.c | 29 +++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 8b115307c836a..1f13c677a1476 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -46,6 +46,15 @@ GetCurrentTdeCreateEvent(void) return &tdeCurrentCreateEvent; } +static void +checkPrincipalKeyConfigured(void) +{ + if (!pg_tde_principal_key_configured(MyDatabaseId)) + ereport(ERROR, + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); +} + static void checkEncryptionClause(const char *accessMethod) { @@ -53,22 +62,16 @@ checkEncryptionClause(const char *accessMethod) { tdeCurrentCreateEvent.encryptMode = true; } - else if ((accessMethod == NULL || accessMethod[0] == 0) && strcmp(default_table_access_method, "tde_heap") == 0) + else if (accessMethod == NULL && strcmp(default_table_access_method, "tde_heap") == 0) { tdeCurrentCreateEvent.encryptMode = true; } if (tdeCurrentCreateEvent.encryptMode) { - if (!pg_tde_principal_key_configured(MyDatabaseId)) - { - ereport(ERROR, - errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables")); - } + checkPrincipalKeyConfigured(); } - - if (EnforceEncryption && !tdeCurrentCreateEvent.encryptMode) + else if (EnforceEncryption) { ereport(ERROR, errmsg("pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed.")); @@ -143,9 +146,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) table_close(rel, NoLock); if (tdeCurrentCreateEvent.encryptMode) - { - checkEncryptionClause(""); - } + checkPrincipalKeyConfigured(); } else ereport(DEBUG1, errmsg("Failed to get relation Oid for relation:%s", stmt->relation->relname)); @@ -229,9 +230,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) relation_close(rel, NoLock); if (tdeCurrentCreateEvent.encryptMode) - { - checkEncryptionClause(""); - } + checkPrincipalKeyConfigured(); } } } From 9d22ae3f81e2c3e999f6449fd0fbc3039c33ae4f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 18:08:52 +0200 Subject: [PATCH 034/796] PG-1504 Remove side effects from checkEncryptionStatus() A function with that name should not have side effects so let's add a function which only checks if a table should be encrypted based on the AM. --- contrib/pg_tde/src/pg_tde_event_capture.c | 41 +++++++++++++---------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 1f13c677a1476..3a5bcbfc9b034 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -46,6 +46,15 @@ GetCurrentTdeCreateEvent(void) return &tdeCurrentCreateEvent; } +static bool +shouldEncryptTable(const char *accessMethod) +{ + if (accessMethod) + return strcmp(accessMethod, "tde_heap") == 0; + else + return strcmp(default_table_access_method, "tde_heap") == 0; +} + static void checkPrincipalKeyConfigured(void) { @@ -56,17 +65,8 @@ checkPrincipalKeyConfigured(void) } static void -checkEncryptionClause(const char *accessMethod) +checkEncryptionStatus(void) { - if (accessMethod && strcmp(accessMethod, "tde_heap") == 0) - { - tdeCurrentCreateEvent.encryptMode = true; - } - else if (accessMethod == NULL && strcmp(default_table_access_method, "tde_heap") == 0) - { - tdeCurrentCreateEvent.encryptMode = true; - } - if (tdeCurrentCreateEvent.encryptMode) { checkPrincipalKeyConfigured(); @@ -155,27 +155,30 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) else if (IsA(parsetree, CreateStmt)) { CreateStmt *stmt = (CreateStmt *) parsetree; - const char *accessMethod = stmt->accessMethod; validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - tdeCurrentCreateEvent.relation = stmt->relation; - checkEncryptionClause(accessMethod); + if (shouldEncryptTable(stmt->accessMethod)) + tdeCurrentCreateEvent.encryptMode = true; + + checkEncryptionStatus(); } else if (IsA(parsetree, CreateTableAsStmt)) { CreateTableAsStmt *stmt = (CreateTableAsStmt *) parsetree; - const char *accessMethod = stmt->into->accessMethod; validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); tdeCurrentCreateEvent.relation = stmt->into->rel; - checkEncryptionClause(accessMethod); + if (shouldEncryptTable(stmt->into->accessMethod)) + tdeCurrentCreateEvent.encryptMode = true; + + checkEncryptionStatus(); } else if (IsA(parsetree, AlterTableStmt)) { @@ -192,13 +195,15 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (cmd->subtype == AT_SetAccessMethod) { - const char *accessMethod = cmd->name; - tdeCurrentCreateEvent.relation = stmt->relation; tdeCurrentCreateEvent.baseTableOid = relationId; tdeCurrentCreateEvent.alterAccessMethodMode = true; - checkEncryptionClause(accessMethod); + if (shouldEncryptTable(cmd->name)) + tdeCurrentCreateEvent.encryptMode = true; + + checkEncryptionStatus(); + alterSetAccessMethod = true; } } From 71ce4c9157273a20bde37139eee6282c300d7e1e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 7 Apr 2025 19:56:23 +0200 Subject: [PATCH 035/796] PG-1504 Avoid early return where it does not improve reasbaility --- contrib/pg_tde/src/pg_tde_event_capture.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 3a5bcbfc9b034..6873989480995 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -86,9 +86,8 @@ validateCurrentEventTriggerState(bool mightStartTransaction) if (RecoveryInProgress()) { reset_current_tde_create_event(); - return; } - if (tdeCurrentCreateEvent.tid.value != InvalidFullTransactionId.value && tid.value != tdeCurrentCreateEvent.tid.value) + else if (tdeCurrentCreateEvent.tid.value != InvalidFullTransactionId.value && tid.value != tdeCurrentCreateEvent.tid.value) { /* There was a failed query, end event trigger didn't execute */ reset_current_tde_create_event(); From 090e356f4b3f1ba1ce2ee43322c2dc5983c4bd23 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 11 Apr 2025 15:39:16 +0200 Subject: [PATCH 036/796] PG-1504 Remove unused field in tdeCurrentCreateEvent --- contrib/pg_tde/src/pg_tde_event_capture.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 6873989480995..63ca173ac2191 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -31,7 +31,7 @@ #include "catalog/tde_global_space.h" /* Global variable that gets set at ddl start and cleard out at ddl end*/ -static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0},.relation = NULL}; +static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}}; static bool alterSetAccessMethod = false; static void reset_current_tde_create_event(void); @@ -129,7 +129,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); tdeCurrentCreateEvent.baseTableOid = relationId; - tdeCurrentCreateEvent.relation = stmt->relation; if (relationId != InvalidOid) { @@ -158,8 +157,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - tdeCurrentCreateEvent.relation = stmt->relation; - if (shouldEncryptTable(stmt->accessMethod)) tdeCurrentCreateEvent.encryptMode = true; @@ -172,8 +169,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - tdeCurrentCreateEvent.relation = stmt->into->rel; - if (shouldEncryptTable(stmt->into->accessMethod)) tdeCurrentCreateEvent.encryptMode = true; @@ -194,7 +189,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (cmd->subtype == AT_SetAccessMethod) { - tdeCurrentCreateEvent.relation = stmt->relation; tdeCurrentCreateEvent.baseTableOid = relationId; tdeCurrentCreateEvent.alterAccessMethodMode = true; @@ -216,7 +210,6 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) */ tdeCurrentCreateEvent.baseTableOid = relationId; - tdeCurrentCreateEvent.relation = stmt->relation; if (relationId != InvalidOid) { @@ -314,10 +307,9 @@ reset_current_tde_create_event(void) { tdeCurrentCreateEvent.encryptMode = false; tdeCurrentCreateEvent.baseTableOid = InvalidOid; - tdeCurrentCreateEvent.relation = NULL; tdeCurrentCreateEvent.tid = InvalidFullTransactionId; - alterSetAccessMethod = false; tdeCurrentCreateEvent.alterAccessMethodMode = false; + alterSetAccessMethod = false; } static Oid From 36b17e85792d60869e6a9ab3a7b520ba8c751333 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 12 Apr 2025 13:41:23 +0200 Subject: [PATCH 037/796] PG-1504 Use castNode() when casting nodes This gives us some extra protection against typos. --- contrib/pg_tde/src/pg_tde_event_capture.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 63ca173ac2191..d9a375e633712 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -117,12 +117,12 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) ereport(ERROR, errmsg("Function can only be fired by event trigger manager")); - trigdata = (EventTriggerData *) fcinfo->context; + trigdata = castNode(EventTriggerData, fcinfo->context); parsetree = trigdata->parsetree; if (IsA(parsetree, IndexStmt)) { - IndexStmt *stmt = (IndexStmt *) parsetree; + IndexStmt *stmt = castNode(IndexStmt, parsetree); Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); validateCurrentEventTriggerState(true); @@ -152,7 +152,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) } else if (IsA(parsetree, CreateStmt)) { - CreateStmt *stmt = (CreateStmt *) parsetree; + CreateStmt *stmt = castNode(CreateStmt, parsetree); validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); @@ -164,7 +164,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) } else if (IsA(parsetree, CreateTableAsStmt)) { - CreateTableAsStmt *stmt = (CreateTableAsStmt *) parsetree; + CreateTableAsStmt *stmt = castNode(CreateTableAsStmt, parsetree); validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); @@ -176,7 +176,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) } else if (IsA(parsetree, AlterTableStmt)) { - AlterTableStmt *stmt = (AlterTableStmt *) parsetree; + AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree); ListCell *lcmd; Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); @@ -185,7 +185,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) foreach(lcmd, stmt->cmds) { - AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); if (cmd->subtype == AT_SetAccessMethod) { @@ -257,14 +257,14 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) EventTriggerData *trigdata; Node *parsetree; - trigdata = (EventTriggerData *) fcinfo->context; - parsetree = trigdata->parsetree; - /* Ensure this function is being called as an event trigger */ if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ ereport(ERROR, errmsg("Function can only be fired by event trigger manager")); + trigdata = castNode(EventTriggerData, fcinfo->context); + parsetree = trigdata->parsetree; + if (IsA(parsetree, AlterTableStmt) && tdeCurrentCreateEvent.alterAccessMethodMode) { /* From 7587e44a091542c9e3bc439818c9b12b76c57065 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 12 Apr 2025 14:08:16 +0200 Subject: [PATCH 038/796] PG-1504 Return null pointer from event triggers According to to the manual we should return a null pointer, but it is contradicted by an example on the same page but let's follow what plpgsql does. An event trigger function must return a NULL pointer (not an SQL null value, that is, do not set isNull true). --- contrib/pg_tde/src/pg_tde_event_capture.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index d9a375e633712..b1fd2e28724e9 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -244,7 +244,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) reset_current_tde_create_event(); } } - PG_RETURN_NULL(); + + PG_RETURN_VOID(); } /* @@ -299,7 +300,7 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) reset_current_tde_create_event(); } - PG_RETURN_NULL(); + PG_RETURN_VOID(); } static void From 253c8a4a72791c014e6e57313e3de1deb18075d5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 11:17:13 +0200 Subject: [PATCH 039/796] PG-1504 Open the relation directly with the RangeVar Instead of first looking up the oid and then using that to open the relation we can use relation_openrv() with the RangeVar directly. --- contrib/pg_tde/src/pg_tde_event_capture.c | 33 +++++++++++------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index b1fd2e28724e9..d9cbe1ebf750d 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -123,32 +123,29 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (IsA(parsetree, IndexStmt)) { IndexStmt *stmt = castNode(IndexStmt, parsetree); - Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); + Relation rel; validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - tdeCurrentCreateEvent.baseTableOid = relationId; - - if (relationId != InvalidOid) - { - Relation rel = table_open(relationId, NoLock); - - if (rel->rd_rel->relam == get_tde_table_am_oid()) - { - /* We are creating the index on encrypted table */ - /* set the global state */ - tdeCurrentCreateEvent.encryptMode = true; - } + rel = table_openrv(stmt->relation, AccessShareLock); - table_close(rel, NoLock); + tdeCurrentCreateEvent.baseTableOid = rel->rd_id; - if (tdeCurrentCreateEvent.encryptMode) - checkPrincipalKeyConfigured(); + if (rel->rd_rel->relam == get_tde_table_am_oid()) + { + /* + * We are creating an index on an encrypted table so set the + * global state. + */ + tdeCurrentCreateEvent.encryptMode = true; } - else - ereport(DEBUG1, errmsg("Failed to get relation Oid for relation:%s", stmt->relation->relname)); + /* Hold on to lock until end of transaction */ + table_close(rel, NoLock); + + if (tdeCurrentCreateEvent.encryptMode) + checkPrincipalKeyConfigured(); } else if (IsA(parsetree, CreateStmt)) { From 06b671fc04cd36cf77fef5c9bdfe6a654a64ef37 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 11 Apr 2025 01:17:03 +0200 Subject: [PATCH 040/796] PG-1460 Always generate a new WAL key on server start Since you can take a copy of a PostgreSQL data directory and start both the old and the new version you could get two versions where the same encrypted counter is used for CTR which would mean we could comapre them and potentially decrypt the data. For this reason we need to generate a new WAL key every time we start the server. --- .../pg_tde/src/access/pg_tde_xlog_encrypt.c | 20 +++++++++++++------ contrib/pg_tde/t/009_wal_encrypt.pl | 11 ++++++++++ contrib/pg_tde/t/expected/009_wal_encrypt.out | 8 ++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index 16f6e9c531f1d..eaae1c87f24e6 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -192,19 +192,27 @@ TDEXLogSmgrInit(void) /* TODO: move to the separate func, it's not an SMGR init */ InternalKey *key = pg_tde_read_last_wal_key(); - /* TDOO: clean-up this mess */ - if ((!key && EncryptXLog) || (key && - ((key->rel_type & TDE_KEY_TYPE_WAL_ENCRYPTED && !EncryptXLog) || - (key->rel_type & TDE_KEY_TYPE_WAL_UNENCRYPTED && EncryptXLog)))) + /* + * Always generate a new key on starting PostgreSQL to protect against + * attacks on CTR ciphers based on comparing the WAL generated by two + * divergent copies of the same cluster. + */ + if (EncryptXLog) { pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), - (EncryptXLog ? TDE_KEY_TYPE_WAL_ENCRYPTED : TDE_KEY_TYPE_WAL_UNENCRYPTED)); + TDE_KEY_TYPE_WAL_ENCRYPTED); + } + else if (key && key->rel_type & TDE_KEY_TYPE_WAL_ENCRYPTED) + { + pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), + TDE_KEY_TYPE_WAL_UNENCRYPTED); + pfree(key); } else if (key) { EncryptionKey = *key; - pfree(key); pg_atomic_write_u64(&EncryptionState->enc_key_lsn, EncryptionKey.start_lsn); + pfree(key); } pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 481f83d85413b..88fece2e24d58 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -86,6 +86,17 @@ $stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);', extra_params => ['-a']); PGTDE::append_to_file($stdout); +PGTDE::append_to_file("-- server restart with still wal encryption"); +$node->stop(); +$rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); + +$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (7), (8);', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + $stdout = $node->safe_psql('postgres', "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);", extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index 64fb19fa21bc4..7d7c57268913d 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -21,6 +21,10 @@ ALTER SYSTEM SET pg_tde.wal_encrypt = on; SHOW pg_tde.wal_encrypt; on INSERT INTO test_wal (k) VALUES (5), (6); +-- server restart with still wal encryption +SHOW pg_tde.wal_encrypt; +on +INSERT INTO test_wal (k) VALUES (7), (8); SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL); BEGIN 739 COMMIT 739 @@ -36,6 +40,10 @@ BEGIN 742 table public.test_wal: INSERT: id[integer]:5 k[integer]:5 table public.test_wal: INSERT: id[integer]:6 k[integer]:6 COMMIT 742 +BEGIN 743 +table public.test_wal: INSERT: id[integer]:7 k[integer]:7 +table public.test_wal: INSERT: id[integer]:8 k[integer]:8 +COMMIT 743 SELECT pg_drop_replication_slot('tde_slot'); DROP EXTENSION pg_tde; From 92e40cdc38f6e98956cdde1d6293a3073f63688d Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoi Date: Tue, 15 Apr 2025 13:03:43 +0300 Subject: [PATCH 041/796] XLog signed info when creating a Principal Key Before this commit, we XLogged an unsigned PrincipalKey info when creating the key. Which leads to: 1. In case of crash recovery, the redo would rewrite a map_ file with an empty sign info. And the server would later fail to start with "Failed to verify principal key header..." 2. Replicas would create a _map file with an empty sign info. Which in turn leads to a fail on restart. For PG-1539 --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 9 ++++++++- contrib/pg_tde/src/catalog/tde_principal_key.c | 10 ++-------- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index c1b1324ecfe2f..38c370bb84c66 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -290,7 +290,7 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) * The caller must have an EXCLUSIVE LOCK on the files before calling this function. */ void -pg_tde_save_principal_key(const TDEPrincipalKey *principal_key) +pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) { int map_fd = -1; off_t curr_pos = 0; @@ -304,6 +304,13 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key) pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + if (write_xlog) + { + XLogBeginInsert(); + XLogRegisterData((char *) &signed_key_Info, sizeof(TDESignedPrincipalKeyInfo)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY); + } + map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, true, &curr_pos); close(map_fd); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 26af5bf67ea15..cfd87f9aab025 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -342,13 +342,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (!already_has_key) { /* First key created for the database */ - pg_tde_save_principal_key(new_principal_key); - - /* XLog the new key */ - XLogBeginInsert(); - XLogRegisterData((char *) &new_principal_key->keyInfo, sizeof(TDEPrincipalKeyInfo)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY); - + pg_tde_save_principal_key(new_principal_key, true); push_principal_key_to_cache(new_principal_key); } else @@ -838,7 +832,7 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode) *newPrincipalKey = *principalKey; newPrincipalKey->keyInfo.databaseId = dbOid; - pg_tde_save_principal_key(newPrincipalKey); + pg_tde_save_principal_key(newPrincipalKey, false); push_principal_key_to_cache(newPrincipalKey); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index b6788481275b9..20f0acd269ff0 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -118,7 +118,7 @@ extern void pg_tde_delete_tde_files(Oid dbOid); extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); -extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key); +extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key); extern void pg_tde_write_map_keydata_file(off_t size, char *file_data); From 9823fb75a24ad51ee1c7d13184276a88a5c3cccc Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Apr 2025 13:00:05 +0200 Subject: [PATCH 042/796] Add Dragos Andriciuc to code owners Dragos is a new technical writer. Welcome! --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index faa87493bf62c..3bdd7c5f42812 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,5 +2,5 @@ # Order is important; the last matching pattern takes the most precedence. * @dutow @dAdAbird -/contrib/pg_tde/documentation/ @nastena1606 +/contrib/pg_tde/documentation/ @nastena1606 @Andriciuc /.github/ @artemgavrilov From c8dd16849fb4fcd8ac7103c83aa16aeb7c4abdd7 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 11 Apr 2025 16:32:13 +0200 Subject: [PATCH 043/796] PG-1458 Add default key info/verify funcions --- .../pg_tde/documentation/docs/functions.md | 24 +++++++++++++++++++ .../pg_tde/expected/default_principal_key.out | 21 ++++++++++++++++ .../expected/default_principal_key_1.out | 21 ++++++++++++++++ contrib/pg_tde/pg_tde--1.0-rc.sql | 19 +++++++++++++++ contrib/pg_tde/sql/default_principal_key.sql | 11 +++++++++ .../pg_tde/src/catalog/tde_principal_key.c | 14 +++++++++++ 6 files changed, 110 insertions(+) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 80f858f4f4f9a..fb2fafe51338c 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -298,6 +298,14 @@ Displays information about the principal key for the server scope, if exists. SELECT pg_tde_server_key_info() ``` +### pg_tde_default_key_info + +Displays the information about the default principal key, if it exists. + +``` +SELECT pg_tde_default_key_info() +``` + ### pg_tde_verify_key This function checks that the current database has a properly functional encryption setup, which means: @@ -329,3 +337,19 @@ If any of the above checks fail, the function reports an error. ``` SELECT pg_tde_verify_server_key() ``` + +### pg_tde_verify_default_key + +This function checks that the default key is properly configured, which means: + +* A key provider is configured +* The key provider is accessible using the specified configuration +* There is a principal key that can be used for any scope +* The principal key can be retrieved from the remote key provider +* The principal key returned from the key provider is the same as cached in the server memory + +If any of the above checks fail, the function reports an error. + +``` +SELECT pg_tde_verify_default_key() +``` diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 75c64ffc02235..0bae2551e2f06 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -6,12 +6,33 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi -3 (1 row) +-- Should fail: no default principal key for the server yet +SELECT pg_tde_verify_default_key(); +ERROR: principal key not configured for current database +-- Should fail: no default principal key for the server yet +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); +ERROR: Principal key does not exists for the database +HINT: Use set_key interface to set the principal key SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- (1 row) +SELECT pg_tde_verify_default_key(); + pg_tde_verify_default_key +--------------------------- + +(1 row) + +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -3 | file-provider | default-key +(1 row) + -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out index 589b9f81228cc..9ad2389332539 100644 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ b/contrib/pg_tde/expected/default_principal_key_1.out @@ -6,12 +6,33 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi -4 (1 row) +-- Should fail: no default principal key for the server yet +SELECT pg_tde_verify_default_key(); +ERROR: principal key not configured for current database +-- Should fail: no default principal key for the server yet +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); +ERROR: Principal key does not exists for the database +HINT: Use set_key interface to set the principal key SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- (1 row) +SELECT pg_tde_verify_default_key(); + pg_tde_verify_default_key +--------------------------- + +(1 row) + +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -4 | file-provider | default-key +(1 row) + -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 7531578bdfe41..03de0ea5fb774 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -455,6 +455,11 @@ RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +CREATE FUNCTION pg_tde_verify_default_key() +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; + CREATE FUNCTION pg_tde_key_info() RETURNS TABLE ( key_name text, key_provider_name text, @@ -464,6 +469,14 @@ LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_server_key_info() +RETURNS TABLE ( key_name text, + key_provider_name text, + key_provider_id integer, + key_createion_time timestamp with time zone) +LANGUAGE C +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION pg_tde_default_key_info() RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, @@ -591,8 +604,11 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_key_info() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_server_key_info() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_default_key_info() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_key() TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_server_key() TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_default_key() TO %I', target_role); END; $$; @@ -672,8 +688,11 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_key_info() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_server_key_info() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_default_key_info() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_key() FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_server_key() FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_default_key() FROM %I', target_role); END; $$; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index e9c5a67a89a04..9a100a2a0d3ab 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -3,7 +3,18 @@ CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); +-- Should fail: no default principal key for the server yet +SELECT pg_tde_verify_default_key(); + +-- Should fail: no default principal key for the server yet +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); + SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); +SELECT pg_tde_verify_default_key(); + +SELECT key_provider_id, key_provider_name, key_name + FROM pg_tde_default_key_info(); -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index cfd87f9aab025..2472431462f8c 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -54,6 +54,7 @@ PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_verify_key); PG_FUNCTION_INFO_V1(pg_tde_verify_server_key); +PG_FUNCTION_INFO_V1(pg_tde_verify_default_key); typedef struct TdePrincipalKeySharedState { @@ -607,6 +608,13 @@ pg_tde_server_key_info(PG_FUNCTION_ARGS) return pg_tde_get_key_info(fcinfo, GLOBAL_DATA_TDE_OID); } +PG_FUNCTION_INFO_V1(pg_tde_default_key_info); +Datum +pg_tde_default_key_info(PG_FUNCTION_ARGS) +{ + return pg_tde_get_key_info(fcinfo, DEFAULT_DATA_TDE_OID); +} + Datum pg_tde_verify_key(PG_FUNCTION_ARGS) { @@ -619,6 +627,12 @@ pg_tde_verify_server_key(PG_FUNCTION_ARGS) return pg_tde_verify_principal_key_internal(GLOBAL_DATA_TDE_OID); } +Datum +pg_tde_verify_default_key(PG_FUNCTION_ARGS) +{ + return pg_tde_verify_principal_key_internal(DEFAULT_DATA_TDE_OID); +} + static Datum pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) { From dcdcebbf92d4ed71a05459d7ddf88b4308fa7e76 Mon Sep 17 00:00:00 2001 From: Naeem Akhter <40981522+Naeem-Akhter@users.noreply.github.com> Date: Tue, 15 Apr 2025 19:42:57 +0500 Subject: [PATCH 044/796] PG-1482, PG-1289 Add coverage to repo and percona server version check. (#212) - Added code coverage to link repo to codecov.io for coverage stats on PR and merge. - Added coverage badge on the landing page (readme) of the repo. - Updated GH action to run on PUSH/MERGE, as this is required for code coverage. - Updated bash files in ci_scripts folder to accommodate tde installcheck only. - Added percona server version scheme verification TAP test case. --- .github/codecov.yml | 45 +++++++++ .github/workflows/psp-matrix.yml | 3 + .github/workflows/psp-reusable.yml | 94 +++++++++++++++++-- README.md | 2 + ci_scripts/configure-tde-server.sh | 4 +- ci_scripts/env.sh | 4 + ci_scripts/make-build.sh | 14 ++- ci_scripts/make-test-tde.sh | 28 ++++-- ci_scripts/make-test.sh | 3 +- ci_scripts/ubuntu-deps.sh | 1 + .../test_misc/t/008_percona_server_version.pl | 62 ++++++++++++ 11 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 .github/codecov.yml create mode 100644 ci_scripts/env.sh create mode 100644 src/test/modules/test_misc/t/008_percona_server_version.pl diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000000..e124140442877 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,45 @@ +codecov: + strict_yaml_branch: TDE_REL_17_STABLE +fixes: + - "src/::contrib/pg_tde/src/" # move path for codecov file mappings e.g., "src/" => "contrib/pg_tde/src/" +coverage: + status: + project: + default: + target: 90% + threshold: 1% + base: auto +comment: + layout: "header, diff, components" +component_management: + default_rules: + statuses: + - type: project + target: auto + branches: + - "TDE_REL_17_STABLE" + individual_components: + - component_id: access + paths: + - contrib/pg_tde/src/access/** + - component_id: catalog + paths: + - contrib/pg_tde/src/catalog/** + - component_id: common + paths: + - contrib/pg_tde/src/common/** + - component_id: encryption + paths: + - contrib/pg_tde/src/encryption/** + - component_id: keyring + paths: + - contrib/pg_tde/src/keyring/** + - component_id: src + paths: + - contrib/pg_tde/src/*.c + - component_id: smgr + paths: + - contrib/pg_tde/src/smgr/** + - component_id: transam + paths: + - contrib/pg_tde/src/transam/** diff --git a/.github/workflows/psp-matrix.yml b/.github/workflows/psp-matrix.yml index 64a6c6babd5d1..ec64492a20913 100644 --- a/.github/workflows/psp-matrix.yml +++ b/.github/workflows/psp-matrix.yml @@ -1,6 +1,9 @@ name: PSP on: pull_request: + push: + branches: + - TDE_REL_17_STABLE workflow_dispatch: jobs: diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index 48293fbe21324..a851a1d0176ef 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -14,6 +14,7 @@ on: env: artifact_name: build-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + coverage_artifact_name: coverage-build-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} jobs: build: @@ -33,7 +34,7 @@ jobs: - name: Build postgres run: src/ci_scripts/${{ inputs.build_script }}-build.sh ${{ inputs.build_type }} - - name: 'Tar files' + - name: Archive pginst to artifact tar file run: tar -czf artifacts.tar src pginst - name: Upload build artifacts @@ -45,6 +46,36 @@ jobs: artifacts.tar retention-days: 1 + build-coverage: + name: Build PSP for Coverage + runs-on: ${{ inputs.os }} + if: inputs.build_script == 'make' && inputs.build_type == 'debug' + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + path: 'src' + submodules: recursive + ref: ${{ github.ref }} + + - name: Install dependencies + run: src/ci_scripts/ubuntu-deps.sh + + - name: Build postgres + run: src/ci_scripts/${{ inputs.build_script }}-build.sh ${{ inputs.build_type }} --enable-coverage + + - name: Archive pginst to artifact tar file + run: tar -czf coverage-artifacts.tar src pginst + + - name: Upload build coverage-artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.coverage_artifact_name }} + overwrite: true + path: | + coverage-artifacts.tar + retention-days: 1 + test: name: Test PSP runs-on: ${{ inputs.os }} @@ -57,7 +88,7 @@ jobs: name: ${{ env.artifact_name }} path: . - - name: 'Untar files' + - name: Extract artifact file run: tar -xzf artifacts.tar - name: Install dependencies @@ -95,15 +126,15 @@ jobs: name: ${{ env.artifact_name }} path: . - - name: 'Untar files' + - name: Extract artifact file run: tar -xzf artifacts.tar - name: Install dependencies - run: src/ci_scripts/ubuntu-deps.sh - + run: src/ci_scripts/ubuntu-deps.sh + - name: Setup kmip and vault run: src/ci_scripts/setup-keyring-servers.sh - + - name: Test postgres with TDE run: src/ci_scripts/${{ inputs.build_script }}-test-tde.sh --continue @@ -119,3 +150,54 @@ jobs: src/contrib/*/regression.diffs src/contrib/*/regression.out retention-days: 3 + + test_tde_coverage: + name: Generate Codecov Code Coverage + runs-on: ${{ inputs.os }} + if: inputs.build_script == 'make' && inputs.build_type == 'debug' + needs: build + + steps: + - name: Download build coverage-artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ env.coverage_artifact_name }} + path: . + + - name: Extract artifact file + run: tar -xzf coverage-artifacts.tar + + - name: Install dependencies + run: src/ci_scripts/ubuntu-deps.sh + + - name: Setup kmip and vault + run: src/ci_scripts/setup-keyring-servers.sh + + - name: Test postgres with TDE to generate coverage + run: src/ci_scripts/${{ inputs.build_script }}-test-tde.sh --continue --tde-only + + - name: Run code coverage + run: find src/ -type f -name "*.c" ! -path '*libkmip*' | xargs -t gcov -abcfu + working-directory: src/contrib/pg_tde + + - name: Upload coverage data to codecov.io + uses: codecov/codecov-action@v5 + with: + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + working-directory: src/contrib/pg_tde + files: "*.c.gcov" + + - name: Report on test fail + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: coverage-testlog-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + path: | + src/build/testrun/ + src/contrib/pg_tde/t/ + src/contrib/pg_tde/results + src/contrib/pg_tde/regression.diffs + src/contrib/pg_tde/regression.out + src/contrib/pg_tde/*.gcov + retention-days: 3 diff --git a/README.md b/README.md index 835ece8089a18..139bf5cfd8008 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![codecov](https://codecov.io/github/percona/postgres/graph/badge.svg?token=Wow78BMYdP)](https://codecov.io/github/percona/postgres) + Percona Server for PostgreSQL ============================= diff --git a/ci_scripts/configure-tde-server.sh b/ci_scripts/configure-tde-server.sh index a5dfedd4c42bc..7ce012f7c7c43 100644 --- a/ci_scripts/configure-tde-server.sh +++ b/ci_scripts/configure-tde-server.sh @@ -1,11 +1,9 @@ #!/bin/bash set -e -# This script is used to configure a TDE server for testing purposes. -export TDE_MODE=1 - SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" +source $SCRIPT_DIR/env.sh cd "$SCRIPT_DIR/.." diff --git a/ci_scripts/env.sh b/ci_scripts/env.sh new file mode 100644 index 0000000000000..558a01d7f086a --- /dev/null +++ b/ci_scripts/env.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export TDE_MODE=1 +export PERCONA_SERVER_VERSION=17.4.1 diff --git a/ci_scripts/make-build.sh b/ci_scripts/make-build.sh index 48e015f85af8b..799fd2364a2be 100755 --- a/ci_scripts/make-build.sh +++ b/ci_scripts/make-build.sh @@ -1,9 +1,19 @@ #!/bin/bash -export TDE_MODE=1 +ENABLE_COVERAGE= + +for arg in "$@" +do + case "$arg" in + --enable-coverage) + ENABLE_COVERAGE="--enable-coverage" + shift;; + esac +done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" +source $SCRIPT_DIR/env.sh cd "$SCRIPT_DIR/.." @@ -12,5 +22,5 @@ if [ "$1" = "debugoptimized" ]; then export CXXFLAGS="-O2" fi -./configure --enable-debug --enable-cassert --enable-tap-tests --prefix=$INSTALL_DIR +./configure --enable-debug --enable-cassert --enable-tap-tests --prefix=$INSTALL_DIR $ENABLE_COVERAGE make install-world -j diff --git a/ci_scripts/make-test-tde.sh b/ci_scripts/make-test-tde.sh index 161feff01b99a..17568c0b4756d 100755 --- a/ci_scripts/make-test-tde.sh +++ b/ci_scripts/make-test-tde.sh @@ -1,16 +1,30 @@ #!/bin/bash set -e +ADD_FLAGS= +TDE_ONLY=0 -export TDE_MODE=1 +for arg in "$@" +do + case "$arg" in + --continue) + ADD_FLAGS="-k" + shift;; + --tde-only) + TDE_ONLY=1 + shift;; + esac +done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +source $SCRIPT_DIR/env.sh source $SCRIPT_DIR/configure-tde-server.sh -ADD_FLAGS= - -if [ "$1" = "--continue" ]; then - ADD_FLAGS="-k" +if [ "$TDE_ONLY" -eq 1 ]; +then + cd "$SCRIPT_DIR/../contrib/pg_tde" + EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck $ADD_FLAGS +else + cd "$SCRIPT_DIR/.." + EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck-world $ADD_FLAGS fi - -EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck-world $ADD_FLAGS diff --git a/ci_scripts/make-test.sh b/ci_scripts/make-test.sh index e16ef4a986948..52c0aa58670bb 100755 --- a/ci_scripts/make-test.sh +++ b/ci_scripts/make-test.sh @@ -1,9 +1,8 @@ #!/bin/bash -export TDE_MODE=1 - SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" +source $SCRIPT_DIR/env.sh cd "$SCRIPT_DIR/.." diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index 5cb62fb993212..52a65745c816c 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -42,6 +42,7 @@ DEPS=( # Test pg_tde python3-pykmip libhttp-server-simple-perl + lcov ) sudo apt-get update diff --git a/src/test/modules/test_misc/t/008_percona_server_version.pl b/src/test/modules/test_misc/t/008_percona_server_version.pl new file mode 100644 index 0000000000000..c7723525ebf22 --- /dev/null +++ b/src/test/modules/test_misc/t/008_percona_server_version.pl @@ -0,0 +1,62 @@ +#!/usr/bin/perl +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use lib 't'; +use Env; + +plan tests => 6; + +# Initialize a test cluster +my $node = PostgreSQL::Test::Cluster->new('pg_server'); +$node->init(); +my $pgdata = $node->data_dir; + +# To make this testcase work, PERCONA_SERVER_VERSION variable should be available in environment. +# If you are using ci_scripts it is already declated in ci_scripts/env.sh +# If you are using command line make for regression then export like: +# export PERCONA_SERVER_VERSION=17.4.1 + +if (!defined($ENV{PERCONA_SERVER_VERSION})) +{ + BAIL_OUT("PERCONA_SERVER_VERSION variable not present in the environment"); +} + +my $percona_expected_server_version = $ENV{PERCONA_SERVER_VERSION}; + +# Start server +my $rt_value = $node->start; +ok($rt_value == 1, "Start Server"); + +# Get PG Server version (e.g 17.4) from pg_config +my $pg_server_version = `pg_config --version | awk {'print \$2'}`; +$pg_server_version=~ s/^\s+|\s+$//g; + +# Check pg_config output. +my $pg_config_output = `pg_config --version`; +$pg_config_output=~ s/^\s+|\s+$//g; +cmp_ok($pg_config_output,'eq',"PostgreSQL $pg_server_version - Percona Server for PostgreSQL $percona_expected_server_version", "Test pg_config --version output"); + +# Check psql --version output. +my $psql_version_output = `psql --version`; +$psql_version_output=~ s/^\s+|\s+$//g; +cmp_ok($psql_version_output,'eq',"psql (PostgreSQL) $pg_server_version - Percona Server for PostgreSQL $percona_expected_server_version", "Test psql --version output"); + +# Check postgres --version output. +my $postgres_output = `postgres --version`; +$postgres_output=~ s/^\s+|\s+$//g; +cmp_ok($postgres_output,'eq',"postgres (PostgreSQL) $pg_server_version - Percona Server for PostgreSQL $percona_expected_server_version", "Test postgres --version output"); + +# Check select version() output. +my ($cmdret, $stdout, $stderr) = $node->psql('postgres', "select version();", extra_params => ['-a', '-Pformat=aligned','-Ptuples_only=on']); +ok($cmdret == 0, "# Get output of select version();"); +$stdout=~ s/^\s+|\s+$//g; +like($stdout, "/PostgreSQL $pg_server_version - Percona Server for PostgreSQL $percona_expected_server_version/", "Test select version() output"); + +# Stop the server +$node->stop; + +# Done testing for this testcase file. +done_testing(); From 99ef6b20f92a1fcd8df26fdacf5070d21ff3721c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 11:59:44 +0200 Subject: [PATCH 045/796] PG-1440 Only let superusers modify the global key proviers Since as soon as we have installed pg_tde the database owner can call any function created by the extension so any database owner can meddle with any global key provider. The only way to prevent the database owner to do whatever they want add permissions checks to the C code and here we keep that check simple by limiting modifying the global key provider to only the super user. Additionally we also protect the function for settting the WAL key, for setting the default key and to be paranoid also the function for using a global key provider to set the database key. The third is not obvious if it is necessary or not but I chose to be paranoid and relax that restirction later once we have demed it to be secure. --- .../pg_tde/documentation/docs/functions.md | 7 +- contrib/pg_tde/expected/access_control.out | 13 ++++ contrib/pg_tde/pg_tde--1.0-rc.sql | 70 ------------------- contrib/pg_tde/sql/access_control.sql | 8 +++ contrib/pg_tde/src/catalog/tde_keyring.c | 10 +++ .../pg_tde/src/catalog/tde_principal_key.c | 20 ++++++ 6 files changed, 53 insertions(+), 75 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index fb2fafe51338c..4af66a4fb31be 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -4,7 +4,7 @@ The `pg_tde` extension provides functions for managing different aspects of its ## Permission management -By default, `pg_tde` is restrictive. It doesn't allow any operations until permissions are granted to the user. Only superusers can run permission management functions to manage user permissions. +By default, `pg_tde` is restrictive. It doesn't allow any operations until permissions are granted to the user. Only superusers can run permission management functions to manage user permissions. Operations on the global scope are limited to superusers only. Permissions are based on the normal `EXECUTE` permission on the functions provided by `pg_tde`. Superusers manage them using the `GRANT EXECUTE` and `REVOKE EXECUTE` commands. @@ -19,10 +19,7 @@ Use these functions to grant or revoke permissions to manage permissions for the ### Global scope key management -Use these functions to grant or revoke permissions to manage permissions for the global scope - the entire PostgreSQL instance. They enable or disable all functions related to the providers and keys for the global scope: - -* `pg_tde_grant_global_key_management_to_role(role)` -* `pg_tde_revoke_global_key_management_from_role(role)` +Managment of the global scope is restricted to superusers only. ### Permission management diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 4bb987955efac..3a3087dfb268a 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -52,6 +52,19 @@ SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); test-db-key | file-vault | 1 (1 row) +-- only superuser +SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +ERROR: must be superuser to modify global key providers +SELECT pg_tde_change_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +ERROR: must be superuser to modify global key providers +SELECT pg_tde_delete_global_key_provider('file-vault'); +ERROR: must be superuser to modify global key providers +SELECT pg_tde_set_key_using_global_key_provider('key1', 'file-vault'); +ERROR: must be superuser to access global key providers +SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'file-vault'); +ERROR: must be superuser to access global key providers +SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'file-vault'); +ERROR: must be superuser to access global key providers RESET ROLE; SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); pg_tde_revoke_key_viewer_from_role diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 03de0ea5fb774..72876cb58a962 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -528,39 +528,6 @@ ALTER EVENT TRIGGER pg_tde_trigger_create_index_2 ENABLE ALWAYS; -- Per database extension initialization SELECT pg_tde_extension_initialize(); -CREATE FUNCTION pg_tde_grant_global_key_management_to_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(text, text, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_vault_v2(text, text, text, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider(text, text, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_vault_v2(text, text, text, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(text, text, BOOLEAN) TO %I', target_role); -END; -$$; - CREATE FUNCTION pg_tde_grant_database_key_management_to_role( target_role TEXT) RETURNS VOID @@ -612,39 +579,6 @@ BEGIN END; $$; -CREATE FUNCTION pg_tde_revoke_global_key_management_from_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider(text, text, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_global_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider(text, text, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_global_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(text) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(text, text, BOOLEAN) FROM %I', target_role); -END; -$$; - CREATE FUNCTION pg_tde_revoke_database_key_management_from_role( target_role TEXT) RETURNS VOID @@ -703,7 +637,6 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_global_key_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) TO %I', target_role); @@ -722,12 +655,10 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_global_key_management_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_global_key_management_from_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) FROM %I', target_role); EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) FROM %I', target_role); @@ -736,6 +667,5 @@ $$; -- Revoking all the privileges from the public role SELECT pg_tde_revoke_database_key_management_from_role('public'); -SELECT pg_tde_revoke_global_key_management_from_role('public'); SELECT pg_tde_revoke_grant_management_from_role('public'); SELECT pg_tde_revoke_key_viewer_from_role('public'); diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 6ead98a608c1e..03e0f75a57c8f 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -22,6 +22,14 @@ SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +-- only superuser +SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_delete_global_key_provider('file-vault'); +SELECT pg_tde_set_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'file-vault'); + RESET ROLE; SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index ebeb6444ddf73..68ae7e427f40c 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -214,6 +214,11 @@ pg_tde_change_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_change_global_key_provider(PG_FUNCTION_ARGS) { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers"))); + return pg_tde_change_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } @@ -264,6 +269,11 @@ pg_tde_add_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_add_global_key_provider(PG_FUNCTION_ARGS) { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers"))); + return pg_tde_add_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 2472431462f8c..d23cd3f0d0f67 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -485,6 +485,11 @@ pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers"))); + pg_tde_set_principal_key_internal(principal_key_name, GS_DEFAULT, provider_name, ensure_new_key); PG_RETURN_VOID(); @@ -509,6 +514,11 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers"))); + pg_tde_set_principal_key_internal(principal_key_name, GS_GLOBAL, provider_name, ensure_new_key); PG_RETURN_VOID(); @@ -521,6 +531,11 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers"))); + pg_tde_set_principal_key_internal(principal_key_name, GS_SERVER, provider_name, ensure_new_key); PG_RETURN_VOID(); @@ -1045,6 +1060,11 @@ pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers"))); + return pg_tde_delete_key_provider_internal(fcinfo, 1); } From 20ab981941875e651e6d1847a2125493a14d4fdc Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 14:04:12 +0200 Subject: [PATCH 046/796] PG-1440 Remove unnecesary query for test case This query gave no extra coverage, either in qualitity or quality. --- contrib/pg_tde/expected/access_control.out | 13 +++---------- contrib/pg_tde/sql/access_control.sql | 1 - 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 3a3087dfb268a..c6a5594bc9bf9 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -27,12 +27,6 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_key 1 (1 row) -SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 2 -(1 row) - SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -40,11 +34,10 @@ SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+-------------------------------------------------------------- + id | provider_name | provider_type | options +----+---------------+---------------+------------------------------------------------------------ 1 | file-vault | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_2.per"} -(2 rows) +(1 row) SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); key_name | key_provider_name | key_provider_id diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 03e0f75a57c8f..f992304b1b590 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -17,7 +17,6 @@ SET ROLE regress_pg_tde_access_control; -- should now be allowed SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); From a08ed995702b8d2f4a771314cbe57bb50a4b5857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 16 Apr 2025 10:30:14 +0200 Subject: [PATCH 047/796] Modernize the way we call ereport() part 2 These were not updated in 725c34da520cca4597f6751d1d671c6191466200 and we still want to use this way of calling ereport() everywhere. --- contrib/pg_tde/src/catalog/tde_keyring.c | 8 ++--- .../pg_tde/src/catalog/tde_principal_key.c | 16 +++++----- contrib/pg_tde/src/encryption/enc_aes.c | 30 +++++++++---------- contrib/pg_tde/src/keyring/keyring_api.c | 6 ++-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 68ae7e427f40c..b7937f568e36d 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -216,8 +216,8 @@ pg_tde_change_global_key_provider(PG_FUNCTION_ARGS) { if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers")); return pg_tde_change_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } @@ -271,8 +271,8 @@ pg_tde_add_global_key_provider(PG_FUNCTION_ARGS) { if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers")); return pg_tde_add_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index d23cd3f0d0f67..f7c225372de4d 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -487,8 +487,8 @@ pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); pg_tde_set_principal_key_internal(principal_key_name, GS_DEFAULT, provider_name, ensure_new_key); @@ -516,8 +516,8 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); pg_tde_set_principal_key_internal(principal_key_name, GS_GLOBAL, provider_name, ensure_new_key); @@ -533,8 +533,8 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); pg_tde_set_principal_key_internal(principal_key_name, GS_SERVER, provider_name, ensure_new_key); @@ -1062,8 +1062,8 @@ pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) { if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers")); return pg_tde_delete_key_provider_internal(fcinfo, 1); } diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index d878c6260d036..cb6f265f7da8f 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -150,35 +150,35 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned if (EVP_EncryptInit_ex(ctx, cipher_gcm, NULL, NULL, NULL) == 0) ereport(ERROR, - (errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_set_padding(ctx, 0) == 0) ereport(ERROR, - (errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) == 0) ereport(ERROR, - (errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) == 0) ereport(ERROR, - (errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_EncryptUpdate(ctx, NULL, &out_len, (unsigned char *) aad, aad_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_EncryptUpdate(ctx, out, &out_len, in, in_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_EncryptFinal_ex(ctx, out + out_len, &out_len_final) == 0) ereport(ERROR, - (errmsg("EVP_CipherFinal_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherFinal_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) == 0) ereport(ERROR, - (errmsg("EVP_CTRL_GCM_GET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CTRL_GCM_GET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); /* * We encrypt one block (16 bytes) Our expectation is that the result @@ -205,31 +205,31 @@ AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned if (EVP_DecryptInit_ex(ctx, cipher_gcm, NULL, NULL, NULL) == 0) ereport(ERROR, - (errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_set_padding(ctx, 0) == 0) ereport(ERROR, - (errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) == 0) ereport(ERROR, - (errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) == 0) ereport(ERROR, - (errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag) == 0) ereport(ERROR, - (errmsg("EVP_CTRL_GCM_SET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CTRL_GCM_SET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_DecryptUpdate(ctx, NULL, &out_len, aad, aad_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_DecryptUpdate(ctx, out, &out_len, in, in_len) == 0) ereport(ERROR, - (errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL)))); + errmsg("EVP_CipherUpdate failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); if (EVP_DecryptFinal_ex(ctx, out + out_len, &out_len_final) == 0) { diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index d2f119b1ba116..766d75dc43fd9 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -78,7 +78,7 @@ RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderType type) kp = find_key_provider_type(type); if (kp) ereport(ERROR, - (errmsg("Key provider of type %d already registered", type))); + errmsg("Key provider of type %d already registered", type)); #ifndef FRONTEND oldcontext = MemoryContextSwitchTo(TopMemoryContext); @@ -102,7 +102,7 @@ KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes if (kp == NULL) { ereport(WARNING, - (errmsg("Key provider of type %d not registered", keyring->type))); + errmsg("Key provider of type %d not registered", keyring->type)); *returnCode = KEYRING_CODE_INVALID_PROVIDER; return NULL; } @@ -116,7 +116,7 @@ KeyringStoreKey(GenericKeyring *keyring, KeyInfo *key) if (kp == NULL) ereport(ERROR, - (errmsg("Key provider of type %d not registered", keyring->type))); + errmsg("Key provider of type %d not registered", keyring->type)); kp->routine->keyring_store_key(keyring, key); } From 6aa4203ecaef4eba73a645d14afe6c010e0cf305 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 11:49:32 +0200 Subject: [PATCH 048/796] Actually run the test of pg_tde_version() The test was added in fb5658242eed71b645768ec35ec13d3e4c8cc1e4 but never actually added to the lists of tests. --- contrib/pg_tde/Makefile | 1 + contrib/pg_tde/meson.build | 1 + 2 files changed, 2 insertions(+) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index cd206b877de59..5565f4347aa79 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -23,6 +23,7 @@ relocate \ subtransaction \ tablespace \ vault_v2_test \ +version \ default_principal_key TAP_TESTS = 1 diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 5a95a4c8ba7f0..1041ea232da3d 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -100,6 +100,7 @@ sql_tests = [ 'subtransaction', 'tablespace', 'vault_v2_test', + 'version', 'default_principal_key', ] From c10665840ecf8e986b78a3ab18d5ec673bbb604f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 11:09:45 +0200 Subject: [PATCH 049/796] Remove dead code in pg_tde_defs.c --- contrib/pg_tde/src/include/pg_tde_defs.h | 3 --- contrib/pg_tde/src/pg_tde_defs.c | 11 ----------- 2 files changed, 14 deletions(-) diff --git a/contrib/pg_tde/src/include/pg_tde_defs.h b/contrib/pg_tde/src/include/pg_tde_defs.h index 1402c6a8fbef5..b96dab5452935 100644 --- a/contrib/pg_tde/src/include/pg_tde_defs.h +++ b/contrib/pg_tde/src/include/pg_tde_defs.h @@ -8,9 +8,6 @@ #ifndef PG_TDE_DEFS_H #define PG_TDE_DEFS_H - extern const char *pg_tde_package_string(void); -extern const char *pg_tde_package_name(void); -extern const char *pg_tde_package_version(void); #endif /* PG_TDE_DEFS_H */ diff --git a/contrib/pg_tde/src/pg_tde_defs.c b/contrib/pg_tde/src/pg_tde_defs.c index 22d2602cbe56a..fb86807a870e2 100644 --- a/contrib/pg_tde/src/pg_tde_defs.c +++ b/contrib/pg_tde/src/pg_tde_defs.c @@ -23,14 +23,3 @@ pg_tde_package_string(void) { return PACKAGE_STRING; } - -const char * -pg_tde_package_name(void) -{ - return PACKAGE_NAME; -} -const char * -pg_tde_package_version(void) -{ - return PACKAGE_VERSION; -} From 44c3619f5ec929641dc7ff467f6f00f313f2ecc9 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 11:16:40 +0200 Subject: [PATCH 050/796] Stop pretending that we use autotools to build config.h We have not been using autotools since commit e0978a8be6c70b2fccc86ca1cb8fc5499dd83a88 so stop pretending that we do related to config.h and instead directly have the necessary defines with the right names in pg_tde.h. --- contrib/pg_tde/Makefile | 1 - contrib/pg_tde/meson.build | 1 - contrib/pg_tde/src/include/config.h | 12 ------------ contrib/pg_tde/src/include/pg_tde.h | 4 ++++ contrib/pg_tde/src/include/pg_tde_defs.h | 13 ------------ contrib/pg_tde/src/pg_tde.c | 3 +-- contrib/pg_tde/src/pg_tde_defs.c | 25 ------------------------ 7 files changed, 5 insertions(+), 54 deletions(-) delete mode 100644 contrib/pg_tde/src/include/config.h delete mode 100644 contrib/pg_tde/src/include/pg_tde_defs.h delete mode 100644 contrib/pg_tde/src/pg_tde_defs.c diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 5565f4347aa79..d7a07396a1e09 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -45,7 +45,6 @@ src/catalog/tde_principal_key.o \ src/common/pg_tde_shmem.o \ src/common/pg_tde_utils.o \ src/smgr/pg_tde_smgr.o \ -src/pg_tde_defs.o \ src/pg_tde_event_capture.o \ src/pg_tde_guc.o \ src/pg_tde.o \ diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 1041ea232da3d..5527666f51d8c 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -18,7 +18,6 @@ pg_tde_sources = files( 'src/keyring/keyring_kmip_impl.c', 'src/keyring/keyring_vault.c', 'src/pg_tde.c', - 'src/pg_tde_defs.c', 'src/pg_tde_event_capture.c', 'src/pg_tde_guc.c', 'src/smgr/pg_tde_smgr.c', diff --git a/contrib/pg_tde/src/include/config.h b/contrib/pg_tde/src/include/config.h deleted file mode 100644 index 393cefbfb7dee..0000000000000 --- a/contrib/pg_tde/src/include/config.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef TDE_CONFIG_H -#define TDE_CONFIG_H - -#define PACKAGE_NAME "pg_tde" -#define PACKAGE_VERSION "1.0.0-rc" - -#define PACKAGE_STRING PACKAGE_NAME" "PACKAGE_VERSION - -#define PACKAGE_TARNAME "pg_tde" -#define PACKAGE_BUGREPORT "https://github.com/percona/pg_tde/issues" - -#endif /* TDE_CONFIG_H */ diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index 167efea4bfdcf..d3c14387045a0 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -8,6 +8,10 @@ #ifndef PG_TDE_H #define PG_TDE_H +#define PG_TDE_NAME "pg_tde" +#define PG_TDE_VERSION "1.0.0-rc" +#define PG_TDE_VERSION_STRING PG_TDE_NAME " " PG_TDE_VERSION + #define PG_TDE_DATA_DIR "pg_tde" typedef struct XLogExtensionInstall diff --git a/contrib/pg_tde/src/include/pg_tde_defs.h b/contrib/pg_tde/src/include/pg_tde_defs.h deleted file mode 100644 index b96dab5452935..0000000000000 --- a/contrib/pg_tde/src/include/pg_tde_defs.h +++ /dev/null @@ -1,13 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_defs.h - * src/include/pg_tde_defs.h - * - *------------------------------------------------------------------------- - */ -#ifndef PG_TDE_DEFS_H -#define PG_TDE_DEFS_H - -extern const char *pg_tde_package_string(void); - -#endif /* PG_TDE_DEFS_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index e37e67abcd18f..34fd1b872067d 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -32,7 +32,6 @@ #include "keyring/keyring_vault.h" #include "keyring/keyring_kmip.h" #include "utils/builtins.h" -#include "pg_tde_defs.h" #include "smgr/pg_tde_smgr.h" #include "catalog/tde_global_space.h" #include "utils/percona.h" @@ -222,7 +221,7 @@ run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo) Datum pg_tde_version(PG_FUNCTION_ARGS) { - PG_RETURN_TEXT_P(cstring_to_text(pg_tde_package_string())); + PG_RETURN_TEXT_P(cstring_to_text(PG_TDE_VERSION_STRING)); } Datum diff --git a/contrib/pg_tde/src/pg_tde_defs.c b/contrib/pg_tde/src/pg_tde_defs.c deleted file mode 100644 index fb86807a870e2..0000000000000 --- a/contrib/pg_tde/src/pg_tde_defs.c +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_defs.c - * The configure script generates config.h which contains the package_* defs - * and these defines conflicts with the PG defines. - * This file is used to provide the package version string to the extension - * without including the config.h file. - * - * IDENTIFICATION - * contrib/pg_tde/src/pg_tde_defs.c - * - *------------------------------------------------------------------------- - */ - - -#include "config.h" -#include "pg_tde_defs.h" - - -/* Returns package version */ -const char * -pg_tde_package_string(void) -{ - return PACKAGE_STRING; -} From 033f7cae77ed65cab369741e4a2b3aec825ad77b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 10:32:27 +0200 Subject: [PATCH 051/796] Make sure we have tests which fetch KMIP and Vault keys Before this we had no coverage of the code path for successfully fetching a KMIP key from the key provider. --- contrib/pg_tde/expected/kmip_test.out | 6 ++++++ contrib/pg_tde/expected/vault_v2_test.out | 6 ++++++ contrib/pg_tde/sql/kmip_test.sql | 2 ++ contrib/pg_tde/sql/vault_v2_test.sql | 2 ++ 4 files changed, 16 insertions(+) diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index c590a25be972e..9b4949ece005a 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -27,5 +27,11 @@ SELECT * from test_enc; 3 | 3 (3 rows) +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) + DROP TABLE test_enc; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 8943184e4bfb6..06a2fd71949f7 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -44,5 +44,11 @@ SELECT * from test_enc; 3 | 3 (3 rows) +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) + DROP TABLE test_enc; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index 9be6b8f4e2e83..4b647ba11295d 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -15,6 +15,8 @@ INSERT INTO test_enc (k) VALUES (3); SELECT * from test_enc; +SELECT pg_tde_verify_key(); + DROP TABLE test_enc; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index f50aff68c59c6..4e8b92c97aa2a 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -27,6 +27,8 @@ INSERT INTO test_enc (k) VALUES (3); SELECT * from test_enc; +SELECT pg_tde_verify_key(); + DROP TABLE test_enc; DROP EXTENSION pg_tde; From cc459df8962e1b8a9988717e0d889b5a992ee030 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Apr 2025 13:50:36 +0300 Subject: [PATCH 052/796] Always pfree tmp WAL key when allocated We didn't pfree the key in case of `EncryptXLog == true && key == true` --- contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c index eaae1c87f24e6..706f8a442d8e7 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c @@ -206,15 +206,16 @@ TDEXLogSmgrInit(void) { pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), TDE_KEY_TYPE_WAL_UNENCRYPTED); - pfree(key); } else if (key) { EncryptionKey = *key; pg_atomic_write_u64(&EncryptionState->enc_key_lsn, EncryptionKey.start_lsn); - pfree(key); } + if (key) + pfree(key); + pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); #endif From a4818a845f75b3dfb43579c9d1bcab0214a880be Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 17:57:17 +0200 Subject: [PATCH 053/796] Clean up TAP test helpers Checking for PostgreSQL versions can be done in a cleaner way plus we do not need to run chmod. --- contrib/pg_tde/t/pgtde.pm | 44 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 67b1709aad749..a9039baf25704 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -4,10 +4,10 @@ use File::Basename; use File::Compare; use Test::More; -our @ISA= qw( Exporter ); +our @ISA = qw(Exporter); # These CAN be exported. -our @EXPORT = qw( pgtde_init_pg pgtde_start_pg pgtde_stop_pg pgtde_psql_cmd pgtde_setup_pg_tde pgtde_create_extension pgtde_drop_extension ); +our @EXPORT = qw(pgtde_init_pg pgtde_start_pg pgtde_stop_pg pgtde_psql_cmd pgtde_setup_pg_tde pgtde_create_extension pgtde_drop_extension); # Instance of pg server that would be spanwed by TAP testing. A new server will be created for each TAP test. our $pg_node; @@ -29,13 +29,11 @@ BEGIN { $PG_MAJOR_VERSION = `pg_config --version | awk {'print \$2'} | cut -f1 -d"." | sed -e 's/[^0-9].*\$//g'`; $PG_MAJOR_VERSION =~ s/^\s+|\s+$//g; - # Depending upon PG server version load the required module at runtime when pgtde.pm is loaded. - my $node_module = $PG_MAJOR_VERSION > 14 ? "PostgreSQL::Test::Cluster" : "PostgresNode"; - my $node_module_file = $node_module; - $node_module_file =~ s[::][/]g; - $node_module_file .= '.pm'; - require $node_module_file; - $node_module->import; + if ($PG_MAJOR_VERSION >= 15) { + eval { require PostgreSQL::Test::Cluster; }; + } else { + eval { require PostgresNode; }; + } } sub pgtde_init_pg @@ -43,11 +41,9 @@ sub pgtde_init_pg print "Postgres major version: $PG_MAJOR_VERSION \n"; # For Server version 15 & above, spawn the server using PostgreSQL::Test::Cluster - if ($PG_MAJOR_VERSION > 14) { + if ($PG_MAJOR_VERSION >= 15) { $pg_node = PostgreSQL::Test::Cluster->new('pgtde_regression'); - } - # For Server version 14 & below, spawn the server using PostgresNode - elsif ($PG_MAJOR_VERSION < 15) { + } else { $pg_node = PostgresNode->get_new_node('pgtde_regression'); } @@ -60,36 +56,22 @@ sub append_to_file { my ($str) = @_; - # For Server version 15 & above, use PostgreSQL::Test::Utils to write to files - if ($PG_MAJOR_VERSION > 14) { + if ($PG_MAJOR_VERSION >= 15) { PostgreSQL::Test::Utils::append_to_file($out_filename_with_path, $str . "\n"); - } - # For Server version 14 & below, use PostgresNode to write to files - elsif ($PG_MAJOR_VERSION < 15) { + } else { TestLib::append_to_file($out_filename_with_path, $str . "\n"); } - chmod(0640 , $out_filename_with_path) - or die("unable to set permissions for $out_filename_with_path"); - - return; } sub append_to_debug_file { my ($str) = @_; - # For Server version 15 & above, use PostgreSQL::Test::Utils to write to files - if ($PG_MAJOR_VERSION > 14) { + if ($PG_MAJOR_VERSION >= 15) { PostgreSQL::Test::Utils::append_to_file($debug_out_filename_with_path, $str . "\n"); - } - # For Server version 14 & below, use PostgresNode to write to files - elsif ($PG_MAJOR_VERSION < 15) { + } else { TestLib::append_to_file($debug_out_filename_with_path, $str . "\n"); } - chmod(0640 , $debug_out_filename_with_path) - or die("unable to set permissions for $debug_out_filename_with_path"); - - return; } sub setup_files_dir From 945cbe436b32b566ed55595660c1d36445fc3b5c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 10:02:33 +0200 Subject: [PATCH 054/796] Make global variable local in TAP tests --- contrib/pg_tde/t/pgtde.pm | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index a9039baf25704..0d052ae6ee83b 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -9,9 +9,6 @@ our @ISA = qw(Exporter); # These CAN be exported. our @EXPORT = qw(pgtde_init_pg pgtde_start_pg pgtde_stop_pg pgtde_psql_cmd pgtde_setup_pg_tde pgtde_create_extension pgtde_drop_extension); -# Instance of pg server that would be spanwed by TAP testing. A new server will be created for each TAP test. -our $pg_node; - # Expected .out filename of TAP testcase being executed. These are already part of repo under t/expected/*. our $expected_filename_with_path; @@ -38,18 +35,20 @@ BEGIN { sub pgtde_init_pg { + my $node; + print "Postgres major version: $PG_MAJOR_VERSION \n"; # For Server version 15 & above, spawn the server using PostgreSQL::Test::Cluster if ($PG_MAJOR_VERSION >= 15) { - $pg_node = PostgreSQL::Test::Cluster->new('pgtde_regression'); + $node = PostgreSQL::Test::Cluster->new('pgtde_regression'); } else { - $pg_node = PostgresNode->get_new_node('pgtde_regression'); + $node = PostgresNode->get_new_node('pgtde_regression'); } - $pg_node->dump_info; - $pg_node->init; - return $pg_node; + $node->dump_info; + $node->init; + return $node; } sub append_to_file From 233305bbc4a7eaa5b1db044e85c66b2b16a2a443 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Apr 2025 20:36:36 +0200 Subject: [PATCH 055/796] Remove unused includes in TAP tests Additionally we remove unnecessary copies if the generated config file. --- contrib/pg_tde/t/001_basic.pl | 2 -- contrib/pg_tde/t/002_rotate_key.pl | 2 -- contrib/pg_tde/t/003_remote_config.pl | 2 -- contrib/pg_tde/t/004_file_config.pl | 4 ---- contrib/pg_tde/t/005_multiple_extensions.pl | 4 ---- contrib/pg_tde/t/006_remote_vault_config.pl | 4 +--- contrib/pg_tde/t/007_tde_heap.pl | 2 -- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 2 -- contrib/pg_tde/t/009_wal_encrypt.pl | 2 -- contrib/pg_tde/t/010_change_key_provider.pl | 1 - contrib/pg_tde/t/011_unlogged_tables.pl | 2 -- 11 files changed, 1 insertion(+), 26 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 4740992f03257..03a6713ed87ed 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 31b99250dadec..040fa593a132a 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 40fb1be7c0d72..5d3376cfd036c 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 4bf359d04d5df..b90a7e2cbf91a 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; @@ -16,8 +14,6 @@ my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -copy("$pgdata/postgresql.conf", "$pgdata/postgresql.conf.bak"); - # UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index d3beecb8843fb..8db86a24248a8 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; @@ -23,8 +21,6 @@ my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -copy("$pgdata/postgresql.conf", "$pgdata/postgresql.conf.bak"); - # UPDATE postgresql.conf to include/load pg_stat_monitor library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'\n"; diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index c0fb84b3d159c..de2f23dd395cf 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -2,13 +2,11 @@ use strict; use warnings; +use Env; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; -use Env; # Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index be006eb7a3898..e63b12643128d 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 3203da3db72bd..9113fae4462da 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 88fece2e24d58..db6d5f4f0a91d 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index a59ecab9d7dad..9697787719479 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -3,7 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; use File::Copy; use Test::More; use lib 't'; diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 22e7c5f10c680..8c74429042112 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -3,8 +3,6 @@ use strict; use warnings; use File::Basename; -use File::Compare; -use File::Copy; use Test::More; use lib 't'; use pgtde; From 3bb5edf7b903232c7be3d890a284cc9e90d15d59 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Apr 2025 20:58:50 +0200 Subject: [PATCH 056/796] Remove pointless white space and comments from TAP tests It removed readability rather than improved it. --- contrib/pg_tde/t/001_basic.pl | 16 ++------- contrib/pg_tde/t/002_rotate_key.pl | 33 ++++--------------- contrib/pg_tde/t/003_remote_config.pl | 14 ++------ contrib/pg_tde/t/004_file_config.pl | 11 ++----- contrib/pg_tde/t/005_multiple_extensions.pl | 11 +------ contrib/pg_tde/t/006_remote_vault_config.pl | 14 ++------ contrib/pg_tde/t/007_tde_heap.pl | 29 ++-------------- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 15 ++------- contrib/pg_tde/t/009_wal_encrypt.pl | 12 ++----- contrib/pg_tde/t/010_change_key_provider.pl | 17 +--------- contrib/pg_tde/t/011_unlogged_tables.pl | 10 +----- 11 files changed, 25 insertions(+), 157 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 03a6713ed87ed..1a44006ed27a8 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -7,23 +7,18 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -# CREATE EXTENSION IF NOT EXISTS and change out file permissions my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); @@ -35,11 +30,8 @@ $rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); ok($rt_value == 3, "Failing query"); - -# Restart the server PGTDE::append_to_file("-- server restart"); $node->stop(); - $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -55,7 +47,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -79,18 +70,15 @@ $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 040fa593a132a..495e19ae697d2 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -7,36 +7,27 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -# CREATE EXTENSION IF NOT EXISTS and change out file permissions my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); - $rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); ok($rt_value == 3, "Failing query"); - -# Restart the server PGTDE::append_to_file("-- server restart"); $node->stop(); - $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -64,13 +55,12 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -#rotate key +# Rotate key $stdout = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -83,14 +73,12 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); - -#Again rotate key +# Again rotate key $stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -103,13 +91,12 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -#Again rotate key +# Again rotate key $stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -125,13 +112,12 @@ # TODO: add method to query current info # And maybe debug tools to show what's in a file keyring? -#Again rotate key +# Again rotate key $stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);", extra_params => ['-a']); PGTDE::append_to_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -148,7 +134,6 @@ PGTDE::append_to_file($stdout); # Things still work after a restart -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -170,9 +155,6 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); - -# end - $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -182,18 +164,15 @@ $rt_value = $node->stop(); $rt_value = $node->start(); -# DROP EXTENSION ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 5d3376cfd036c..d6007f6428fee 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -7,10 +7,8 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; @@ -22,7 +20,6 @@ package MyWebServer; my %dispatch = ( '/hello' => \&resp_hello, - # ... ); sub handle_request { @@ -52,10 +49,9 @@ sub resp_hello { } } -my $pid = MyWebServer->new(8888)->background(); +my $pid = MyWebServer->new(8888)->background(); -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; @@ -80,7 +76,6 @@ sub resp_hello { $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -91,20 +86,17 @@ sub resp_hello { $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc2;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); system("kill $pid"); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index b90a7e2cbf91a..f9e55b2f33d70 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -7,14 +7,11 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; @@ -42,7 +39,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -53,18 +49,15 @@ $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 8db86a24248a8..d9d970b57d05d 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -7,7 +7,6 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); my $PG_VERSION_STRING = `pg_config --version`; @@ -17,7 +16,6 @@ plan skip_all => "pg_tde test case only for PPG server package install with extensions."; } -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; @@ -32,7 +30,6 @@ print $conf2 "/tmp/keyring_data_file\n"; close $conf2; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); @@ -94,7 +91,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -131,24 +127,19 @@ ok($cmdret == 0, "SELECT XXX FROM pg_stat_monitor"); PGTDE::append_to_debug_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_stat_monitor;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_debug_file($stdout); -# Stop the server $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index de2f23dd395cf..38a7cfccac279 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -8,10 +8,8 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; @@ -24,7 +22,6 @@ package MyWebServer; my %dispatch = ( '/token' => \&resp_token, '/url' => \&resp_url, - # ... ); sub handle_request { @@ -60,10 +57,9 @@ sub resp_url { } } -my $pid = MyWebServer->new(8889)->background(); +my $pid = MyWebServer->new(8889)->background(); -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; @@ -88,7 +84,6 @@ sub resp_url { $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -99,20 +94,17 @@ sub resp_url { $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc2;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); system("kill -9 $pid"); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index e63b12643128d..fb574d34566eb 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -7,7 +7,6 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); my $PG_VERSION_STRING = `pg_config --version`; @@ -17,44 +16,33 @@ plan skip_all => "pg_tde test case only for Percona Server for PostgreSQL"; } -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -# CREATE EXTENSION IF NOT EXISTS and change out file permissions my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); - $rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); ok($rt_value == 3, "Failing query"); - -# Restart the server PGTDE::append_to_file("-- server restart"); $node->stop(); - $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); $rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); $rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); - - ######################### test_enc1 (simple create table w tde_heap) - $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -100,7 +88,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc4 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); - ######################### test_enc5 (create tde_heap + truncate) $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); @@ -121,7 +108,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -167,9 +153,6 @@ sub verify_table $strings .= `strings $tablefile2 | grep foo`; PGTDE::append_to_file($strings); - - - # Verify that we can't see the data in the file my $tablefile3 = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile3 .= '/'; @@ -183,9 +166,6 @@ sub verify_table $strings .= `strings $tablefile3 | grep foo`; PGTDE::append_to_file($strings); - - - # Verify that we can't see the data in the file my $tablefile4 = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile4 .= '/'; @@ -199,8 +179,6 @@ sub verify_table $strings .= `strings $tablefile4 | grep foo`; PGTDE::append_to_file($strings); - - $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -216,18 +194,15 @@ sub verify_table $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc5;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 9113fae4462da..7e563b31def22 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -7,21 +7,17 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); my ($cmdret, $stdout); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); @@ -54,23 +50,18 @@ }, extra_params => ['-a']); PGTDE::append_to_file($stdout); - $cmdret = $node->psql('tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');", extra_params => ['-a']); ok($cmdret == 0, "ROTATE KEY"); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $node->stop(); - $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('tbc', 'SELECT * FROM country_table;', extra_params => ['-a']); PGTDE::append_to_file($stdout); - -# DROP EXTENSION $stdout = $node->safe_psql('tbc', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_file($stdout); @@ -81,14 +72,12 @@ }, extra_params => ['-a']); ok($cmdret == 0, "DROP DATABSE"); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index db6d5f4f0a91d..39b9fa4d06926 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -7,14 +7,11 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; print $conf "wal_level = 'logical'\n"; @@ -37,7 +34,6 @@ $stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server, it should work with encryption now PGTDE::append_to_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); @@ -72,7 +68,6 @@ $stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); PGTDE::append_to_file($stdout); - PGTDE::append_to_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); @@ -101,17 +96,14 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_drop_replication_slot('tde_slot');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Stop the server + $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 9697787719479..0bbd3d1c11937 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -8,14 +8,11 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; @@ -25,11 +22,9 @@ unlink('/tmp/change_key_provider_3.per'); unlink('/tmp/change_key_provider_4.per'); -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -# CREATE EXTENSION IF NOT EXISTS and change out file permissions my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); @@ -68,7 +63,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -95,7 +89,6 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -114,7 +107,6 @@ PGTDE::append_to_file("-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -127,12 +119,10 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION (undef, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -# CREATE EXTENSION ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_file($stdout); @@ -158,7 +148,6 @@ $stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Restart the server PGTDE::append_to_file("-- server restart"); $rt_value = $node->stop(); $rt_value = $node->start(); @@ -188,19 +177,15 @@ $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -# DROP EXTENSION (undef, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); PGTDE::append_to_file($stdout); PGTDE::append_to_file($stderr); -# Stop the server $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 8c74429042112..5c81b92233497 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -7,19 +7,15 @@ use lib 't'; use pgtde; -# Get file name and CREATE out file name and dirs WHERE requried PGTDE::setup_files_dir(basename($0)); -# CREATE new PostgreSQL node and do initdb my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_tde library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -# Start server my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); @@ -43,7 +39,6 @@ PGTDE::append_to_file("-- kill -9"); $node->kill9(); -# Start server PGTDE::append_to_file("-- server start"); $rt_value = $node->start; ok($rt_value == 1, "Start Server"); @@ -54,14 +49,11 @@ $stdout = $node->safe_psql('postgres', "INSERT INTO t SELECT generate_series(1, 4);", extra_params => ['-a']); PGTDE::append_to_file($stdout); -# Stop the server $node->stop(); -# compare the expected and out file +# Compare the expected and out file my $compare = PGTDE->compare_results(); -# Test/check if expected and result/out file match. If Yes, test passes. is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); -# Done testing for this testcase file. done_testing(); From 891adb150cab4d706903a96d3272b39e647bd707 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Apr 2025 21:07:06 +0200 Subject: [PATCH 057/796] Always restart the server in the same way in our TAP tests It is hard to keep track when every place does it differently. --- contrib/pg_tde/t/001_basic.pl | 3 ++- contrib/pg_tde/t/002_rotate_key.pl | 19 +++++++++++++------ contrib/pg_tde/t/003_remote_config.pl | 8 ++++---- contrib/pg_tde/t/004_file_config.pl | 3 ++- contrib/pg_tde/t/005_multiple_extensions.pl | 3 ++- contrib/pg_tde/t/006_remote_vault_config.pl | 8 ++++---- contrib/pg_tde/t/007_tde_heap.pl | 3 ++- contrib/pg_tde/t/010_change_key_provider.pl | 12 ++++++++---- contrib/pg_tde/t/expected/002_rotate_key.out | 2 +- 9 files changed, 38 insertions(+), 23 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 1a44006ed27a8..97a5623cd2c65 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -48,8 +48,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 495e19ae697d2..436892ba2f1c1 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -62,8 +62,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -80,8 +81,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -98,8 +100,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -119,8 +122,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); PGTDE::append_to_file($stdout); @@ -135,8 +139,9 @@ # Things still work after a restart PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); # But now can't be changed to another global provider ($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);", extra_params => ['-a']); @@ -161,8 +166,10 @@ $stdout = $node->safe_psql('postgres', 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;', extra_params => ['-a']); PGTDE::append_to_file($stdout); -$rt_value = $node->stop(); +PGTDE::append_to_file("-- server restart"); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index d6007f6428fee..1adfa99b58cb2 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -56,9 +56,8 @@ sub resp_hello { print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -my $rt_value = $node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +my $rt_value = $node->start(); +ok($rt_value == 1, "Start Server"); my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); @@ -77,8 +76,9 @@ sub resp_hello { PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index f9e55b2f33d70..cbafeb4557e95 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -40,8 +40,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index d9d970b57d05d..776ed3be7e58b 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -92,8 +92,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 38a7cfccac279..776635c1382ce 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -64,9 +64,8 @@ sub resp_url { print $conf "shared_preload_libraries = 'pg_tde'\n"; close $conf; -my $rt_value = $node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +my $rt_value = $node->start(); +ok($rt_value == 1, "Start Server"); my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); @@ -85,8 +84,9 @@ sub resp_url { PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); PGTDE::append_to_file($stdout); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index fb574d34566eb..6b960af82594b 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -109,8 +109,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); sub verify_table { diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 0bbd3d1c11937..6a88bd8e4661e 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -64,8 +64,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); # Verify $stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); @@ -90,8 +91,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); # Verify (undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); @@ -108,8 +110,9 @@ move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); # Verify $stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); @@ -149,8 +152,9 @@ PGTDE::append_to_file($stdout); PGTDE::append_to_file("-- server restart"); -$rt_value = $node->stop(); +$node->stop(); $rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); # Verify (undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 19f435e287eb8..ee47bcb3c80aa 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -90,5 +90,5 @@ psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key DROP TABLE test_enc; ALTER SYSTEM RESET pg_tde.inherit_global_providers; +-- server restart DROP EXTENSION pg_tde CASCADE; - From b190cd0985b0daa4fffc9d87f59229df5c7d256a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 08:40:08 +0200 Subject: [PATCH 058/796] Add test helper to TAP tests which runs psql and appends This simplifies working with tests a lot since now we will also always get the errors from failed queries directly in the test output instead of it being truncated when the TAP tests aborts due to the query failing. There is still a good case for why we should instead write idiomatic TAP tests but this at least does a lot to improve the expereince of people who have to work with these tests without changing the way the tests work. Plus that the code is cleaner now so it should be easier to move away from this way of testing in the future. --- contrib/pg_tde/t/001_basic.pl | 35 ++-- contrib/pg_tde/t/002_rotate_key.pl | 137 +++++--------- contrib/pg_tde/t/003_remote_config.pl | 27 +-- contrib/pg_tde/t/004_file_config.pl | 27 +-- contrib/pg_tde/t/005_multiple_extensions.pl | 1 - contrib/pg_tde/t/006_remote_vault_config.pl | 27 +-- contrib/pg_tde/t/007_tde_heap.pl | 96 ++++------ contrib/pg_tde/t/008_key_rotate_tablespace.pl | 46 ++--- contrib/pg_tde/t/009_wal_encrypt.pl | 57 ++---- contrib/pg_tde/t/010_change_key_provider.pl | 173 ++++++------------ contrib/pg_tde/t/011_unlogged_tables.pl | 25 +-- contrib/pg_tde/t/expected/001_basic.out | 7 + contrib/pg_tde/t/expected/002_rotate_key.out | 7 +- .../pg_tde/t/expected/003_remote_config.out | 4 + contrib/pg_tde/t/expected/004_file_config.out | 4 + .../t/expected/006_remote_vault_config.out | 4 + contrib/pg_tde/t/expected/007_tde_heap.out | 9 + .../t/expected/008_key_rotate_tablespace.out | 21 +-- .../t/expected/010_change_key_provider.out | 3 +- contrib/pg_tde/t/pgtde.pm | 15 ++ 20 files changed, 269 insertions(+), 456 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 97a5623cd2c65..0bd3468b38725 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -19,41 +19,33 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';', extra_params => ['-a']); -ok($cmdret == 0, "SELECT PGTDE VERSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';'); -$rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -ok($rt_value == 3, "Failing query"); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (\'foobar\'),(\'barfoo\');'); + +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Verify that we can't see the data in the file my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); @@ -68,12 +60,9 @@ $strings .= `strings $tablefile | grep foo`; PGTDE::append_to_file($strings); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc;'); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 436892ba2f1c1..d0ddf6c29923b 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -19,123 +19,86 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -$rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -ok($rt_value == 3, "Failing query"); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5),(6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5),(6);'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Rotate key -$stdout = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # TODO: add method to query current info # And maybe debug tools to show what's in a file keyring? # Again rotate key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;'); # Things still work after a restart PGTDE::append_to_file("-- server restart"); @@ -144,36 +107,24 @@ ok($rt_value == 1, "Restart Server"); # But now can't be changed to another global provider -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);", extra_params => ['-a']); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); - -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); +PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); + +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc;'); + +PGTDE::psql($node, 'postgres', 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); $node->stop(); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 1adfa99b58cb2..d8636044432ea 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -59,36 +59,27 @@ sub resp_hello { my $rt_value = $node->start(); ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc2;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index cbafeb4557e95..9b924874edb40 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -23,36 +23,27 @@ my $rt_value = $node->start(); ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 776ed3be7e58b..df393fff90de3 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -19,7 +19,6 @@ my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; -# UPDATE postgresql.conf to include/load pg_stat_monitor library open my $conf, '>>', "$pgdata/postgresql.conf"; print $conf "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'\n"; print $conf "pg_stat_monitor.pgsm_bucket_time = 360000\n"; diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 776635c1382ce..01c2f412469c8 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -67,36 +67,27 @@ sub resp_url { my $rt_value = $node->start(); ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');", extra_params => ['-a']); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc2;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 6b960af82594b..d84a459121443 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -26,87 +26,67 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -$rt_value = $node->psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -ok($rt_value == 3, "Failing query"); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');", extra_params => ['-a']); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); ######################### test_enc1 (simple create table w tde_heap) -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc1 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); ######################### test_enc2 (create heap + alter to tde_heap) -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc2 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap;'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); ######################### test_enc3 (default_table_access_method) -$stdout = $node->safe_psql('postgres', 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc3 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc3 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc3 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc3 ORDER BY id ASC;'); ######################### test_enc4 (create heap + alter default) -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap;', extra_params => ['-a']); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc4 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT;', extra_params => ['-a']); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc4 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc4 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT;'); + +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc4 ORDER BY id ASC;'); ######################### test_enc5 (create tde_heap + truncate) -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'CHECKPOINT;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CHECKPOINT;'); -$stdout = $node->safe_psql('postgres', 'TRUNCATE test_enc5;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'TRUNCATE test_enc5;'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;'); PGTDE::append_to_file("-- server restart"); $node->stop(); @@ -123,8 +103,7 @@ sub verify_table $tablefile .= '/'; $tablefile .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\''.$table.'\');'); - $stdout = $node->safe_psql('postgres', 'SELECT * FROM ' . $table . ' ORDER BY id ASC;', extra_params => ['-a']); - PGTDE::append_to_file($stdout); + PGTDE::psql($node, 'postgres', 'SELECT * FROM ' . $table . ' ORDER BY id ASC;'); my $strings = 'TABLEFILE FOR ' . $table . ' FOUND: '; $strings .= `(ls $tablefile >/dev/null && echo -n yes) || echo -n no`; @@ -180,24 +159,13 @@ sub verify_table $strings .= `strings $tablefile4 | grep foo`; PGTDE::append_to_file($strings); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc2;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc3;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc4;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc5;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc3;'); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc4;'); +PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc5;'); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 7e563b31def22..fb73d2b26a3ab 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -9,8 +9,6 @@ PGTDE::setup_files_dir(basename($0)); -my ($cmdret, $stdout); - my $node = PGTDE->pgtde_init_pg(); my $pgdata = $node->data_dir; @@ -21,57 +19,43 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -$node->safe_psql('postgres', - q{ -SET allow_in_place_tablespaces = true; -CREATE TABLESPACE test_tblspace LOCATION ''; -CREATE DATABASE tbc TABLESPACE = test_tblspace; -}); +PGTDE::psql($node, 'postgres', "SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION '';"); +PGTDE::psql($node, 'postgres', 'CREATE DATABASE tbc TABLESPACE = test_tblspace;'); -$stdout = $node->safe_psql('tbc', - q{ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +PGTDE::psql($node, 'tbc', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'tbc', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); +PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); +PGTDE::psql($node, 'tbc', " CREATE TABLE country_table ( country_id serial primary key, country_name text unique not null, continent text not null ) USING tde_heap; +"); +PGTDE::psql($node, 'tbc', " INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), ('UK', 'Europe'), ('USA', 'North America'); +"); -SELECT * FROM country_table; - -}, extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'tbc', 'SELECT * FROM country_table;'); -$cmdret = $node->psql('tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');", extra_params => ['-a']); -ok($cmdret == 0, "ROTATE KEY"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');"); PGTDE::append_to_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('tbc', 'SELECT * FROM country_table;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'tbc', 'SELECT * FROM country_table;'); -$stdout = $node->safe_psql('tbc', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); -ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'tbc', 'DROP EXTENSION pg_tde CASCADE;'); -$stdout = $node->safe_psql('postgres', q{ -DROP DATABASE tbc; -DROP TABLESPACE test_tblspace; -}, extra_params => ['-a']); -ok($cmdret == 0, "DROP DATABSE"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP DATABASE tbc;'); +PGTDE::psql($node, 'postgres', 'DROP TABLESPACE test_tblspace;'); $node->stop(); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 39b9fa4d06926..233594c1674a0 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -22,82 +22,63 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my $stdout = $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');"); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');"); -$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); -$stdout = $node->safe_psql('postgres', "SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding');"); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id));', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id));'); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (1), (2);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (1), (2);'); -$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = off;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = off;'); PGTDE::append_to_file("-- server restart without wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (3), (4);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (3), (4);'); -$stdout = $node->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);'); PGTDE::append_to_file("-- server restart with still wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', "SHOW pg_tde.wal_encrypt;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_wal (k) VALUES (7), (8);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (7), (8);'); -$stdout = $node->safe_psql('postgres', "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);"); -$stdout = $node->safe_psql('postgres', "SELECT pg_drop_replication_slot('tde_slot');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_drop_replication_slot('tde_slot');"); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); $node->stop(); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 6a88bd8e4661e..101c67c6b776c 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -25,43 +25,28 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); + +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Change provider and move file PGTDE::append_to_file("-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); move('/tmp/change_key_provider_1.per', '/tmp/change_key_provider_2.per'); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_file("-- server restart"); $node->stop(); @@ -69,26 +54,17 @@ ok($rt_value == 1, "Restart Server"); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Change provider and do not move file -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_database_key_providers();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_file("-- server restart"); $node->stop(); @@ -96,15 +72,9 @@ ok($rt_value == 1, "Restart Server"); # Verify -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -(undef, $stdout, $stderr) = $node->psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_file("-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); @@ -115,41 +85,26 @@ ok($rt_value == 1, "Restart Server"); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -(undef, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); # Change provider and generate a new principal key -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); + +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"); PGTDE::append_to_file("-- server restart"); $node->stop(); @@ -157,33 +112,19 @@ ok($rt_value == 1, "Restart Server"); # Verify -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -(undef, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -(undef, $stdout, $stderr) = $node->psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); -(undef, $stdout, $stderr) = $node->psql('postgres', 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); - -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); +PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"); # Verify -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_verify_key();", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_is_encrypted('test_enc');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); - -(undef, $stdout, $stderr) = $node->psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); -PGTDE::append_to_file($stderr); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); $node->stop(); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 5c81b92233497..b267ff227067a 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -19,22 +19,15 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); -ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); -$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); -$stdout = $node->safe_psql('postgres', "CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap;"); -$stdout = $node->safe_psql('postgres', "INSERT INTO t SELECT generate_series(1, 4);", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "INSERT INTO t SELECT generate_series(1, 4);"); -$stdout = $node->safe_psql('postgres', "CHECKPOINT;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "CHECKPOINT;"); PGTDE::append_to_file("-- kill -9"); $node->kill9(); @@ -43,11 +36,9 @@ $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -$stdout = $node->safe_psql('postgres', "TABLE t;", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "TABLE t;"); -$stdout = $node->safe_psql('postgres', "INSERT INTO t SELECT generate_series(1, 4);", extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::psql($node, 'postgres', "INSERT INTO t SELECT generate_series(1, 4);"); $node->stop(); diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index 0ea866e83ebc8..21e08d26830ee 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -1,7 +1,14 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; pg_tde|1.0-rc +CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; +psql::1: ERROR: principal key not configured +HINT: create one using pg_tde_set_key before using encrypted tables -- server restart +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc ORDER BY id ASC; diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index ee47bcb3c80aa..8e178cd72b297 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -1,4 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; +psql::1: ERROR: principal key not configured +HINT: create one using pg_tde_set_key before using encrypted tables -- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 @@ -18,7 +21,8 @@ INSERT INTO test_enc (k) VALUES (5),(6); SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 -0 +SELECT pg_tde_set_key_using_database_key_provider('rotated-key1'); + SELECT * FROM test_enc ORDER BY id ASC; 1|5 2|6 @@ -75,6 +79,7 @@ SELECT * FROM test_enc ORDER BY id ASC; 2|6 ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF; -- server restart +SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -1|file-2|rotated-keyX diff --git a/contrib/pg_tde/t/expected/003_remote_config.out b/contrib/pg_tde/t/expected/003_remote_config.out index 35bcd3f49651a..b4c5cc63fc59a 100644 --- a/contrib/pg_tde/t/expected/003_remote_config.out +++ b/contrib/pg_tde/t/expected/003_remote_config.out @@ -1,4 +1,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' )); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc2 (k) VALUES (5),(6); SELECT * FROM test_enc2 ORDER BY id ASC; diff --git a/contrib/pg_tde/t/expected/004_file_config.out b/contrib/pg_tde/t/expected/004_file_config.out index e5f705d3137c3..c9e6a89524524 100644 --- a/contrib/pg_tde/t/expected/004_file_config.out +++ b/contrib/pg_tde/t/expected/004_file_config.out @@ -1,4 +1,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' )); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc1 (k) VALUES (5),(6); SELECT * FROM test_enc1 ORDER BY id ASC; diff --git a/contrib/pg_tde/t/expected/006_remote_vault_config.out b/contrib/pg_tde/t/expected/006_remote_vault_config.out index 35bcd3f49651a..4110b74664e57 100644 --- a/contrib/pg_tde/t/expected/006_remote_vault_config.out +++ b/contrib/pg_tde/t/expected/006_remote_vault_config.out @@ -1,4 +1,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider'); + CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc2 (k) VALUES (5),(6); SELECT * FROM test_enc2 ORDER BY id ASC; diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/007_tde_heap.out index 867f714c9fb16..3d596657d0b92 100644 --- a/contrib/pg_tde/t/expected/007_tde_heap.out +++ b/contrib/pg_tde/t/expected/007_tde_heap.out @@ -1,5 +1,12 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; +psql::1: ERROR: principal key not configured +HINT: create one using pg_tde_set_key before using encrypted tables -- server restart +SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +1 +SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc1 (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc1 ORDER BY id ASC; @@ -16,7 +23,9 @@ INSERT INTO test_enc3 (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc3 ORDER BY id ASC; 1|foobar 2|barfoo +CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap; INSERT INTO test_enc4 (k) VALUES ('foobar'),('barfoo'); +SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT; SELECT * FROM test_enc4 ORDER BY id ASC; 1|foobar 2|barfoo diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index 58600feeeb367..222742b1d84ac 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -1,3 +1,5 @@ +SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION ''; +CREATE DATABASE tbc TABLESPACE = test_tblspace; CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); 1 @@ -16,29 +18,14 @@ SELECT * FROM country_table; 1|Japan|Asia 2|UK|Europe 3|USA|North America -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -1 -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault'); -CREATE TABLE country_table ( - country_id serial primary key, - country_name text unique not null, - continent text not null -) USING tde_heap; -INSERT INTO country_table (country_name, continent) - VALUES ('Japan', 'Asia'), - ('UK', 'Europe'), - ('USA', 'North America'); -SELECT * FROM country_table; -1|Japan|Asia -2|UK|Europe -3|USA|North America -- server restart SELECT * FROM country_table; 1|Japan|Asia 2|UK|Europe 3|USA|North America DROP EXTENSION pg_tde CASCADE; +psql::1: NOTICE: drop cascades to table country_table DROP DATABASE tbc; DROP TABLESPACE test_tblspace; diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index d7cfbd2a394b0..0849ff96822d7 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -66,7 +66,8 @@ psql::1: NOTICE: drop cascades to table test_enc CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); 1 -0 +SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); SELECT pg_tde_verify_key(); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 0d052ae6ee83b..21aa93864ecda 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -51,6 +51,21 @@ sub pgtde_init_pg return $node; } +sub psql +{ + my ($node, $dbname, $sql) = @_; + + my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, extra_params => ['-a']); + + if ($stdout ne '') { + append_to_file($stdout); + } + + if ($stderr ne '') { + append_to_file($stderr); + } +} + sub append_to_file { my ($str) = @_; From 908d0776ba1c4a7b5ddd15aef9217ef6256b5916 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 09:50:11 +0200 Subject: [PATCH 059/796] Rename test helper append_to_file() to append_to_result_file() This way we can also import PostgreSQL's test utilities into the current namespace. --- contrib/pg_tde/t/001_basic.pl | 8 +++--- contrib/pg_tde/t/002_rotate_key.pl | 14 +++++----- contrib/pg_tde/t/003_remote_config.pl | 2 +- contrib/pg_tde/t/004_file_config.pl | 2 +- contrib/pg_tde/t/005_multiple_extensions.pl | 16 +++++------ contrib/pg_tde/t/006_remote_vault_config.pl | 2 +- contrib/pg_tde/t/007_tde_heap.pl | 22 +++++++-------- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 2 +- contrib/pg_tde/t/009_wal_encrypt.pl | 8 +++--- contrib/pg_tde/t/010_change_key_provider.pl | 12 ++++---- contrib/pg_tde/t/011_unlogged_tables.pl | 4 +-- contrib/pg_tde/t/pgtde.pm | 28 +++++++++---------- 12 files changed, 59 insertions(+), 61 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 0bd3468b38725..87146f1cdaf8a 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -25,7 +25,7 @@ PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -40,7 +40,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -54,11 +54,11 @@ my $strings = 'TABLEFILE FOUND: '; $strings .= `(ls $tablefile >/dev/null && echo yes) || echo no`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile | grep foo`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc;'); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index d0ddf6c29923b..38cd31075706f 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -23,7 +23,7 @@ PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -47,7 +47,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -60,7 +60,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -73,7 +73,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -89,7 +89,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -101,7 +101,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;'); # Things still work after a restart -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -119,7 +119,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index d8636044432ea..2cfd35e9052a0 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -70,7 +70,7 @@ sub resp_hello { PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 9b924874edb40..8fe8178ce5508 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -34,7 +34,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index df393fff90de3..309d68a3f2ed3 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -44,7 +44,7 @@ # Create pg_tde extension ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); # Create Other extensions ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS pgaudit;', extra_params => ['-a']); @@ -82,24 +82,24 @@ $rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); $stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); # Print PGSM settings ($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT name, setting, unit, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, pending_restart FROM pg_settings WHERE name='pg_stat_monitor.pgsm_query_shared_buffer';", extra_params => ['-a', '-Pformat=aligned','-Ptuples_only=off']); @@ -129,7 +129,7 @@ $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); -PGTDE::append_to_file($stdout); +PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_stat_monitor;', extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 01c2f412469c8..db50f2e4a69bd 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -78,7 +78,7 @@ sub resp_url { PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index d84a459121443..f2018382f6586 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -30,7 +30,7 @@ PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -88,14 +88,14 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); sub verify_table { - PGTDE::append_to_file('###########################'); + PGTDE::append_to_result_file('###########################'); my ($table) = @_; @@ -107,11 +107,11 @@ sub verify_table my $strings = 'TABLEFILE FOR ' . $table . ' FOUND: '; $strings .= `(ls $tablefile >/dev/null && echo -n yes) || echo -n no`; - PGTDE::append_to_file($strings); + PGTDE::append_to_result_file($strings); $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile | grep foo`; - PGTDE::append_to_file($strings); + PGTDE::append_to_result_file($strings); } verify_table('test_enc1'); @@ -127,11 +127,11 @@ sub verify_table my $strings = 'TABLEFILE2 FOUND: '; $strings .= `(ls $tablefile2 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile2 | grep foo`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); # Verify that we can't see the data in the file my $tablefile3 = $node->safe_psql('postgres', 'SHOW data_directory;'); @@ -140,11 +140,11 @@ sub verify_table $strings = 'TABLEFILE3 FOUND: '; $strings .= `(ls $tablefile3 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile3 | grep foo`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); # Verify that we can't see the data in the file my $tablefile4 = $node->safe_psql('postgres', 'SHOW data_directory;'); @@ -153,11 +153,11 @@ sub verify_table $strings = 'TABLEFILE4 FOUND: '; $strings .= `(ls $tablefile4 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile4 | grep foo`; -PGTDE::append_to_file($strings); +PGTDE::append_to_result_file($strings); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index fb73d2b26a3ab..a7e93a693fc53 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -45,7 +45,7 @@ PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');"); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 233594c1674a0..4528a34445427 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -30,7 +30,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); -PGTDE::append_to_file("-- server restart with wal encryption"); +PGTDE::append_to_result_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -45,7 +45,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = off;'); -PGTDE::append_to_file("-- server restart without wal encryption"); +PGTDE::append_to_result_file("-- server restart without wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -56,7 +56,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); -PGTDE::append_to_file("-- server restart with wal encryption"); +PGTDE::append_to_result_file("-- server restart with wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -65,7 +65,7 @@ PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);'); -PGTDE::append_to_file("-- server restart with still wal encryption"); +PGTDE::append_to_result_file("-- server restart with still wal encryption"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 101c67c6b776c..c94f82089b4a5 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -39,7 +39,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Change provider and move file -PGTDE::append_to_file("-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); +PGTDE::append_to_result_file("-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); move('/tmp/change_key_provider_1.per', '/tmp/change_key_provider_2.per'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); @@ -48,7 +48,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -66,7 +66,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -76,10 +76,10 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::append_to_file("-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); +PGTDE::append_to_result_file("-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); @@ -106,7 +106,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"); -PGTDE::append_to_file("-- server restart"); +PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index b267ff227067a..fcfa26addb672 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -29,10 +29,10 @@ PGTDE::psql($node, 'postgres', "CHECKPOINT;"); -PGTDE::append_to_file("-- kill -9"); +PGTDE::append_to_result_file("-- kill -9"); $node->kill9(); -PGTDE::append_to_file("-- server start"); +PGTDE::append_to_result_file("-- server start"); $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 21aa93864ecda..0a313ad151832 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -27,9 +27,15 @@ BEGIN { $PG_MAJOR_VERSION =~ s/^\s+|\s+$//g; if ($PG_MAJOR_VERSION >= 15) { - eval { require PostgreSQL::Test::Cluster; }; + eval { + require PostgreSQL::Test::Cluster; + import PostgreSQL::Test::Utils; + } } else { - eval { require PostgresNode; }; + eval { + require PostgresNode; + import TestLib; + } } } @@ -58,34 +64,26 @@ sub psql my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, extra_params => ['-a']); if ($stdout ne '') { - append_to_file($stdout); + append_to_result_file($stdout); } if ($stderr ne '') { - append_to_file($stderr); + append_to_result_file($stderr); } } -sub append_to_file +sub append_to_result_file { my ($str) = @_; - if ($PG_MAJOR_VERSION >= 15) { - PostgreSQL::Test::Utils::append_to_file($out_filename_with_path, $str . "\n"); - } else { - TestLib::append_to_file($out_filename_with_path, $str . "\n"); - } + append_to_file($out_filename_with_path, $str . "\n"); } sub append_to_debug_file { my ($str) = @_; - if ($PG_MAJOR_VERSION >= 15) { - PostgreSQL::Test::Utils::append_to_file($debug_out_filename_with_path, $str . "\n"); - } else { - TestLib::append_to_file($debug_out_filename_with_path, $str . "\n"); - } + append_to_file($debug_out_filename_with_path, $str . "\n"); } sub setup_files_dir From 832cbb4f4dc069de737084b51de01a5b66c0dd13 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 17:57:09 +0200 Subject: [PATCH 060/796] Convert output of TAP tests to be more like pg_regress This improves readbility of diffs a lot. --- contrib/pg_tde/t/expected/001_basic.out | 32 ++- contrib/pg_tde/t/expected/002_rotate_key.out | 164 +++++++++++++--- .../pg_tde/t/expected/003_remote_config.out | 26 ++- contrib/pg_tde/t/expected/004_file_config.out | 26 ++- .../t/expected/006_remote_vault_config.out | 26 ++- contrib/pg_tde/t/expected/007_tde_heap.out | 90 +++++++-- .../t/expected/008_key_rotate_tablespace.out | 34 +++- contrib/pg_tde/t/expected/009_wal_encrypt.out | 84 +++++--- .../t/expected/010_change_key_provider.out | 184 +++++++++++++++--- .../pg_tde/t/expected/011_unlogged_tables.out | 14 +- contrib/pg_tde/t/pgtde.pm | 2 +- 11 files changed, 547 insertions(+), 135 deletions(-) diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index 21e08d26830ee..c1e385741b99f 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -1,23 +1,43 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; -pg_tde|1.0-rc + extname | extversion +---------+------------ + pg_tde | 1.0-rc +(1 row) + CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + -- server restart SELECT * FROM test_enc ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + TABLEFILE FOUND: yes CONTAINS FOO (should be empty): diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 8e178cd72b297..12129902d2d3d 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -4,92 +4,196 @@ psql::1: ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per'); -2 + pg_tde_add_database_key_provider_file +--------------------------------------- + 2 +(1 row) + SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per'); --1 + pg_tde_add_global_key_provider_file +------------------------------------- + -1 +(1 row) + SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per'); --2 + pg_tde_add_global_key_provider_file +------------------------------------- + -2 +(1 row) + SELECT pg_tde_list_all_database_key_providers(); -(1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}") -(2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") + pg_tde_list_all_database_key_providers +------------------------------------------------------------------------------------------ + (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}") + (2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") +(2 rows) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5),(6); SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_set_key_using_database_key_provider('rotated-key1'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -1|file-vault|rotated-key1 + key_provider_id | key_provider_name | key_name +-----------------+-------------------+-------------- + 1 | file-vault | rotated-key1 +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -2|file-2|rotated-key2 + key_provider_id | key_provider_name | key_name +-----------------+-------------------+-------------- + 2 | file-2 | rotated-key2 +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); --2|file-3|rotated-key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -2 | file-3 | rotated-key +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); --1|file-2|rotated-keyX + key_provider_id | key_provider_name | key_name +-----------------+-------------------+-------------- + -1 | file-2 | rotated-keyX +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT * FROM test_enc ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF; -- server restart SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); --1|file-2|rotated-keyX + key_provider_id | key_provider_name | key_name +-----------------+-------------------+-------------- + -1 | file-2 | rotated-keyX +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -2|file-2|rotated-key2 + key_provider_id | key_provider_name | key_name +-----------------+-------------------+-------------- + 2 | file-2 | rotated-key2 +(1 row) + SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key diff --git a/contrib/pg_tde/t/expected/003_remote_config.out b/contrib/pg_tde/t/expected/003_remote_config.out index b4c5cc63fc59a..fc4208ba360ed 100644 --- a/contrib/pg_tde/t/expected/003_remote_config.out +++ b/contrib/pg_tde/t/expected/003_remote_config.out @@ -1,16 +1,32 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' )); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc2 (k) VALUES (5),(6); SELECT * FROM test_enc2 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT * FROM test_enc2 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP TABLE test_enc2; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/004_file_config.out b/contrib/pg_tde/t/expected/004_file_config.out index c9e6a89524524..878e2d3621ea0 100644 --- a/contrib/pg_tde/t/expected/004_file_config.out +++ b/contrib/pg_tde/t/expected/004_file_config.out @@ -1,16 +1,32 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' )); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc1 (k) VALUES (5),(6); SELECT * FROM test_enc1 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT * FROM test_enc1 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP TABLE test_enc1; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/006_remote_vault_config.out b/contrib/pg_tde/t/expected/006_remote_vault_config.out index 4110b74664e57..8fe7b12360bc0 100644 --- a/contrib/pg_tde/t/expected/006_remote_vault_config.out +++ b/contrib/pg_tde/t/expected/006_remote_vault_config.out @@ -1,16 +1,32 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL); -1 + pg_tde_add_database_key_provider_vault_v2 +------------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc2 (k) VALUES (5),(6); SELECT * FROM test_enc2 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT * FROM test_enc2 ORDER BY id ASC; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP TABLE test_enc2; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/007_tde_heap.out index 3d596657d0b92..6a5aef32bf731 100644 --- a/contrib/pg_tde/t/expected/007_tde_heap.out +++ b/contrib/pg_tde/t/expected/007_tde_heap.out @@ -4,68 +4,116 @@ psql::1: ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc1 (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc1 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)); INSERT INTO test_enc2 (k) VALUES ('foobar'),('barfoo'); ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap; SELECT * FROM test_enc2 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)); INSERT INTO test_enc3 (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc3 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap; INSERT INTO test_enc4 (k) VALUES ('foobar'),('barfoo'); SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT; SELECT * FROM test_enc4 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc5 (k) VALUES ('foobar'),('barfoo'); CHECKPOINT; TRUNCATE test_enc5; INSERT INTO test_enc5 (k) VALUES ('foobar'),('barfoo'); SELECT * FROM test_enc5 ORDER BY id ASC; -3|foobar -4|barfoo + id | k +----+-------- + 3 | foobar + 4 | barfoo +(2 rows) + -- server restart ########################### SELECT * FROM test_enc1 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + TABLEFILE FOR test_enc1 FOUND: yes CONTAINS FOO (should be empty): ########################### SELECT * FROM test_enc2 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + TABLEFILE FOR test_enc2 FOUND: yes CONTAINS FOO (should be empty): ########################### SELECT * FROM test_enc3 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + TABLEFILE FOR test_enc3 FOUND: yes CONTAINS FOO (should be empty): ########################### SELECT * FROM test_enc4 ORDER BY id ASC; -1|foobar -2|barfoo + id | k +----+-------- + 1 | foobar + 2 | barfoo +(2 rows) + TABLEFILE FOR test_enc4 FOUND: yes CONTAINS FOO (should be empty): ########################### SELECT * FROM test_enc5 ORDER BY id ASC; -3|foobar -4|barfoo + id | k +----+-------- + 3 | foobar + 4 | barfoo +(2 rows) + TABLEFILE FOR test_enc5 FOUND: yes CONTAINS FOO (should be empty): TABLEFILE2 FOUND: yes diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index 222742b1d84ac..7d88a74e1c47d 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -2,8 +2,16 @@ SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION CREATE DATABASE tbc TABLESPACE = test_tblspace; CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE country_table ( country_id serial primary key, @@ -15,16 +23,28 @@ INSERT INTO country_table (country_name, continent) ('UK', 'Europe'), ('USA', 'North America'); SELECT * FROM country_table; -1|Japan|Asia -2|UK|Europe -3|USA|North America + country_id | country_name | continent +------------+--------------+--------------- + 1 | Japan | Asia + 2 | UK | Europe + 3 | USA | North America +(3 rows) + SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) -- server restart SELECT * FROM country_table; -1|Japan|Asia -2|UK|Europe -3|USA|North America + country_id | country_name | continent +------------+--------------+--------------- + 1 | Japan | Asia + 2 | UK | Europe + 3 | USA | North America +(3 rows) + DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table country_table DROP DATABASE tbc; diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index 7d7c57268913d..c0df1e2a03110 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -1,49 +1,85 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per'); --1 + pg_tde_add_global_key_provider_file +------------------------------------- + -1 +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption SHOW pg_tde.wal_encrypt; -on + pg_tde.wal_encrypt +-------------------- + on +(1 row) + SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding'); -tde_slot + slot_name +----------- + tde_slot +(1 row) + CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id)); INSERT INTO test_wal (k) VALUES (1), (2); ALTER SYSTEM SET pg_tde.wal_encrypt = off; -- server restart without wal encryption SHOW pg_tde.wal_encrypt; -off + pg_tde.wal_encrypt +-------------------- + off +(1 row) + INSERT INTO test_wal (k) VALUES (3), (4); ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption SHOW pg_tde.wal_encrypt; -on + pg_tde.wal_encrypt +-------------------- + on +(1 row) + INSERT INTO test_wal (k) VALUES (5), (6); -- server restart with still wal encryption SHOW pg_tde.wal_encrypt; -on + pg_tde.wal_encrypt +-------------------- + on +(1 row) + INSERT INTO test_wal (k) VALUES (7), (8); SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL); -BEGIN 739 -COMMIT 739 -BEGIN 740 -table public.test_wal: INSERT: id[integer]:1 k[integer]:1 -table public.test_wal: INSERT: id[integer]:2 k[integer]:2 -COMMIT 740 -BEGIN 741 -table public.test_wal: INSERT: id[integer]:3 k[integer]:3 -table public.test_wal: INSERT: id[integer]:4 k[integer]:4 -COMMIT 741 -BEGIN 742 -table public.test_wal: INSERT: id[integer]:5 k[integer]:5 -table public.test_wal: INSERT: id[integer]:6 k[integer]:6 -COMMIT 742 -BEGIN 743 -table public.test_wal: INSERT: id[integer]:7 k[integer]:7 -table public.test_wal: INSERT: id[integer]:8 k[integer]:8 -COMMIT 743 + data +----------------------------------------------------------- + BEGIN 739 + COMMIT 739 + BEGIN 740 + table public.test_wal: INSERT: id[integer]:1 k[integer]:1 + table public.test_wal: INSERT: id[integer]:2 k[integer]:2 + COMMIT 740 + BEGIN 741 + table public.test_wal: INSERT: id[integer]:3 k[integer]:3 + table public.test_wal: INSERT: id[integer]:4 k[integer]:4 + COMMIT 741 + BEGIN 742 + table public.test_wal: INSERT: id[integer]:5 k[integer]:5 + table public.test_wal: INSERT: id[integer]:6 k[integer]:6 + COMMIT 742 + BEGIN 743 + table public.test_wal: INSERT: id[integer]:7 k[integer]:7 + table public.test_wal: INSERT: id[integer]:8 k[integer]:8 + COMMIT 743 +(18 rows) + SELECT pg_drop_replication_slot('tde_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index 0849ff96822d7..ca988c6259919 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -1,50 +1,122 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_list_all_database_key_providers(); -(1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_1.per""}") + pg_tde_list_all_database_key_providers +-------------------------------------------------------------------------------------------- + (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_1.per""}") +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per'); -1 + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 +(1 row) + SELECT pg_tde_list_all_database_key_providers(); -(1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_2.per""}") + pg_tde_list_all_database_key_providers +-------------------------------------------------------------------------------------------- + (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_2.per""}") +(1 row) + SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); -1 + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 +(1 row) + SELECT pg_tde_list_all_database_key_providers(); -(1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_3.per""}") + pg_tde_list_all_database_key_providers +-------------------------------------------------------------------------------------------- + (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_3.per""}") +(1 row) + SELECT pg_tde_verify_key(); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + -- server restart SELECT pg_tde_verify_key(); psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 @@ -55,30 +127,66 @@ psql::1: ERROR: failed to retrieve principal key test-key from keyring w -- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per -- server restart SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc (k) VALUES (5), (6); SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); -1 + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 +(1 row) + -- server restart SELECT pg_tde_verify_key(); psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file @@ -89,13 +197,29 @@ psql::1: ERROR: Failed to verify principal key header for key test-key, CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); -1 + pg_tde_change_database_key_provider_file +------------------------------------------ + 1 +(1 row) + SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) SELECT pg_tde_is_encrypted('test_enc'); -t + pg_tde_is_encrypted +--------------------- + t +(1 row) + SELECT * FROM test_enc ORDER BY id; -1|5 -2|6 + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc diff --git a/contrib/pg_tde/t/expected/011_unlogged_tables.out b/contrib/pg_tde/t/expected/011_unlogged_tables.out index 031fd4f80b24d..71c52786b134f 100644 --- a/contrib/pg_tde/t/expected/011_unlogged_tables.out +++ b/contrib/pg_tde/t/expected/011_unlogged_tables.out @@ -1,7 +1,15 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); -1 + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap; INSERT INTO t SELECT generate_series(1, 4); @@ -9,4 +17,8 @@ CHECKPOINT; -- kill -9 -- server start TABLE t; + x +--- +(0 rows) + INSERT INTO t SELECT generate_series(1, 4); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 0a313ad151832..989b41089c3bf 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -61,7 +61,7 @@ sub psql { my ($node, $dbname, $sql) = @_; - my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, extra_params => ['-a']); + my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, extra_params => ['-a', '-Pformat=aligned', '-Ptuples_only=off']); if ($stdout ne '') { append_to_result_file($stdout); From cb80b20a434e09a7cf3d7de43a60b1f4e7eb5976 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Apr 2025 19:35:53 +0300 Subject: [PATCH 061/796] pfree tmp key when set principal key Pushing a principal key to the cache, we copy it to the shared mem. Hence, the palloced tmp version can be freed. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index f7c225372de4d..6b4f1174c2058 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -361,6 +361,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, LWLockRelease(lock_files); pfree(new_keyring); + pfree(new_principal_key); } /* From 8a7fc7acb5fc55f37b9ae6e7da88be25f67bf529 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 17:59:18 +0200 Subject: [PATCH 062/796] PG-1441 Clean up code formatting of pg_tde WAL resource manager --- contrib/pg_tde/src/access/pg_tde_xlog.c | 35 +++++++++++-------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index b34801f34762f..9bafa64577597 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -45,9 +45,6 @@ RegisterTdeRmgr(void) RegisterCustomRmgr(RM_TDERMGR_ID, &tdeheap_rmgr); } -/* - * TDE fork XLog - */ static void tdeheap_rmgr_redo(XLogReaderState *record) { @@ -71,21 +68,18 @@ tdeheap_rmgr_redo(XLogReaderState *record) extension_install_redo(xlrec); } - else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) { KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); redo_key_provider_info(xlrec); } - else if (info == XLOG_TDE_ROTATE_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); xl_tde_perform_rotate_key(xlrec); } - else if (info == XLOG_TDE_FREE_MAP_ENTRY) { RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); @@ -109,25 +103,25 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); } - if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) + else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); appendStringInfo(buf, "add tde principal key for db %u", xlrec->databaseId); } - if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) + else if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) { XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); appendStringInfo(buf, "tde extension install for db %u", xlrec->database_id); } - if (info == XLOG_TDE_ROTATE_KEY) + else if (info == XLOG_TDE_ROTATE_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); appendStringInfo(buf, "rotate principal key for %u", xlrec->databaseId); } - if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) + else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) { KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); @@ -138,14 +132,15 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) static const char * tdeheap_rmgr_identify(uint8 info) { - if ((info & ~XLR_INFO_MASK) == XLOG_TDE_ADD_RELATION_KEY) - return "XLOG_TDE_ADD_RELATION_KEY"; - - if ((info & ~XLR_INFO_MASK) == XLOG_TDE_ADD_PRINCIPAL_KEY) - return "XLOG_TDE_ADD_PRINCIPAL_KEY"; - - if ((info & ~XLR_INFO_MASK) == XLOG_TDE_EXTENSION_INSTALL_KEY) - return "XLOG_TDE_EXTENSION_INSTALL_KEY"; - - return NULL; + switch (info & ~XLR_INFO_MASK) + { + case XLOG_TDE_ADD_RELATION_KEY: + return "XLOG_TDE_ADD_RELATION_KEY"; + case XLOG_TDE_ADD_PRINCIPAL_KEY: + return "XLOG_TDE_ADD_PRINCIPAL_KEY"; + case XLOG_TDE_EXTENSION_INSTALL_KEY: + return "XLOG_TDE_EXTENSION_INSTALL_KEY"; + default: + return NULL; + } } From ee8c285ce6fc71a8be04157bb37195cd7770bf58 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 17:55:36 +0200 Subject: [PATCH 063/796] PG-1441 Change the name of the WAL resource manager Seems like we used a plaholder name so instead picked pg_tde since that is more than clear enough. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 9bafa64577597..c17a6954229f9 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -30,10 +30,8 @@ static void tdeheap_rmgr_redo(XLogReaderState *record); static void tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record); static const char *tdeheap_rmgr_identify(uint8 info); -#define RM_TDERMGR_NAME "test_tdeheap_custom_rmgr" - static const RmgrData tdeheap_rmgr = { - .rm_name = RM_TDERMGR_NAME, + .rm_name = "pg_tde", .rm_redo = tdeheap_rmgr_redo, .rm_desc = tdeheap_rmgr_desc, .rm_identify = tdeheap_rmgr_identify, From 35ff2fdca8c9d9fc057179482148fafa85cb1ddf Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 18:00:00 +0200 Subject: [PATCH 064/796] PG-1441 Improve pg_tde resource manager record types and decriptions While there is a quite big variation already among PostgreSQL's own record types and decriptions at least try not to invent something totally different. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index c17a6954229f9..b8609ca2ad59f 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -99,31 +99,31 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - appendStringInfo(buf, "add tde internal key for relation %u/%u", xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); + appendStringInfo(buf, "rel: %u/%u/%u", xlrec->mapEntry.spcOid, xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); - appendStringInfo(buf, "add tde principal key for db %u", xlrec->databaseId); + appendStringInfo(buf, "db: %u", xlrec->databaseId); } else if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) { XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); - appendStringInfo(buf, "tde extension install for db %u", xlrec->database_id); + appendStringInfo(buf, "db: %u", xlrec->database_id); } else if (info == XLOG_TDE_ROTATE_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); - appendStringInfo(buf, "rotate principal key for %u", xlrec->databaseId); + appendStringInfo(buf, "db: %u", xlrec->databaseId); } else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) { KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); - appendStringInfo(buf, "add key provider %s for %u", xlrec->provider.provider_name, xlrec->database_id); + appendStringInfo(buf, "db: %u, provider id: %d", xlrec->database_id, xlrec->provider.provider_id); } } @@ -133,11 +133,11 @@ tdeheap_rmgr_identify(uint8 info) switch (info & ~XLR_INFO_MASK) { case XLOG_TDE_ADD_RELATION_KEY: - return "XLOG_TDE_ADD_RELATION_KEY"; + return "ADD_RELATION_KEY"; case XLOG_TDE_ADD_PRINCIPAL_KEY: - return "XLOG_TDE_ADD_PRINCIPAL_KEY"; + return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_EXTENSION_INSTALL_KEY: - return "XLOG_TDE_EXTENSION_INSTALL_KEY"; + return "EXTENSION_INSTALL_KEY"; default: return NULL; } From 33d78260c5b7b91dd887e4781f90665a1102d5de Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 18:00:20 +0200 Subject: [PATCH 065/796] PG-1441 Add record type names and descriptions for all types Some resource manager record types were missing the name or the description. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index b8609ca2ad59f..d80edf1d8ca2a 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -125,6 +125,12 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "db: %u, provider id: %d", xlrec->database_id, xlrec->provider.provider_id); } + else if (info == XLOG_TDE_FREE_MAP_ENTRY) + { + RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); + + appendStringInfo(buf, "rel: %u/%u/%u", xlrec->spcOid, xlrec->dbOid, xlrec->relNumber); + } } static const char * @@ -138,6 +144,12 @@ tdeheap_rmgr_identify(uint8 info) return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_EXTENSION_INSTALL_KEY: return "EXTENSION_INSTALL_KEY"; + case XLOG_TDE_ROTATE_KEY: + return "ROTATE_KEY"; + case XLOG_TDE_ADD_KEY_PROVIDER_KEY: + return "ADD_KEY_PROVIDER_KEY"; + case XLOG_TDE_FREE_MAP_ENTRY: + return "FREE_MAP_ENTRY"; default: return NULL; } From ac535128473dbb89f6f262da56152f93b227b58c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 18:14:30 +0200 Subject: [PATCH 066/796] PG-1441 Further improve resource manager type names The type names were a a mess with diffeent naming convetions, e.g. KEY vs PRINCIPAL_KEY, so try to standardize on something sane. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 +- contrib/pg_tde/src/access/pg_tde_xlog.c | 32 +++++++++---------- contrib/pg_tde/src/catalog/tde_keyring.c | 2 +- .../pg_tde/src/include/access/pg_tde_xlog.h | 8 ++--- contrib/pg_tde/src/pg_tde.c | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 38c370bb84c66..b0b7fd972679b 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -764,7 +764,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p /* Insert the XLog record */ XLogBeginInsert(); XLogRegisterData((char *) xlrec, xlrec_size); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_KEY); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_PRINCIPAL_KEY); pfree(xlrec); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index d80edf1d8ca2a..083006dfb0e2f 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -60,25 +60,25 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_save_principal_key_redo(mkey); } - else if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) + else if (info == XLOG_TDE_INSTALL_EXTENSION) { XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); extension_install_redo(xlrec); } - else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) + else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); redo_key_provider_info(xlrec); } - else if (info == XLOG_TDE_ROTATE_KEY) + else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); xl_tde_perform_rotate_key(xlrec); } - else if (info == XLOG_TDE_FREE_MAP_ENTRY) + else if (info == XLOG_TDE_REMOVE_RELATION_KEY) { RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); @@ -107,25 +107,25 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "db: %u", xlrec->databaseId); } - else if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) + else if (info == XLOG_TDE_INSTALL_EXTENSION) { XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); appendStringInfo(buf, "db: %u", xlrec->database_id); } - else if (info == XLOG_TDE_ROTATE_KEY) + else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); appendStringInfo(buf, "db: %u", xlrec->databaseId); } - else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY) + else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); appendStringInfo(buf, "db: %u, provider id: %d", xlrec->database_id, xlrec->provider.provider_id); } - else if (info == XLOG_TDE_FREE_MAP_ENTRY) + else if (info == XLOG_TDE_REMOVE_RELATION_KEY) { RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); @@ -142,14 +142,14 @@ tdeheap_rmgr_identify(uint8 info) return "ADD_RELATION_KEY"; case XLOG_TDE_ADD_PRINCIPAL_KEY: return "ADD_PRINCIPAL_KEY"; - case XLOG_TDE_EXTENSION_INSTALL_KEY: - return "EXTENSION_INSTALL_KEY"; - case XLOG_TDE_ROTATE_KEY: - return "ROTATE_KEY"; - case XLOG_TDE_ADD_KEY_PROVIDER_KEY: - return "ADD_KEY_PROVIDER_KEY"; - case XLOG_TDE_FREE_MAP_ENTRY: - return "FREE_MAP_ENTRY"; + case XLOG_TDE_INSTALL_EXTENSION: + return "INSTALL_EXTENSION"; + case XLOG_TDE_ROTATE_PRINCIPAL_KEY: + return "ROTATE_PRINCIPAL_KEY"; + case XLOG_TDE_WRITE_KEY_PROVIDER: + return "WRITE_KEY_PROVIDER"; + case XLOG_TDE_REMOVE_RELATION_KEY: + return "REMOVE_RELATION_KEY"; default: return NULL; } diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b7937f568e36d..0eb726352d58d 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -512,7 +512,7 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(KeyringProviderXLRecord)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_KEY_PROVIDER_KEY); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); #else Assert(0); #endif diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 22b8bd9116039..ec1a3bbc72f4e 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -16,10 +16,10 @@ /* TDE XLOG resource manager */ #define XLOG_TDE_ADD_RELATION_KEY 0x00 #define XLOG_TDE_ADD_PRINCIPAL_KEY 0x10 -#define XLOG_TDE_EXTENSION_INSTALL_KEY 0x20 -#define XLOG_TDE_ROTATE_KEY 0x30 -#define XLOG_TDE_ADD_KEY_PROVIDER_KEY 0x40 -#define XLOG_TDE_FREE_MAP_ENTRY 0x50 +#define XLOG_TDE_INSTALL_EXTENSION 0x20 +#define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x30 +#define XLOG_TDE_WRITE_KEY_PROVIDER 0x40 +#define XLOG_TDE_REMOVE_RELATION_KEY 0x50 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 34fd1b872067d..aedb8eb699439 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -147,7 +147,7 @@ pg_tde_extension_initialize(PG_FUNCTION_ARGS) */ XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(XLogExtensionInstall)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_EXTENSION_INSTALL_KEY); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_INSTALL_EXTENSION); PG_RETURN_NULL(); } From ee3279b9b4f8cf9fde42ca9e108c666374b5e0bb Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Apr 2025 18:21:42 +0200 Subject: [PATCH 067/796] PG-1441 Sort resource manager record types Since we are breaking backwards compatbility anyway we might as well have them in a bit nicer order. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 50 +++++++++---------- .../pg_tde/src/include/access/pg_tde_xlog.h | 8 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 083006dfb0e2f..d37a1c18ca3fc 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -54,17 +54,23 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_write_key_map_entry_redo(&xlrec->mapEntry, &xlrec->pkInfo); } + else if (info == XLOG_TDE_REMOVE_RELATION_KEY) + { + RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); + + pg_tde_free_key_map_entry(xlrec, 0); + } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { TDESignedPrincipalKeyInfo *mkey = (TDESignedPrincipalKeyInfo *) XLogRecGetData(record); pg_tde_save_principal_key_redo(mkey); } - else if (info == XLOG_TDE_INSTALL_EXTENSION) + else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { - XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); + XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); - extension_install_redo(xlrec); + xl_tde_perform_rotate_key(xlrec); } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { @@ -72,17 +78,11 @@ tdeheap_rmgr_redo(XLogReaderState *record) redo_key_provider_info(xlrec); } - else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) - { - XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); - - xl_tde_perform_rotate_key(xlrec); - } - else if (info == XLOG_TDE_REMOVE_RELATION_KEY) + else if (info == XLOG_TDE_INSTALL_EXTENSION) { - RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); + XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); - pg_tde_free_key_map_entry(xlrec, 0); + extension_install_redo(xlrec); } else { @@ -101,17 +101,17 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "rel: %u/%u/%u", xlrec->mapEntry.spcOid, xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); } - else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) + else if (info == XLOG_TDE_REMOVE_RELATION_KEY) { - TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); + RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); - appendStringInfo(buf, "db: %u", xlrec->databaseId); + appendStringInfo(buf, "rel: %u/%u/%u", xlrec->spcOid, xlrec->dbOid, xlrec->relNumber); } - else if (info == XLOG_TDE_INSTALL_EXTENSION) + else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { - XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); + TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); - appendStringInfo(buf, "db: %u", xlrec->database_id); + appendStringInfo(buf, "db: %u", xlrec->databaseId); } else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { @@ -125,11 +125,11 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "db: %u, provider id: %d", xlrec->database_id, xlrec->provider.provider_id); } - else if (info == XLOG_TDE_REMOVE_RELATION_KEY) + else if (info == XLOG_TDE_INSTALL_EXTENSION) { - RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); + XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); - appendStringInfo(buf, "rel: %u/%u/%u", xlrec->spcOid, xlrec->dbOid, xlrec->relNumber); + appendStringInfo(buf, "db: %u", xlrec->database_id); } } @@ -140,16 +140,16 @@ tdeheap_rmgr_identify(uint8 info) { case XLOG_TDE_ADD_RELATION_KEY: return "ADD_RELATION_KEY"; + case XLOG_TDE_REMOVE_RELATION_KEY: + return "REMOVE_RELATION_KEY"; case XLOG_TDE_ADD_PRINCIPAL_KEY: return "ADD_PRINCIPAL_KEY"; - case XLOG_TDE_INSTALL_EXTENSION: - return "INSTALL_EXTENSION"; case XLOG_TDE_ROTATE_PRINCIPAL_KEY: return "ROTATE_PRINCIPAL_KEY"; case XLOG_TDE_WRITE_KEY_PROVIDER: return "WRITE_KEY_PROVIDER"; - case XLOG_TDE_REMOVE_RELATION_KEY: - return "REMOVE_RELATION_KEY"; + case XLOG_TDE_INSTALL_EXTENSION: + return "INSTALL_EXTENSION"; default: return NULL; } diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index ec1a3bbc72f4e..0d8d99b012d2a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -13,13 +13,13 @@ #include "postgres.h" -/* TDE XLOG resource manager */ +/* TDE XLOG record types */ #define XLOG_TDE_ADD_RELATION_KEY 0x00 -#define XLOG_TDE_ADD_PRINCIPAL_KEY 0x10 -#define XLOG_TDE_INSTALL_EXTENSION 0x20 +#define XLOG_TDE_REMOVE_RELATION_KEY 0x10 +#define XLOG_TDE_ADD_PRINCIPAL_KEY 0x20 #define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x30 #define XLOG_TDE_WRITE_KEY_PROVIDER 0x40 -#define XLOG_TDE_REMOVE_RELATION_KEY 0x50 +#define XLOG_TDE_INSTALL_EXTENSION 0x50 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 From 2131faf17d6861d65c554c36a8d1d83d2ff0f58f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 11 Apr 2025 04:50:56 +0200 Subject: [PATCH 068/796] Use PostgreSQL's find_typedef script instead of our own Since the PostgreSQL repo already contains a script extracting typedefs from object files let's use it. --- ci_scripts/dump-typedefs.sh | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/ci_scripts/dump-typedefs.sh b/ci_scripts/dump-typedefs.sh index 181beac31c07c..bcc1e1b61dd13 100755 --- a/ci_scripts/dump-typedefs.sh +++ b/ci_scripts/dump-typedefs.sh @@ -1,7 +1,7 @@ #!/bin/bash SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" -cd "$SCRIPT_DIR/../" +cd "$SCRIPT_DIR/.." if ! test -f src/backend/postgres; then echo "src/backend/postgres doesn't exists, run make-build.sh first in debug mode" @@ -13,21 +13,7 @@ if ! test -f contrib/pg_tde/pg_tde.so; then exit 1 fi -objdump -W src/backend/postgres |\ - egrep -A3 DW_TAG_typedef |\ - perl -e ' while (<>) { chomp; @flds = split;next unless (1 < @flds);\ - next if $flds[0] ne "DW_AT_name" && $flds[1] ne "DW_AT_name";\ - next if $flds[-1] =~ /^DW_FORM_str/;\ - print $flds[-1],"\n"; }' |\ - sort | uniq > percona.typedefs - -objdump -W contrib/pg_tde/pg_tde.so |\ - egrep -A3 DW_TAG_typedef |\ - perl -e ' while (<>) { chomp; @flds = split;next unless (1 < @flds);\ - next if $flds[0] ne "DW_AT_name" && $flds[1] ne "DW_AT_name";\ - next if $flds[-1] =~ /^DW_FORM_str/;\ - print $flds[-1],"\n"; }' |\ - sort | uniq > tde.typedefs +src/tools/find_typedef src/backend contrib/pg_tde > percona.typedefs # Combine with original typedefs -cat percona.typedefs tde.typedefs src/tools/pgindent/typedefs.list | sort | uniq > combined.typedefs +cat percona.typedefs src/tools/pgindent/typedefs.list | sort -u > combined.typedefs From 14a3d36ae13956370db0fb1cd3e14ae478bf0299 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 11 Apr 2025 05:13:19 +0200 Subject: [PATCH 069/796] Only check pg_tde with the combined typedefs The reason to do this is that the old approach created an unnecessary diff against upstream where they had forgot SinglePartitionSpec in typedefs.list. Additionally add two new structs from our SMGR patch to the list. --- ci_scripts/dump-typedefs.sh | 9 ++------- ci_scripts/run-pgindent.sh | 6 +++++- src/include/nodes/parsenodes.h | 2 +- src/tools/pgindent/typedefs.list | 2 ++ 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ci_scripts/dump-typedefs.sh b/ci_scripts/dump-typedefs.sh index bcc1e1b61dd13..6f854a4889570 100755 --- a/ci_scripts/dump-typedefs.sh +++ b/ci_scripts/dump-typedefs.sh @@ -3,17 +3,12 @@ SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" cd "$SCRIPT_DIR/.." -if ! test -f src/backend/postgres; then - echo "src/backend/postgres doesn't exists, run make-build.sh first in debug mode" - exit 1 -fi - if ! test -f contrib/pg_tde/pg_tde.so; then echo "contrib/pg_tde/pg_tde.so doesn't exists, run make-build.sh first in debug mode" exit 1 fi -src/tools/find_typedef src/backend contrib/pg_tde > percona.typedefs +src/tools/find_typedef contrib/pg_tde > pg_tde.typedefs # Combine with original typedefs -cat percona.typedefs src/tools/pgindent/typedefs.list | sort -u > combined.typedefs +cat pg_tde.typedefs src/tools/pgindent/typedefs.list | sort -u > combined.typedefs diff --git a/ci_scripts/run-pgindent.sh b/ci_scripts/run-pgindent.sh index 13964ffe35c5c..4b9ba00f278b8 100755 --- a/ci_scripts/run-pgindent.sh +++ b/ci_scripts/run-pgindent.sh @@ -16,4 +16,8 @@ cd "$SCRIPT_DIR/.." export PATH=$SCRIPT_DIR/../src/tools/pgindent/:$INSTALL_DIR/bin/:$PATH -pgindent --typedefs=combined.typedefs "$@" . +# Check everything except pg_tde with the list in the repo +pgindent --typedefs=src/tools/pgindent/typedefs.list --excludes=<(echo "contrib/pg_tde") "$@" . + +# Check pg_tde with the fresh list extraxted from the object file +pgindent --typedefs=combined.typedefs "$@" contrib/pg_tde diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0ed589426af86..67c90a2bd328d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -945,7 +945,7 @@ typedef struct PartitionRangeDatum typedef struct SinglePartitionSpec { NodeTag type; -} SinglePartitionSpec; +} SinglePartitionSpec; /* * PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 72e6d3a9865ab..2114d560ceda0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1607,6 +1607,7 @@ ManyTestResourceKind Material MaterialPath MaterialState +MdSMgrRelationData MdfdVec Memoize MemoizeEntry @@ -3206,6 +3207,7 @@ XLogRecordBuffer XLogRecoveryCtlData XLogRedoAction XLogSegNo +XLogSmgr XLogSource XLogStats XLogwrtResult From 40c32b2de2e175988efa8453edc7b79187a9a966 Mon Sep 17 00:00:00 2001 From: Shahid Ullah <32063351+shahidullah79@users.noreply.github.com> Date: Thu, 17 Apr 2025 18:43:55 +0500 Subject: [PATCH 070/796] =?UTF-8?q?[PG-1545]=20-=20Remove=20pg=5Ftde=5Frev?= =?UTF-8?q?oke=5Fglobal=5Fkey=5Fmanagement=5Ffrom=5Frole=20refe=E2=80=A6?= =?UTF-8?q?=20(#228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …rence --- contrib/pg_tde/pg_tde--1.0-rc.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 72876cb58a962..c717c30dda579 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -641,7 +641,6 @@ BEGIN EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_global_key_management_from_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) TO %I', target_role); EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) TO %I', target_role); From 1390dd0cef76d89f460f0444f3e248ce94af9316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 17 Apr 2025 13:53:19 +0200 Subject: [PATCH 071/796] PG-1535 Do not delete global key provider in use The code wrongly assumed that the databaseId set in the keyInfo returned from GetPrincipalKeyNoDefault() would be the Oid of the key provider owner, while in reality it is the Oid of the database using it as a principal key. --- contrib/pg_tde/expected/default_principal_key.out | 3 ++- contrib/pg_tde/expected/default_principal_key_1.out | 3 ++- contrib/pg_tde/expected/key_provider.out | 12 +++++------- contrib/pg_tde/expected/key_provider_1.out | 12 +++++------- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 0bae2551e2f06..8569aab5b42a0 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -39,8 +39,9 @@ ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- + -1 | file-keyring -3 | file-provider -(1 row) +(2 rows) -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out index 9ad2389332539..0a90d7696ea69 100644 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ b/contrib/pg_tde/expected/default_principal_key_1.out @@ -40,8 +40,9 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+----------------- -1 | reg_file-global + -2 | file-keyring -4 | file-provider -(2 rows) +(3 rows) -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 733efc46cd321..202555ae9dad2 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -139,16 +139,13 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', f -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); - pg_tde_delete_global_key_provider ------------------------------------ - -(1 row) - +ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- + -1 | file-keyring -2 | file-keyring2 -(1 row) +(2 rows) -- works SELECT pg_tde_delete_global_key_provider('file-keyring2'); @@ -160,6 +157,7 @@ SELECT pg_tde_delete_global_key_provider('file-keyring2'); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- -(0 rows) + -1 | file-keyring +(1 row) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out index 3463d6a384f5c..a583cc3dad108 100644 --- a/contrib/pg_tde/expected/key_provider_1.out +++ b/contrib/pg_tde/expected/key_provider_1.out @@ -141,17 +141,14 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', f -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); - pg_tde_delete_global_key_provider ------------------------------------ - -(1 row) - +ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+----------------- -1 | reg_file-global + -2 | file-keyring -3 | file-keyring2 -(2 rows) +(3 rows) -- works SELECT pg_tde_delete_global_key_provider('file-keyring2'); @@ -164,6 +161,7 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+----------------- -1 | reg_file-global -(1 row) + -2 | file-keyring +(2 rows) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 6b4f1174c2058..a3457e7f1de8e 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -953,7 +953,7 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) continue; } - if (providerId == principal_key->keyInfo.keyringId && principal_key->keyInfo.databaseId == GLOBAL_DATA_TDE_OID) + if (providerId == principal_key->keyInfo.keyringId) { systable_endscan(scan); table_close(rel, AccessShareLock); From 3b6d3d46dd38896b68193afb8a850e353dbdf0b5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 19:50:20 +0200 Subject: [PATCH 072/796] Use append_conf() helper in TAP tests This is what PostgreSQL has been using since 9.6 so we can safely use it too in our tests. --- contrib/pg_tde/t/001_basic.pl | 6 +----- contrib/pg_tde/t/002_rotate_key.pl | 6 +----- contrib/pg_tde/t/003_remote_config.pl | 12 ++++-------- contrib/pg_tde/t/004_file_config.pl | 6 +----- contrib/pg_tde/t/005_multiple_extensions.pl | 10 +++------- contrib/pg_tde/t/006_remote_vault_config.pl | 12 ++++-------- contrib/pg_tde/t/007_tde_heap.pl | 13 +------------ contrib/pg_tde/t/008_key_rotate_tablespace.pl | 6 +----- contrib/pg_tde/t/009_wal_encrypt.pl | 10 +++------- contrib/pg_tde/t/010_change_key_provider.pl | 6 +----- contrib/pg_tde/t/011_unlogged_tables.pl | 6 +----- 11 files changed, 21 insertions(+), 72 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 87146f1cdaf8a..585e87a67ad0c 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -10,11 +10,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 38cd31075706f..a6f6821f874ff 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -10,11 +10,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 2cfd35e9052a0..fc8e931ce266d 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -7,11 +7,6 @@ use lib 't'; use pgtde; -PGTDE::setup_files_dir(basename($0)); - -my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - { package MyWebServer; @@ -52,9 +47,10 @@ sub resp_hello { my $pid = MyWebServer->new(8888)->background(); -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +PGTDE::setup_files_dir(basename($0)); + +my $node = PGTDE->pgtde_init_pg(); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start(); ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 8fe8178ce5508..03b0df0666bb0 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -10,11 +10,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); open my $conf2, '>>', "/tmp/datafile-location"; print $conf2 "/tmp/keyring_data_file\n"; diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 309d68a3f2ed3..0cfbddf4f169e 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -17,13 +17,9 @@ } my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'\n"; -print $conf "pg_stat_monitor.pgsm_bucket_time = 360000\n"; -print $conf "pg_stat_monitor.pgsm_normalized_query = 'yes'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'"); +$node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_bucket_time = 360000"); +$node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_normalized_query = 'yes'"); open my $conf2, '>>', "/tmp/datafile-location"; print $conf2 "/tmp/keyring_data_file\n"; diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index db50f2e4a69bd..7b7897ec8684d 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -8,11 +8,6 @@ use lib 't'; use pgtde; -PGTDE::setup_files_dir(basename($0)); - -my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - { package MyWebServer; @@ -60,9 +55,10 @@ sub resp_url { my $pid = MyWebServer->new(8889)->background(); -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +PGTDE::setup_files_dir(basename($0)); + +my $node = PGTDE->pgtde_init_pg(); +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start(); ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index f2018382f6586..4c67a75ef2e88 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -9,19 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $PG_VERSION_STRING = `pg_config --version`; - -if (index(lc($PG_VERSION_STRING), lc("Percona Server")) == -1) -{ - plan skip_all => "pg_tde test case only for Percona Server for PostgreSQL"; -} - my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index a7e93a693fc53..1b39d377cca26 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -10,11 +10,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 4528a34445427..499209c24f6a3 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -10,14 +10,10 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -print $conf "wal_level = 'logical'\n"; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$node->append_conf('postgresql.conf', "wal_level = 'logical'"); # NOT testing that it can't start: the test framework doesn't have an easy way to do this -# print $conf "pg_tde.wal_encrypt = 1\n"; -close $conf; +#$node->append_conf('postgresql.conf', "pg_tde.wal_encrypt = 1"}); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index c94f82089b4a5..8e6a649c886ad 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -11,11 +11,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); unlink('/tmp/change_key_provider_1.per'); unlink('/tmp/change_key_provider_2.per'); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index fcfa26addb672..277b95e44ea5d 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -10,11 +10,7 @@ PGTDE::setup_files_dir(basename($0)); my $node = PGTDE->pgtde_init_pg(); -my $pgdata = $node->data_dir; - -open my $conf, '>>', "$pgdata/postgresql.conf"; -print $conf "shared_preload_libraries = 'pg_tde'\n"; -close $conf; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); From 1df4ff5110b8636413eec6b6c84e860b8d896d27 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 20:32:21 +0200 Subject: [PATCH 073/796] Remove unnecesary comments and code in pgtde.pm None of this is useful or used. --- contrib/pg_tde/t/pgtde.pm | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 989b41089c3bf..6c85b014c21f3 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -4,11 +4,6 @@ use File::Basename; use File::Compare; use Test::More; -our @ISA = qw(Exporter); - -# These CAN be exported. -our @EXPORT = qw(pgtde_init_pg pgtde_start_pg pgtde_stop_pg pgtde_psql_cmd pgtde_setup_pg_tde pgtde_create_extension pgtde_drop_extension); - # Expected .out filename of TAP testcase being executed. These are already part of repo under t/expected/*. our $expected_filename_with_path; @@ -22,7 +17,6 @@ our $out_filename_with_path; our $debug_out_filename_with_path; BEGIN { - # Get PG Server Major version from pg_config $PG_MAJOR_VERSION = `pg_config --version | awk {'print \$2'} | cut -f1 -d"." | sed -e 's/[^0-9].*\$//g'`; $PG_MAJOR_VERSION =~ s/^\s+|\s+$//g; @@ -43,7 +37,7 @@ sub pgtde_init_pg { my $node; - print "Postgres major version: $PG_MAJOR_VERSION \n"; + print "Postgres major version: $PG_MAJOR_VERSION\n"; # For Server version 15 & above, spawn the server using PostgreSQL::Test::Cluster if ($PG_MAJOR_VERSION >= 15) { @@ -90,40 +84,30 @@ sub setup_files_dir { my ($perlfilename) = @_; - # Expected folder where expected output will be present my $expected_folder = "t/expected"; - - # Results/out folder where generated results files will be placed my $results_folder = "t/results"; - # Check if results folder exists or not, create if it doesn't unless (-d $results_folder) { mkdir $results_folder or die "Can't create folder $results_folder: $!\n"; } - # Check if expected folder exists or not, bail out if it doesn't unless (-d $expected_folder) { BAIL_OUT "Expected files folder $expected_folder doesn't exist: \n"; } - #Remove .pl from filename and store in a variable my @split_arr = split /\./, $perlfilename; my $filename_without_extension = $split_arr[0]; - # Create expected filename with path my $expected_filename = "${filename_without_extension}.out"; - $expected_filename_with_path = "${expected_folder}/${expected_filename}"; - # Create results filename with path my $out_filename = "${filename_without_extension}.out"; $out_filename_with_path = "${results_folder}/${out_filename}"; - # Delete already existing result out file, if it exists. - if ( -f $out_filename_with_path) + if (-f $out_filename_with_path) { unlink($out_filename_with_path) or die "Can't delete already existing $out_filename_with_path: $!\n"; } @@ -133,7 +117,6 @@ sub setup_files_dir sub compare_results { - # Compare expected and results files and return the result return compare($expected_filename_with_path, $out_filename_with_path); } From 5fb92917f7b9c213715c1c977077c3eb39417104 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 20:33:45 +0200 Subject: [PATCH 074/796] Drop support for PostgreSQL <15 in pgtde.pm Since until we actually have CI for older versions the code is likely broken anyway we might as well not try to support versions we do not actually support. It is easy to re-add this once we want to add support for PostgreSQL 14. --- contrib/pg_tde/t/pgtde.pm | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 6c85b014c21f3..f992c0e113aa5 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -1,5 +1,8 @@ package PGTDE; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + use File::Basename; use File::Compare; use Test::More; @@ -7,45 +10,15 @@ use Test::More; # Expected .out filename of TAP testcase being executed. These are already part of repo under t/expected/*. our $expected_filename_with_path; -# Major version of PG Server that we are using. -our $PG_MAJOR_VERSION; - # Result .out filename of TAP testcase being executed. Where needed, a new *.out will be created for each TAP test. our $out_filename_with_path; # Runtime output file that is used only for debugging purposes for comparison to PGSS, blocks and timings. our $debug_out_filename_with_path; -BEGIN { - $PG_MAJOR_VERSION = `pg_config --version | awk {'print \$2'} | cut -f1 -d"." | sed -e 's/[^0-9].*\$//g'`; - $PG_MAJOR_VERSION =~ s/^\s+|\s+$//g; - - if ($PG_MAJOR_VERSION >= 15) { - eval { - require PostgreSQL::Test::Cluster; - import PostgreSQL::Test::Utils; - } - } else { - eval { - require PostgresNode; - import TestLib; - } - } -} - sub pgtde_init_pg { - my $node; - - print "Postgres major version: $PG_MAJOR_VERSION\n"; - - # For Server version 15 & above, spawn the server using PostgreSQL::Test::Cluster - if ($PG_MAJOR_VERSION >= 15) { - $node = PostgreSQL::Test::Cluster->new('pgtde_regression'); - } else { - $node = PostgresNode->get_new_node('pgtde_regression'); - } - + my $node = PostgreSQL::Test::Cluster->new('pgtde_regression'); $node->dump_info; $node->init; return $node; From cf2806d9f31146fb90f316e46a1a0601e63492e5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 20:43:57 +0200 Subject: [PATCH 075/796] Simplify setup_files_dir() in pgtde.pm --- contrib/pg_tde/t/pgtde.pm | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index f992c0e113aa5..a063f33486ef2 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -16,6 +16,9 @@ our $out_filename_with_path; # Runtime output file that is used only for debugging purposes for comparison to PGSS, blocks and timings. our $debug_out_filename_with_path; +my $expected_folder = "t/expected"; +my $results_folder = "t/results"; + sub pgtde_init_pg { my $node = PostgreSQL::Test::Cluster->new('pgtde_regression'); @@ -55,37 +58,23 @@ sub append_to_debug_file sub setup_files_dir { - my ($perlfilename) = @_; - - my $expected_folder = "t/expected"; - my $results_folder = "t/results"; + my ($test_filename) = @_; unless (-d $results_folder) { mkdir $results_folder or die "Can't create folder $results_folder: $!\n"; } - unless (-d $expected_folder) - { - BAIL_OUT "Expected files folder $expected_folder doesn't exist: \n"; - } - - my @split_arr = split /\./, $perlfilename; - - my $filename_without_extension = $split_arr[0]; + my ($test_name) = $test_filename =~ /([^.]*)/; - my $expected_filename = "${filename_without_extension}.out"; - $expected_filename_with_path = "${expected_folder}/${expected_filename}"; - - my $out_filename = "${filename_without_extension}.out"; - $out_filename_with_path = "${results_folder}/${out_filename}"; + $expected_filename_with_path = "${expected_folder}/${test_name}.out"; + $out_filename_with_path = "${results_folder}/${test_name}.out"; + $debug_out_filename_with_path = "${results_folder}/${test_name}.out.debug"; if (-f $out_filename_with_path) { unlink($out_filename_with_path) or die "Can't delete already existing $out_filename_with_path: $!\n"; } - - $debug_out_filename_with_path = "${results_folder}/${out_filename}.debug"; } sub compare_results From eb8b307b4c895fefd38fd44209fde187549017ba Mon Sep 17 00:00:00 2001 From: Mohit Joshi Date: Fri, 18 Apr 2025 17:45:57 +0530 Subject: [PATCH 076/796] PG-1491 Add SQL test cases for pg_tde_is_encrypted() for partitioned tables (#207) PG-1491 pg_tde_is_encrypted() is broken for partitioned tables Following new tescases added: partition_table.sql --- contrib/pg_tde/Makefile | 1 + contrib/pg_tde/expected/partition_table.out | 90 +++++++++++++++++++++ contrib/pg_tde/meson.build | 1 + contrib/pg_tde/sql/partition_table.sql | 35 ++++++++ 4 files changed, 127 insertions(+) create mode 100644 contrib/pg_tde/expected/partition_table.out create mode 100644 contrib/pg_tde/sql/partition_table.sql diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index d7a07396a1e09..4ec814004e9ab 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -17,6 +17,7 @@ insert_update_delete \ key_provider \ keyprovider_dependency \ kmip_test \ +partition_table \ pg_tde_is_encrypted \ recreate_storage \ relocate \ diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out new file mode 100644 index 0000000000000..05cdc5708e66c --- /dev/null +++ b/contrib/pg_tde/expected/partition_table.out @@ -0,0 +1,90 @@ +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +CREATE TABLE IF NOT EXISTS partitioned_table ( + id SERIAL, + data TEXT, + created_at DATE NOT NULL, + PRIMARY KEY (id, created_at) + ) PARTITION BY RANGE (created_at) USING tde_heap; +CREATE TABLE partition_q1_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-01-01') TO ('2024-04-01') USING tde_heap; +CREATE TABLE partition_q2_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-04-01') TO ('2024-07-01') USING heap; +CREATE TABLE partition_q3_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-07-01') TO ('2024-10-01') USING tde_heap; +CREATE TABLE partition_q4_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-10-01') TO ('2025-01-01') USING heap; +SELECT pg_tde_is_encrypted('partitioned_table'); + pg_tde_is_encrypted +--------------------- + +(1 row) + +SELECT pg_tde_is_encrypted('partition_q1_2024'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('partition_q2_2024'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('partition_q3_2024'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('partition_q4_2024'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +ALTER TABLE partitioned_table SET ACCESS METHOD heap; +ALTER TABLE partition_q1_2024 SET ACCESS METHOD heap; +ALTER TABLE partition_q2_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q3_2024 SET ACCESS METHOD heap; +ALTER TABLE partition_q4_2024 SET ACCESS METHOD tde_heap; +SELECT pg_tde_is_encrypted('partitioned_table'); + pg_tde_is_encrypted +--------------------- + +(1 row) + +SELECT pg_tde_is_encrypted('partition_q1_2024'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('partition_q2_2024'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('partition_q3_2024'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('partition_q4_2024'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partitioned_table; +DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 5527666f51d8c..da12b21a0e42d 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -93,6 +93,7 @@ sql_tests = [ 'key_provider', 'keyprovider_dependency', 'kmip_test', + 'partition_table', 'pg_tde_is_encrypted', 'relocate', 'recreate_storage', diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql new file mode 100644 index 0000000000000..2651ef71d726f --- /dev/null +++ b/contrib/pg_tde/sql/partition_table.sql @@ -0,0 +1,35 @@ +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); +SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); +CREATE TABLE IF NOT EXISTS partitioned_table ( + id SERIAL, + data TEXT, + created_at DATE NOT NULL, + PRIMARY KEY (id, created_at) + ) PARTITION BY RANGE (created_at) USING tde_heap; + +CREATE TABLE partition_q1_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-01-01') TO ('2024-04-01') USING tde_heap; +CREATE TABLE partition_q2_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-04-01') TO ('2024-07-01') USING heap; +CREATE TABLE partition_q3_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-07-01') TO ('2024-10-01') USING tde_heap; +CREATE TABLE partition_q4_2024 PARTITION OF partitioned_table FOR VALUES FROM ('2024-10-01') TO ('2025-01-01') USING heap; + +SELECT pg_tde_is_encrypted('partitioned_table'); +SELECT pg_tde_is_encrypted('partition_q1_2024'); +SELECT pg_tde_is_encrypted('partition_q2_2024'); +SELECT pg_tde_is_encrypted('partition_q3_2024'); +SELECT pg_tde_is_encrypted('partition_q4_2024'); + +ALTER TABLE partitioned_table SET ACCESS METHOD heap; +ALTER TABLE partition_q1_2024 SET ACCESS METHOD heap; +ALTER TABLE partition_q2_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q3_2024 SET ACCESS METHOD heap; +ALTER TABLE partition_q4_2024 SET ACCESS METHOD tde_heap; + +SELECT pg_tde_is_encrypted('partitioned_table'); +SELECT pg_tde_is_encrypted('partition_q1_2024'); +SELECT pg_tde_is_encrypted('partition_q2_2024'); +SELECT pg_tde_is_encrypted('partition_q3_2024'); +SELECT pg_tde_is_encrypted('partition_q4_2024'); + +DROP TABLE partitioned_table; +DROP EXTENSION pg_tde; From ca37d73e9d10e3ef9ea2779df08e44394b5aac85 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 18 Apr 2025 02:04:42 +0200 Subject: [PATCH 077/796] Remove dead code for scanning key providers by type This code has never been used. --- contrib/pg_tde/src/catalog/tde_keyring.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 0eb726352d58d..34a267b400846 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -44,7 +44,6 @@ typedef enum ProviderScanType { PROVIDER_SCAN_BY_NAME, PROVIDER_SCAN_BY_ID, - PROVIDER_SCAN_BY_TYPE, PROVIDER_SCAN_ALL } ProviderScanType; @@ -671,11 +670,6 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) if (provider.provider_id == *(int *) scanKey) match = true; } - else if (scanType == PROVIDER_SCAN_BY_TYPE) - { - if (provider.provider_type == *(ProviderType *) scanKey) - match = true; - } else if (scanType == PROVIDER_SCAN_ALL) match = true; From eed26578cc5226ce0cdc9383bd49339416da783c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 20:49:19 +0200 Subject: [PATCH 078/796] Remove pgtde_init_pg() TAP helper This helper mostly added confusion by making it seem like it did more work than is actually did. And especially since we will want to call init in the future with different parameters for some tests or initialize from a backup. --- contrib/pg_tde/t/001_basic.pl | 3 ++- contrib/pg_tde/t/002_rotate_key.pl | 3 ++- contrib/pg_tde/t/003_remote_config.pl | 3 ++- contrib/pg_tde/t/004_file_config.pl | 3 ++- contrib/pg_tde/t/005_multiple_extensions.pl | 3 ++- contrib/pg_tde/t/006_remote_vault_config.pl | 3 ++- contrib/pg_tde/t/007_tde_heap.pl | 3 ++- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 3 ++- contrib/pg_tde/t/009_wal_encrypt.pl | 3 ++- contrib/pg_tde/t/010_change_key_provider.pl | 3 ++- contrib/pg_tde/t/011_unlogged_tables.pl | 3 ++- contrib/pg_tde/t/pgtde.pm | 8 -------- 12 files changed, 22 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 585e87a67ad0c..e5d56df94fbfc 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index a6f6821f874ff..bb266f5beb0d8 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index fc8e931ce266d..95d8b57c18628 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -49,7 +49,8 @@ sub resp_hello { PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start(); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 03b0df0666bb0..52a55edfbbefe 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); open my $conf2, '>>', "/tmp/datafile-location"; diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 0cfbddf4f169e..bdf63085e8a9c 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -16,7 +16,8 @@ plan skip_all => "pg_tde test case only for PPG server package install with extensions."; } -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'"); $node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_bucket_time = 360000"); $node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_normalized_query = 'yes'"); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 7b7897ec8684d..58af6bde07ca6 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -57,7 +57,8 @@ sub resp_url { PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start(); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 4c67a75ef2e88..26f47863d078c 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 1b39d377cca26..73ed7b24cdbf8 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 499209c24f6a3..6db8332e97b97 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->append_conf('postgresql.conf', "wal_level = 'logical'"); # NOT testing that it can't start: the test framework doesn't have an easy way to do this diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 8e6a649c886ad..79bd8cc31c755 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -10,7 +10,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); unlink('/tmp/change_key_provider_1.per'); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 277b95e44ea5d..7bc68861fb7c8 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -9,7 +9,8 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PGTDE->pgtde_init_pg(); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); my $rt_value = $node->start; diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index a063f33486ef2..68eaf4c300d1a 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -19,14 +19,6 @@ our $debug_out_filename_with_path; my $expected_folder = "t/expected"; my $results_folder = "t/results"; -sub pgtde_init_pg -{ - my $node = PostgreSQL::Test::Cluster->new('pgtde_regression'); - $node->dump_info; - $node->init; - return $node; -} - sub psql { my ($node, $dbname, $sql) = @_; From 607cf9397d97fb684c76e7eeb046f11fb6d6ccdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 21 Apr 2025 13:19:23 +0200 Subject: [PATCH 079/796] Remove unused tests These were not present in meson.build or Makefile, and as such are just extra maintenance cost for no value. --- .../pg_tde/expected/delete_key_provider.out | 74 ------------------- contrib/pg_tde/expected/no_provider_error.out | 8 -- contrib/pg_tde/sql/delete_key_provider.sql | 20 ----- contrib/pg_tde/sql/no_provider_error.sql | 11 --- 4 files changed, 113 deletions(-) delete mode 100644 contrib/pg_tde/expected/delete_key_provider.out delete mode 100644 contrib/pg_tde/expected/no_provider_error.out delete mode 100644 contrib/pg_tde/sql/delete_key_provider.sql delete mode 100644 contrib/pg_tde/sql/no_provider_error.sql diff --git a/contrib/pg_tde/expected/delete_key_provider.out b/contrib/pg_tde/expected/delete_key_provider.out deleted file mode 100644 index d5724272619d0..0000000000000 --- a/contrib/pg_tde/expected/delete_key_provider.out +++ /dev/null @@ -1,74 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - -SELECT pg_tde_delete_database_key_provider('file-provider'); - pg_tde_delete_database_key_provider -------------------------------------- - -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------- -(0 rows) - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 2 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 2 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - -SELECT pg_tde_delete_database_key_provider('file-provider'); - pg_tde_delete_database_key_provider -------------------------------------- - -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------- -(0 rows) - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 3 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 3 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - -SELECT pg_tde_delete_database_key_provider('file-provider'); - pg_tde_delete_database_key_provider -------------------------------------- - -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------- -(0 rows) - -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/no_provider_error.out b/contrib/pg_tde/expected/no_provider_error.out deleted file mode 100644 index 434781a6c778e..0000000000000 --- a/contrib/pg_tde/expected/no_provider_error.out +++ /dev/null @@ -1,8 +0,0 @@ -CREATE EXTENSION pg_tde; --- should fail -CREATE TABLE t1 (n INT) USING tde_heap; -ERROR: failed to retrieve principal key. Create one using pg_tde_set_key before using encrypted tables. --- should work -CREATE TABLE t2 (n INT) USING heap; -DROP TABLE t2; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/delete_key_provider.sql b/contrib/pg_tde/sql/delete_key_provider.sql deleted file mode 100644 index 935782f2656cd..0000000000000 --- a/contrib/pg_tde/sql/delete_key_provider.sql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; - -SELECT * FROM pg_tde_key_info(); - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_delete_database_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_database_key_providers(); - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_delete_database_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_database_key_providers(); - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_delete_database_key_provider('file-provider'); -SELECT * FROM pg_tde_list_all_database_key_providers(); - -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/no_provider_error.sql b/contrib/pg_tde/sql/no_provider_error.sql deleted file mode 100644 index abe2e18cbcc5e..0000000000000 --- a/contrib/pg_tde/sql/no_provider_error.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE EXTENSION pg_tde; - --- should fail -CREATE TABLE t1 (n INT) USING tde_heap; - --- should work -CREATE TABLE t2 (n INT) USING heap; - -DROP TABLE t2; - -DROP EXTENSION pg_tde; From 54cd79c81e158d6f30d29bdbd74d2d654ed876a9 Mon Sep 17 00:00:00 2001 From: Mohit Joshi Date: Tue, 22 Apr 2025 14:49:45 +0530 Subject: [PATCH 080/796] PG-1517 - Automate testcase for (#243) PG-1473 - Executing pg_tde_verify_principal_key() must require key viewer permission. --- contrib/pg_tde/expected/access_control.out | 76 +++++++++++++++++----- contrib/pg_tde/sql/access_control.sql | 40 +++++++++--- 2 files changed, 90 insertions(+), 26 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index c6a5594bc9bf9..5186a6b117ce2 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -2,10 +2,38 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); ERROR: permission denied for function pg_tde_add_database_key_provider_file -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); ERROR: permission denied for function pg_tde_set_key_using_database_key_provider +SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); +ERROR: must be superuser to modify global key providers +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'global-file-provider'); +ERROR: must be superuser to access global key providers +SELECT pg_tde_set_server_key_using_global_key_provider('wal-key','global-file-provider'); +ERROR: must be superuser to access global key providers +SELECT pg_tde_set_default_key_using_global_key_provider('def-key', 'global-file-provider'); +ERROR: must be superuser to access global key providers +SELECT pg_tde_delete_database_key_provider('local-file-provider'); +ERROR: permission denied for function pg_tde_delete_database_key_provider +SELECT pg_tde_delete_global_key_provider('global-file-provider'); +ERROR: must be superuser to modify global key providers +SELECT pg_tde_list_all_database_key_providers(); +ERROR: permission denied for function pg_tde_list_all_database_key_providers +SELECT pg_tde_list_all_global_key_providers(); +ERROR: permission denied for function pg_tde_list_all_global_key_providers +SELECT pg_tde_key_info(); +ERROR: permission denied for function pg_tde_key_info +SELECT pg_tde_server_key_info(); +ERROR: permission denied for function pg_tde_server_key_info +SELECT pg_tde_default_key_info(); +ERROR: permission denied for function pg_tde_default_key_info +SELECT pg_tde_verify_key(); +ERROR: permission denied for function pg_tde_verify_key +SELECT pg_tde_verify_server_key(); +ERROR: permission denied for function pg_tde_verify_server_key +SELECT pg_tde_verify_default_key(); +ERROR: permission denied for function pg_tde_verify_default_key RESET ROLE; SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); pg_tde_grant_database_key_management_to_role @@ -21,42 +49,48 @@ SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 1 | file-vault | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} + id | provider_name | provider_type | options +----+---------------------+---------------+------------------------------------------------------------ + 1 | local-file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} (1 row) SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); - key_name | key_provider_name | key_provider_id --------------+-------------------+----------------- - test-db-key | file-vault | 1 + key_name | key_provider_name | key_provider_id +-------------+---------------------+----------------- + test-db-key | local-file-provider | 1 +(1 row) + +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + (1 row) -- only superuser -SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); ERROR: must be superuser to modify global key providers -SELECT pg_tde_change_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); ERROR: must be superuser to modify global key providers -SELECT pg_tde_delete_global_key_provider('file-vault'); +SELECT pg_tde_delete_global_key_provider('global-file-provider'); ERROR: must be superuser to modify global key providers -SELECT pg_tde_set_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers -SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers -SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers RESET ROLE; SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); @@ -71,5 +105,15 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); ERROR: permission denied for function pg_tde_list_all_database_key_providers SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); ERROR: permission denied for function pg_tde_key_info +SELECT pg_tde_verify_key(); +ERROR: permission denied for function pg_tde_verify_key +SELECT pg_tde_server_key_info(); +ERROR: permission denied for function pg_tde_server_key_info +SELECT pg_tde_default_key_info(); +ERROR: permission denied for function pg_tde_default_key_info +SELECT pg_tde_verify_server_key(); +ERROR: permission denied for function pg_tde_verify_server_key +SELECT pg_tde_verify_default_key(); +ERROR: permission denied for function pg_tde_verify_default_key RESET ROLE; DROP EXTENSION pg_tde CASCADE; diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index f992304b1b590..9ec1b36d733b0 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -5,8 +5,22 @@ CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); +SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'global-file-provider'); +SELECT pg_tde_set_server_key_using_global_key_provider('wal-key','global-file-provider'); +SELECT pg_tde_set_default_key_using_global_key_provider('def-key', 'global-file-provider'); +SELECT pg_tde_delete_database_key_provider('local-file-provider'); +SELECT pg_tde_delete_global_key_provider('global-file-provider'); +SELECT pg_tde_list_all_database_key_providers(); +SELECT pg_tde_list_all_global_key_providers(); +SELECT pg_tde_key_info(); +SELECT pg_tde_server_key_info(); +SELECT pg_tde_default_key_info(); +SELECT pg_tde_verify_key(); +SELECT pg_tde_verify_server_key(); +SELECT pg_tde_verify_default_key(); RESET ROLE; @@ -16,18 +30,19 @@ SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +SELECT pg_tde_verify_key(); -- only superuser -SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_change_global_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_delete_global_key_provider('file-vault'); -SELECT pg_tde_set_key_using_global_key_provider('key1', 'file-vault'); -SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'file-vault'); -SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'file-vault'); +SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_delete_global_key_provider('global-file-provider'); +SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); +SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); +SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); RESET ROLE; @@ -38,6 +53,11 @@ SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +SELECT pg_tde_verify_key(); +SELECT pg_tde_server_key_info(); +SELECT pg_tde_default_key_info(); +SELECT pg_tde_verify_server_key(); +SELECT pg_tde_verify_default_key(); RESET ROLE; From 71da1f03f2f0ff18ed11e4ba6b07b6bd56705a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 22 Apr 2025 09:29:13 +0200 Subject: [PATCH 081/796] Run pgperltidy Just as we use pgindent we should probably use pgperltidy. This is an initial run of it using the following command: src/tools/pgindent/pgperltidy contrib/pg_tde --- contrib/pg_tde/t/001_basic.pl | 30 +++- contrib/pg_tde/t/002_rotate_key.pl | 120 ++++++++++---- contrib/pg_tde/t/003_remote_config.pl | 87 ++++++----- contrib/pg_tde/t/004_file_config.pl | 16 +- contrib/pg_tde/t/005_multiple_extensions.pl | 146 ++++++++++++++---- contrib/pg_tde/t/006_remote_vault_config.pl | 100 ++++++------ contrib/pg_tde/t/007_tde_heap.pl | 97 ++++++++---- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 31 ++-- contrib/pg_tde/t/009_wal_encrypt.pl | 25 ++- contrib/pg_tde/t/010_change_key_provider.pl | 67 +++++--- contrib/pg_tde/t/011_unlogged_tables.pl | 15 +- contrib/pg_tde/t/pgtde.pm | 67 ++++---- 12 files changed, 539 insertions(+), 262 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index e5d56df94fbfc..cf3b95780a195 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -18,22 +18,33 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';'); +PGTDE::psql($node, 'postgres', + 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';' +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" +); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' +); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc (k) VALUES (\'foobar\'),(\'barfoo\');'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); @@ -47,7 +58,8 @@ # Verify that we can't see the data in the file my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile .= '/'; -$tablefile .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc\');'); +$tablefile .= + $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc\');'); my $strings = 'TABLEFILE FOUND: '; $strings .= `(ls $tablefile >/dev/null && echo yes) || echo no`; @@ -66,6 +78,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index bb266f5beb0d8..2d076333142e4 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -18,30 +18,46 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');"); - -PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); - -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); - -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');" +); + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_list_all_database_key_providers();"); + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" +); + +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5),(6);'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Rotate key -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); @@ -49,12 +65,18 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Again rotate key -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); @@ -62,12 +84,18 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Again rotate key -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); @@ -75,15 +103,21 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # TODO: add method to query current info # And maybe debug tools to show what's in a file keyring? # Again rotate key -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); @@ -91,11 +125,16 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); -PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;'); +PGTDE::psql($node, 'postgres', + 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;'); # Things still work after a restart PGTDE::append_to_result_file("-- server restart"); @@ -104,17 +143,30 @@ ok($rt_value == 1, "Restart Server"); # But now can't be changed to another global provider -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); - -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();"); -PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" +); +PGTDE::psql($node, 'postgres', + "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" +); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc;'); -PGTDE::psql($node, 'postgres', 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;'); +PGTDE::psql($node, 'postgres', + 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;'); PGTDE::append_to_result_file("-- server restart"); $node->stop(); @@ -128,6 +180,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 95d8b57c18628..b8f7978ea4848 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -8,41 +8,44 @@ use pgtde; { -package MyWebServer; - -use HTTP::Server::Simple::CGI; -use base qw(HTTP::Server::Simple::CGI); - -my %dispatch = ( - '/hello' => \&resp_hello, -); - -sub handle_request { - my $self = shift; - my $cgi = shift; - - my $path = $cgi->path_info(); - my $handler = $dispatch{$path}; - - if (ref($handler) eq "CODE") { - print "HTTP/1.0 200 OK\r\n"; - $handler->($cgi); - - } else { - print "HTTP/1.0 404 Not found\r\n"; - print $cgi->header, - $cgi->start_html('Not found'), - $cgi->h1('Not found'), - $cgi->end_html; - } -} -sub resp_hello { - my $cgi = shift; - print $cgi->header, - "/tmp/http_datafile\r\n"; -} - + package MyWebServer; + + use HTTP::Server::Simple::CGI; + use base qw(HTTP::Server::Simple::CGI); + + my %dispatch = ('/hello' => \&resp_hello,); + + sub handle_request + { + my $self = shift; + my $cgi = shift; + + my $path = $cgi->path_info(); + my $handler = $dispatch{$path}; + + if (ref($handler) eq "CODE") + { + print "HTTP/1.0 200 OK\r\n"; + $handler->($cgi); + + } + else + { + print "HTTP/1.0 404 Not found\r\n"; + print $cgi->header, + $cgi->start_html('Not found'), + $cgi->h1('Not found'), + $cgi->end_html; + } + } + + sub resp_hello + { + my $cgi = shift; + print $cgi->header, "/tmp/http_datafile\r\n"; + } + } my $pid = MyWebServer->new(8888)->background(); @@ -58,10 +61,16 @@ sub resp_hello { PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');" +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); @@ -85,6 +94,8 @@ sub resp_hello { # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 52a55edfbbefe..a06a8cf6b7868 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -22,10 +22,16 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');" +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);'); @@ -47,6 +53,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index bdf63085e8a9c..85b5e8a1514c6 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -13,14 +13,19 @@ if (index(lc($PG_VERSION_STRING), lc("Percona Distribution")) == -1) { - plan skip_all => "pg_tde test case only for PPG server package install with extensions."; + plan skip_all => + "pg_tde test case only for PPG server package install with extensions."; } my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'"); -$node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_bucket_time = 360000"); -$node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_normalized_query = 'yes'"); +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'pg_tde, pg_stat_monitor, pgaudit, set_user, pg_repack'" +); +$node->append_conf('postgresql.conf', + "pg_stat_monitor.pgsm_bucket_time = 360000"); +$node->append_conf('postgresql.conf', + "pg_stat_monitor.pgsm_normalized_query = 'yes'"); open my $conf2, '>>', "/tmp/datafile-location"; print $conf2 "/tmp/keyring_data_file\n"; @@ -30,61 +35,115 @@ ok($rt_value == 1, "Start Server"); # Create PGSM extension -my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_stat_monitor;', extra_params => ['-a']); +my ($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS pg_stat_monitor;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE PGSM EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'SELECT pg_stat_monitor_reset();', extra_params => ['-a', '-Pformat=aligned','-Ptuples_only=off']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'SELECT pg_stat_monitor_reset();', + extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); ok($cmdret == 0, "Reset PGSM EXTENSION"); PGTDE::append_to_debug_file($stdout); # Create pg_tde extension -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS pg_tde;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_result_file($stdout); # Create Other extensions -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS pgaudit;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS pgaudit;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE pgaudit EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS set_user;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS set_user;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE set_user EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS pg_repack;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS pg_repack;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE pg_repack EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SET pgaudit.log = 'none'; CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis; SET pgaudit.log = 'all';", extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + "SET pgaudit.log = 'none'; CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis; SET pgaudit.log = 'all';", + extra_params => ['-a']); ok($cmdret == 0, "CREATE postgis EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_raster;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_raster;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE postgis_raster EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_sfcgal;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_sfcgal;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE postgis_sfcgal EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS fuzzystrmatch;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS fuzzystrmatch;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE fuzzystrmatch EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS address_standardizer;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS address_standardizer;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE address_standardizer EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS address_standardizer_data_us;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS address_standardizer_data_us;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE address_standardizer_data_us EXTENSION"); PGTDE::append_to_debug_file($stdout); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_tiger_geocoder;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'CREATE EXTENSION IF NOT EXISTS IF NOT EXISTS postgis_tiger_geocoder;', + extra_params => ['-a']); ok($cmdret == 0, "CREATE postgis_tiger_geocoder EXTENSION"); PGTDE::append_to_debug_file($stdout); -$rt_value = $node->psql('postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql('postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); +$rt_value = $node->psql( + 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", + extra_params => ['-a']); +$rt_value = $node->psql( + 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", + extra_params => ['-a']); -$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', + extra_params => ['-a']); PGTDE::append_to_result_file($stdout); -$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'INSERT INTO test_enc1 (k) VALUES (5),(6);', + extra_params => ['-a']); PGTDE::append_to_result_file($stdout); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'SELECT * FROM test_enc1 ORDER BY id ASC;', + extra_params => ['-a']); PGTDE::append_to_result_file($stdout); PGTDE::append_to_result_file("-- server restart"); @@ -92,19 +151,29 @@ $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'SELECT * FROM test_enc1 ORDER BY id ASC;', + extra_params => ['-a']); PGTDE::append_to_result_file($stdout); -$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc1;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'DROP TABLE test_enc1;', + extra_params => ['-a']); PGTDE::append_to_result_file($stdout); -# Print PGSM settings -($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT name, setting, unit, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, pending_restart FROM pg_settings WHERE name='pg_stat_monitor.pgsm_query_shared_buffer';", extra_params => ['-a', '-Pformat=aligned','-Ptuples_only=off']); +# Print PGSM settings +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + "SELECT name, setting, unit, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, pending_restart FROM pg_settings WHERE name='pg_stat_monitor.pgsm_query_shared_buffer';", + extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); ok($cmdret == 0, "Print PGTDE EXTENSION Settings"); PGTDE::append_to_debug_file($stdout); # Create example database and run pgbench init -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE database example;', extra_params => ['-a']); +($cmdret, $stdout, $stderr) = + $node->psql('postgres', 'CREATE database example;', extra_params => ['-a']); print "cmdret $cmdret\n"; ok($cmdret == 0, "CREATE Database example"); PGTDE::append_to_debug_file($stdout); @@ -112,23 +181,32 @@ my $port = $node->port; print "port $port \n"; -my $out = system ("pgbench -i -s 20 -p $port example"); +my $out = system("pgbench -i -s 20 -p $port example"); print " out: $out \n"; ok($cmdret == 0, "Perform pgbench init"); -$out = system ("pgbench -c 10 -j 2 -t 5000 -p $port example"); +$out = system("pgbench -c 10 -j 2 -t 5000 -p $port example"); print " out: $out \n"; ok($cmdret == 0, "Run pgbench"); -($cmdret, $stdout, $stderr) = $node->psql('postgres', 'SELECT datname, substr(query,0,150) AS query, SUM(calls) AS calls FROM pg_stat_monitor GROUP BY datname, query ORDER BY datname, query, calls;', extra_params => ['-a', '-Pformat=aligned','-Ptuples_only=off']); +($cmdret, $stdout, $stderr) = $node->psql( + 'postgres', + 'SELECT datname, substr(query,0,150) AS query, SUM(calls) AS calls FROM pg_stat_monitor GROUP BY datname, query ORDER BY datname, query, calls;', + extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); ok($cmdret == 0, "SELECT XXX FROM pg_stat_monitor"); PGTDE::append_to_debug_file($stdout); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'DROP EXTENSION pg_tde;', + extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_result_file($stdout); -$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_stat_monitor;', extra_params => ['-a']); +$stdout = $node->safe_psql( + 'postgres', + 'DROP EXTENSION pg_stat_monitor;', + extra_params => ['-a']); ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_debug_file($stdout); @@ -137,6 +215,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 58af6bde07ca6..d7f274159c1c7 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -9,48 +9,52 @@ use pgtde; { -package MyWebServer; - -use HTTP::Server::Simple::CGI; -use base qw(HTTP::Server::Simple::CGI); - -my %dispatch = ( - '/token' => \&resp_token, - '/url' => \&resp_url, -); - -sub handle_request { - my $self = shift; - my $cgi = shift; - - my $path = $cgi->path_info(); - my $handler = $dispatch{$path}; - - if (ref($handler) eq "CODE") { - print "HTTP/1.0 200 OK\r\n"; - $handler->($cgi); - - } else { - print "HTTP/1.0 404 Not found\r\n"; - print $cgi->header, - $cgi->start_html('Not found'), - $cgi->h1('Not found'), - $cgi->end_html; - } -} -sub resp_token { - my $cgi = shift; - print $cgi->header, - "$ENV{'ROOT_TOKEN'}\r\n"; -} + package MyWebServer; + + use HTTP::Server::Simple::CGI; + use base qw(HTTP::Server::Simple::CGI); + + my %dispatch = ( + '/token' => \&resp_token, + '/url' => \&resp_url,); + + sub handle_request + { + my $self = shift; + my $cgi = shift; + + my $path = $cgi->path_info(); + my $handler = $dispatch{$path}; + + if (ref($handler) eq "CODE") + { + print "HTTP/1.0 200 OK\r\n"; + $handler->($cgi); + + } + else + { + print "HTTP/1.0 404 Not found\r\n"; + print $cgi->header, + $cgi->start_html('Not found'), + $cgi->h1('Not found'), + $cgi->end_html; + } + } + + sub resp_token + { + my $cgi = shift; + print $cgi->header, "$ENV{'ROOT_TOKEN'}\r\n"; + } + + sub resp_url + { + my $cgi = shift; + print $cgi->header, "http://127.0.0.1:8200\r\n"; + } -sub resp_url { - my $cgi = shift; - print $cgi->header, - "http://127.0.0.1:8200\r\n"; -} - } my $pid = MyWebServer->new(8889)->background(); @@ -66,10 +70,16 @@ sub resp_url { PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');" +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); @@ -93,6 +103,8 @@ sub resp_url { # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 26f47863d078c..bb5ec672fa8b3 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -18,63 +18,87 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::append_to_result_file("-- server restart"); $node->stop(); $rt_value = $node->start(); ok($rt_value == 1, "Restart Server"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" +); ######################### test_enc1 (simple create table w tde_heap) -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' +); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc1 (k) VALUES (\'foobar\'),(\'barfoo\');'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); ######################### test_enc2 (create heap + alter to tde_heap) -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc2 (k) VALUES (\'foobar\'),(\'barfoo\');'); -PGTDE::psql($node, 'postgres', 'ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap;'); +PGTDE::psql($node, 'postgres', + 'ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap;'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); ######################### test_enc3 (default_table_access_method) -PGTDE::psql($node, 'postgres', 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); +PGTDE::psql($node, 'postgres', + 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));' +); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc3 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc3 (k) VALUES (\'foobar\'),(\'barfoo\');'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc3 ORDER BY id ASC;'); ######################### test_enc4 (create heap + alter default) -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap;' +); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc4 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc4 (k) VALUES (\'foobar\'),(\'barfoo\');'); -PGTDE::psql($node, 'postgres', 'SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT;'); +PGTDE::psql($node, 'postgres', + 'SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT;' +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc4 ORDER BY id ASC;'); ######################### test_enc5 (create tde_heap + truncate) -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' +); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); PGTDE::psql($node, 'postgres', 'CHECKPOINT;'); PGTDE::psql($node, 'postgres', 'TRUNCATE test_enc5;'); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); +PGTDE::psql($node, 'postgres', + 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;'); @@ -85,23 +109,25 @@ sub verify_table { - PGTDE::append_to_result_file('###########################'); + PGTDE::append_to_result_file('###########################'); - my ($table) = @_; + my ($table) = @_; - my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); - $tablefile .= '/'; - $tablefile .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\''.$table.'\');'); + my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); + $tablefile .= '/'; + $tablefile .= $node->safe_psql('postgres', + 'SELECT pg_relation_filepath(\'' . $table . '\');'); - PGTDE::psql($node, 'postgres', 'SELECT * FROM ' . $table . ' ORDER BY id ASC;'); + PGTDE::psql($node, 'postgres', + 'SELECT * FROM ' . $table . ' ORDER BY id ASC;'); - my $strings = 'TABLEFILE FOR ' . $table . ' FOUND: '; - $strings .= `(ls $tablefile >/dev/null && echo -n yes) || echo -n no`; - PGTDE::append_to_result_file($strings); + my $strings = 'TABLEFILE FOR ' . $table . ' FOUND: '; + $strings .= `(ls $tablefile >/dev/null && echo -n yes) || echo -n no`; + PGTDE::append_to_result_file($strings); - $strings = 'CONTAINS FOO (should be empty): '; - $strings .= `strings $tablefile | grep foo`; - PGTDE::append_to_result_file($strings); + $strings = 'CONTAINS FOO (should be empty): '; + $strings .= `strings $tablefile | grep foo`; + PGTDE::append_to_result_file($strings); } verify_table('test_enc1'); @@ -113,9 +139,10 @@ sub verify_table # Verify that we can't see the data in the file my $tablefile2 = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile2 .= '/'; -$tablefile2 .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc2\');'); +$tablefile2 .= + $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc2\');'); -my $strings = 'TABLEFILE2 FOUND: '; +my $strings = 'TABLEFILE2 FOUND: '; $strings .= `(ls $tablefile2 >/dev/null && echo yes) || echo no`; PGTDE::append_to_result_file($strings); @@ -126,7 +153,8 @@ sub verify_table # Verify that we can't see the data in the file my $tablefile3 = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile3 .= '/'; -$tablefile3 .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc3\');'); +$tablefile3 .= + $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc3\');'); $strings = 'TABLEFILE3 FOUND: '; $strings .= `(ls $tablefile3 >/dev/null && echo yes) || echo no`; @@ -139,7 +167,8 @@ sub verify_table # Verify that we can't see the data in the file my $tablefile4 = $node->safe_psql('postgres', 'SHOW data_directory;'); $tablefile4 .= '/'; -$tablefile4 .= $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc4\');'); +$tablefile4 .= + $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc4\');'); $strings = 'TABLEFILE4 FOUND: '; $strings .= `(ls $tablefile4 >/dev/null && echo yes) || echo no`; @@ -162,6 +191,8 @@ sub verify_table # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 73ed7b24cdbf8..6697fe6cc1af0 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -16,14 +16,22 @@ my $rt_value = $node->start; ok($rt_value == 1, "Start Server"); -PGTDE::psql($node, 'postgres', "SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION '';"); -PGTDE::psql($node, 'postgres', 'CREATE DATABASE tbc TABLESPACE = test_tblspace;'); +PGTDE::psql($node, 'postgres', + "SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION '';" +); +PGTDE::psql($node, 'postgres', + 'CREATE DATABASE tbc TABLESPACE = test_tblspace;'); PGTDE::psql($node, 'tbc', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'tbc', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');"); -PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');"); - -PGTDE::psql($node, 'tbc', " +PGTDE::psql($node, 'tbc', + "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" +); +PGTDE::psql($node, 'tbc', + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" +); + +PGTDE::psql( + $node, 'tbc', " CREATE TABLE country_table ( country_id serial primary key, country_name text unique not null, @@ -31,7 +39,8 @@ ) USING tde_heap; "); -PGTDE::psql($node, 'tbc', " +PGTDE::psql( + $node, 'tbc', " INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), ('UK', 'Europe'), @@ -40,7 +49,9 @@ PGTDE::psql($node, 'tbc', 'SELECT * FROM country_table;'); -PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');"); +PGTDE::psql($node, 'tbc', + "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');" +); PGTDE::append_to_result_file("-- server restart"); $node->stop(); @@ -59,6 +70,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index 6db8332e97b97..a02888901ce50 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -21,9 +21,13 @@ PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');" +); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');" +); PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); @@ -34,9 +38,12 @@ PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); -PGTDE::psql($node, 'postgres', "SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding');"); +PGTDE::psql($node, 'postgres', + "SELECT slot_name FROM pg_create_logical_replication_slot('tde_slot', 'test_decoding');" +); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id));'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_wal (id SERIAL, k INTEGER, PRIMARY KEY (id));'); PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (1), (2);'); @@ -71,9 +78,11 @@ PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (7), (8);'); -PGTDE::psql($node, 'postgres', "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);"); +PGTDE::psql($node, 'postgres', + "SELECT data FROM pg_logical_slot_get_changes('tde_slot', NULL, NULL);"); -PGTDE::psql($node, 'postgres', "SELECT pg_drop_replication_slot('tde_slot');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_drop_replication_slot('tde_slot');"); PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); @@ -82,6 +91,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 79bd8cc31c755..0ed471d45f715 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -24,11 +24,18 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); - -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_list_all_database_key_providers();"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" +); + +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -36,10 +43,14 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Change provider and move file -PGTDE::append_to_result_file("-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); +PGTDE::append_to_result_file( + "-- mv /tmp/change_key_provider_1.per /tmp/change_key_provider_2.per"); move('/tmp/change_key_provider_1.per', '/tmp/change_key_provider_2.per'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); @@ -56,8 +67,11 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Change provider and do not move file -PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); @@ -73,7 +87,8 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::append_to_result_file("-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); +PGTDE::append_to_result_file( + "-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); PGTDE::append_to_result_file("-- server restart"); @@ -91,17 +106,25 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); # Change provider and generate a new principal key -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); - -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" +); + +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;' +); PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');" +); PGTDE::append_to_result_file("-- server restart"); $node->stop(); @@ -112,9 +135,13 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::psql($node, 'postgres', 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'); +PGTDE::psql($node, 'postgres', + 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;' +); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');" +); # Verify PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -128,6 +155,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 7bc68861fb7c8..8ad9c879efd5e 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -17,10 +17,15 @@ ok($rt_value == 1, "Start Server"); PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" +); -PGTDE::psql($node, 'postgres', "CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::psql($node, 'postgres', + "CREATE UNLOGGED TABLE t (x int PRIMARY KEY) USING tde_heap;"); PGTDE::psql($node, 'postgres', "INSERT INTO t SELECT generate_series(1, 4);"); @@ -42,6 +47,8 @@ # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 68eaf4c300d1a..16b98c392a5b4 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -21,57 +21,64 @@ my $results_folder = "t/results"; sub psql { - my ($node, $dbname, $sql) = @_; + my ($node, $dbname, $sql) = @_; - my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, extra_params => ['-a', '-Pformat=aligned', '-Ptuples_only=off']); + my (undef, $stdout, $stderr) = $node->psql($dbname, $sql, + extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); - if ($stdout ne '') { - append_to_result_file($stdout); - } + if ($stdout ne '') + { + append_to_result_file($stdout); + } - if ($stderr ne '') { - append_to_result_file($stderr); - } + if ($stderr ne '') + { + append_to_result_file($stderr); + } } sub append_to_result_file { - my ($str) = @_; + my ($str) = @_; - append_to_file($out_filename_with_path, $str . "\n"); + append_to_file($out_filename_with_path, $str . "\n"); } sub append_to_debug_file { - my ($str) = @_; + my ($str) = @_; - append_to_file($debug_out_filename_with_path, $str . "\n"); + append_to_file($debug_out_filename_with_path, $str . "\n"); } sub setup_files_dir { - my ($test_filename) = @_; - - unless (-d $results_folder) - { - mkdir $results_folder or die "Can't create folder $results_folder: $!\n"; - } - - my ($test_name) = $test_filename =~ /([^.]*)/; - - $expected_filename_with_path = "${expected_folder}/${test_name}.out"; - $out_filename_with_path = "${results_folder}/${test_name}.out"; - $debug_out_filename_with_path = "${results_folder}/${test_name}.out.debug"; - - if (-f $out_filename_with_path) - { - unlink($out_filename_with_path) or die "Can't delete already existing $out_filename_with_path: $!\n"; - } + my ($test_filename) = @_; + + unless (-d $results_folder) + { + mkdir $results_folder + or die "Can't create folder $results_folder: $!\n"; + } + + my ($test_name) = $test_filename =~ /([^.]*)/; + + $expected_filename_with_path = "${expected_folder}/${test_name}.out"; + $out_filename_with_path = "${results_folder}/${test_name}.out"; + $debug_out_filename_with_path = + "${results_folder}/${test_name}.out.debug"; + + if (-f $out_filename_with_path) + { + unlink($out_filename_with_path) + or die + "Can't delete already existing $out_filename_with_path: $!\n"; + } } sub compare_results { - return compare($expected_filename_with_path, $out_filename_with_path); + return compare($expected_filename_with_path, $out_filename_with_path); } 1; From 681b9ff1cdd9f3950dc328c1825e0a38e820556b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 21 Apr 2025 14:30:38 +0200 Subject: [PATCH 082/796] Run pgperltidy in github actions Just as we use pgindent to validate that our c files conform to postgres coding standards, we also run pgperltidy to do the same for perl files. We only run it on our own code in contrib/pg_tde/ This doesn't actually run pgperltidy as we need to inject some options in a way that didn't seem possible in that script. Instead it does the same thing with some slight changes. We also bump the ubuntu version for this google actions job to the newest LTS as the older ubuntu version seems to have a version of perltidy that doesn't support the options used by pgperltidy. --- .github/workflows/pgindent.yml | 7 +++++-- ci_scripts/run-pgperltidy.sh | 8 ++++++++ ci_scripts/ubuntu-deps.sh | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100755 ci_scripts/run-pgperltidy.sh diff --git a/.github/workflows/pgindent.yml b/.github/workflows/pgindent.yml index 211d255f07042..c65ee645a8ba2 100644 --- a/.github/workflows/pgindent.yml +++ b/.github/workflows/pgindent.yml @@ -10,7 +10,7 @@ defaults: jobs: check: name: Check - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Clone repository uses: actions/checkout@v4 @@ -26,6 +26,9 @@ jobs: - name: Update typedefs run: ci_scripts/dump-typedefs.sh - + - name: Run pgindent run: ci_scripts/run-pgindent.sh --check --diff + + - name: Run pgperltidy + run: ci_scripts/run-pgperltidy.sh --assert-tidy --standard-error-output diff --git a/ci_scripts/run-pgperltidy.sh b/ci_scripts/run-pgperltidy.sh new file mode 100755 index 0000000000000..c59d50cc2a8f0 --- /dev/null +++ b/ci_scripts/run-pgperltidy.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +cd "$SCRIPT_DIR/../" + +source src/tools/perlcheck/find_perl_files + +find_perl_files contrib/pg_tde/ | xargs perltidy "$@" --profile=src/tools/pgindent/perltidyrc diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index 52a65745c816c..f8028187dcfa7 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -43,6 +43,8 @@ DEPS=( python3-pykmip libhttp-server-simple-perl lcov + # Run pgperltidy + perltidy ) sudo apt-get update From ec51d0895a1ce521bb64b3bf8f8a839b605d8a84 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 19 Apr 2025 03:10:13 +0200 Subject: [PATCH 083/796] PG-1444 Remove dead code for relation key deletion redo This code is dead and there is no plan to re-use it any time soon. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 14 -------------- contrib/pg_tde/src/include/access/pg_tde_xlog.h | 9 ++++----- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index d37a1c18ca3fc..7417d85beecdd 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -54,12 +54,6 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_write_key_map_entry_redo(&xlrec->mapEntry, &xlrec->pkInfo); } - else if (info == XLOG_TDE_REMOVE_RELATION_KEY) - { - RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); - - pg_tde_free_key_map_entry(xlrec, 0); - } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { TDESignedPrincipalKeyInfo *mkey = (TDESignedPrincipalKeyInfo *) XLogRecGetData(record); @@ -101,12 +95,6 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "rel: %u/%u/%u", xlrec->mapEntry.spcOid, xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); } - else if (info == XLOG_TDE_REMOVE_RELATION_KEY) - { - RelFileLocator *xlrec = (RelFileLocator *) XLogRecGetData(record); - - appendStringInfo(buf, "rel: %u/%u/%u", xlrec->spcOid, xlrec->dbOid, xlrec->relNumber); - } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); @@ -140,8 +128,6 @@ tdeheap_rmgr_identify(uint8 info) { case XLOG_TDE_ADD_RELATION_KEY: return "ADD_RELATION_KEY"; - case XLOG_TDE_REMOVE_RELATION_KEY: - return "REMOVE_RELATION_KEY"; case XLOG_TDE_ADD_PRINCIPAL_KEY: return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_ROTATE_PRINCIPAL_KEY: diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 0d8d99b012d2a..21823478d7580 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -15,11 +15,10 @@ /* TDE XLOG record types */ #define XLOG_TDE_ADD_RELATION_KEY 0x00 -#define XLOG_TDE_REMOVE_RELATION_KEY 0x10 -#define XLOG_TDE_ADD_PRINCIPAL_KEY 0x20 -#define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x30 -#define XLOG_TDE_WRITE_KEY_PROVIDER 0x40 -#define XLOG_TDE_INSTALL_EXTENSION 0x50 +#define XLOG_TDE_ADD_PRINCIPAL_KEY 0x10 +#define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x20 +#define XLOG_TDE_WRITE_KEY_PROVIDER 0x30 +#define XLOG_TDE_INSTALL_EXTENSION 0x40 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 From a6f774e57ee8931ecdf13c9a63a044b7fc5c0793 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 19 Apr 2025 03:10:28 +0200 Subject: [PATCH 084/796] PG-1444 Move relation key deleteion to smgr_unlink() Replaces the old way we deleted keys which was built for tde_heap_basic with deleting the the relation key when smgr_unlink() is called on the main fork. This function is always called after commit/abort when a relation deletion has been registered, even if no main fork would exist. This approach means we do not need to WAL log any event for deleting relation keys, the normal SMGR unlink also handles that which fits well into the current approach of doing most of the encryption at the SMGR layer. We also remove the subtransaction test which is no longer useful since it tested things very specific to the old key deleteion. --- contrib/pg_tde/Makefile | 2 - contrib/pg_tde/expected/subtransaction.out | 30 --- contrib/pg_tde/meson.build | 2 - contrib/pg_tde/sql/subtransaction.sql | 25 --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 91 ++------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- .../src/include/transam/pg_tde_xact_handler.h | 18 -- contrib/pg_tde/src/pg_tde.c | 2 - contrib/pg_tde/src/smgr/pg_tde_smgr.c | 24 ++- .../pg_tde/src/transam/pg_tde_xact_handler.c | 186 ------------------ 10 files changed, 40 insertions(+), 342 deletions(-) delete mode 100644 contrib/pg_tde/expected/subtransaction.out delete mode 100644 contrib/pg_tde/sql/subtransaction.sql delete mode 100644 contrib/pg_tde/src/include/transam/pg_tde_xact_handler.h delete mode 100644 contrib/pg_tde/src/transam/pg_tde_xact_handler.c diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 4ec814004e9ab..2c3567cbd6e63 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -21,7 +21,6 @@ partition_table \ pg_tde_is_encrypted \ recreate_storage \ relocate \ -subtransaction \ tablespace \ vault_v2_test \ version \ @@ -33,7 +32,6 @@ src/encryption/enc_aes.o \ src/access/pg_tde_tdemap.o \ src/access/pg_tde_xlog.o \ src/access/pg_tde_xlog_encrypt.o \ -src/transam/pg_tde_xact_handler.o \ src/keyring/keyring_curl.o \ src/keyring/keyring_file.o \ src/keyring/keyring_vault.o \ diff --git a/contrib/pg_tde/expected/subtransaction.out b/contrib/pg_tde/expected/subtransaction.out deleted file mode 100644 index ebfde89210a5b..0000000000000 --- a/contrib/pg_tde/expected/subtransaction.out +++ /dev/null @@ -1,30 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -BEGIN; -- Nesting level 1 -SAVEPOINT sp; -CREATE TABLE foo(s TEXT); -- Nesting level 2 -RELEASE SAVEPOINT sp; -SAVEPOINT sp; -CREATE TABLE bar(s TEXT); -- Nesting level 2 -ROLLBACK TO sp; -- Rollback should not affect first subtransaction -COMMIT; -BEGIN; -- Nesting level 1 -SAVEPOINT sp; -DROP TABLE foo; -- Nesting level 2 -RELEASE SAVEPOINT sp; -SAVEPOINT sp; -CREATE TABLE bar(s TEXT); -- Nesting level 2 -ROLLBACK TO sp; -- Rollback should not affect first subtransaction -COMMIT; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index da12b21a0e42d..9f28aef15af42 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -21,7 +21,6 @@ pg_tde_sources = files( 'src/pg_tde_event_capture.c', 'src/pg_tde_guc.c', 'src/smgr/pg_tde_smgr.c', - 'src/transam/pg_tde_xact_handler.c', ) tde_frontend_sources = files( @@ -97,7 +96,6 @@ sql_tests = [ 'pg_tde_is_encrypted', 'relocate', 'recreate_storage', - 'subtransaction', 'tablespace', 'vault_v2_test', 'version', diff --git a/contrib/pg_tde/sql/subtransaction.sql b/contrib/pg_tde/sql/subtransaction.sql deleted file mode 100644 index 121f1b67c3ab1..0000000000000 --- a/contrib/pg_tde/sql/subtransaction.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; - -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); - - -BEGIN; -- Nesting level 1 -SAVEPOINT sp; -CREATE TABLE foo(s TEXT); -- Nesting level 2 -RELEASE SAVEPOINT sp; -SAVEPOINT sp; -CREATE TABLE bar(s TEXT); -- Nesting level 2 -ROLLBACK TO sp; -- Rollback should not affect first subtransaction -COMMIT; - -BEGIN; -- Nesting level 1 -SAVEPOINT sp; -DROP TABLE foo; -- Nesting level 2 -RELEASE SAVEPOINT sp; -SAVEPOINT sp; -CREATE TABLE bar(s TEXT); -- Nesting level 2 -ROLLBACK TO sp; -- Rollback should not affect first subtransaction -COMMIT; - -DROP EXTENSION pg_tde; \ No newline at end of file diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index b0b7fd972679b..316e0b0940807 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -13,7 +13,6 @@ #include "postgres.h" #include "access/pg_tde_tdemap.h" #include "common/file_perm.h" -#include "transam/pg_tde_xact_handler.h" #include "storage/fd.h" #include "utils/wait_event.h" #include "utils/memutils.h" @@ -129,7 +128,6 @@ static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDES static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static off_t pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog); -static bool pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t offset); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); @@ -486,9 +484,6 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ /* Let's close the file. */ close(map_fd); - - /* Register the entry to be freed in case the transaction aborts */ - RegisterEntryForDeletion(rlocator, curr_pos, false); } /* @@ -548,43 +543,33 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri LWLockRelease(tde_lwlock_enc_keys()); } -static bool -pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t offset) +/* + * Mark relation map entry as free and overwrite the key + * + * This fucntion is called by the pg_tde SMGR when storage is unlinked on + * transaction commit/abort. + */ +void +pg_tde_free_key_map_entry(const RelFileLocator *rlocator) { + char db_map_path[MAXPGPATH]; File map_fd; - bool found = false; off_t curr_pos = 0; - /* Open and validate file for basic correctness. */ - map_fd = pg_tde_open_file_write(db_map_path, NULL, false, &curr_pos); + Assert(rlocator); - /* - * If we need to delete an entry, we expect an offset value to the start - * of the entry to speed up the operation. Otherwise, we'd be sequentially - * scanning the entire map file. - */ - if (offset > 0) - { - curr_pos = lseek(map_fd, offset, SEEK_SET); + pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); - if (curr_pos == -1) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not seek in tde map file \"%s\": %m", - db_map_path)); - } - } + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + /* Open and validate file for basic correctness. */ + map_fd = pg_tde_open_file_write(db_map_path, NULL, false, &curr_pos); - /* - * Read until we find an empty slot. Otherwise, read until end. This seems - * to be less frequent than vacuum. So let's keep this function here - * rather than overloading the vacuum process. - */ while (1) { TDEMapEntry read_map_entry; off_t prev_pos = curr_pos; + bool found; found = pg_tde_read_one_map_entry(map_fd, rlocator, MAP_ENTRY_VALID, &read_map_entry, &curr_pos); @@ -592,7 +577,6 @@ pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t if (curr_pos == prev_pos) break; - /* We found a valid entry for the relation */ if (found) { TDEMapEntry empty_map_entry = { @@ -607,52 +591,9 @@ pg_tde_delete_map_entry(const RelFileLocator *rlocator, char *db_map_path, off_t } } - /* Let's close the file. */ close(map_fd); - /* Return -1 indicating that no entry was removed */ - return found; -} - -/* - * Called when transaction is being completed; either committed or aborted. - * By default, when a transaction creates an entry, we mark it as MAP_ENTRY_VALID. - * Only during the abort phase of the transaction that we are proceed on with - * marking the entry as MAP_ENTRY_FREE. This optimistic strategy that assumes - * that transaction will commit more often then getting aborted avoids - * unnecessary locking. - * - * The offset allows us to simply seek to the desired location and mark the entry - * as MAP_ENTRY_FREE without needing any further processing. - */ -void -pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset) -{ - bool found; - char db_map_path[MAXPGPATH] = {0}; - - Assert(rlocator); - - /* Get the file paths */ - pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); - - LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - - /* Remove the map entry if found */ - found = pg_tde_delete_map_entry(rlocator, db_map_path, offset); - LWLockRelease(tde_lwlock_enc_keys()); - - if (!found) - { - ereport(WARNING, - errcode(ERRCODE_NO_DATA_FOUND), - errmsg("could not find the required map entry for deletion of relation %d in tablespace %d in tde map file \"%s\": %m", - rlocator->relNumber, - rlocator->spcOid, - db_map_path)); - - } } /* diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 20f0acd269ff0..da0f46691410d 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -101,7 +101,7 @@ extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); -extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator, off_t offset); +extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info); #define PG_TDE_MAP_FILENAME "pg_tde_%d_map" diff --git a/contrib/pg_tde/src/include/transam/pg_tde_xact_handler.h b/contrib/pg_tde/src/include/transam/pg_tde_xact_handler.h deleted file mode 100644 index 7838423ae9502..0000000000000 --- a/contrib/pg_tde/src/include/transam/pg_tde_xact_handler.h +++ /dev/null @@ -1,18 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_xact_handler.h - * TDE transaction handling. - * - *------------------------------------------------------------------------- - */ -#ifndef PG_TDE_XACT_HANDLER_H -#define PG_TDE_XACT_HANDLER_H - -#include "postgres.h" -#include "storage/relfilelocator.h" - -extern void RegisterTdeXactCallbacks(void); -extern void RegisterEntryForDeletion(const RelFileLocator *rlocator, off_t map_entry_offset, bool atCommit); - - -#endif /* PG_TDE_XACT_HANDLER_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index aedb8eb699439..77ee55609a8c9 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -13,7 +13,6 @@ #include "postgres.h" #include "funcapi.h" #include "pg_tde.h" -#include "transam/pg_tde_xact_handler.h" #include "miscadmin.h" #include "storage/ipc.h" #include "storage/lwlock.h" @@ -121,7 +120,6 @@ _PG_init(void) prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = tde_shmem_startup; - RegisterTdeXactCallbacks(); InstallFileKeyring(); InstallVaultV2Keyring(); InstallKmipKeyring(); diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index a83fb9ebda82a..9b11f57acedfa 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -115,6 +115,28 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, } } +static void +tde_mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo) +{ + mdunlink(rlocator, forknum, isRedo); + + /* + * As of PostgreSQL 17 we are called once per forks, no matter if they + * exist or not, from smgrdounlinkall() so deleting the relation key on + * attempting to delete the main fork is safe. Additionally since we + * unlink the files after commit/abort we do not need to care about + * concurrent accesses. + * + * We support InvalidForkNumber to be similar to mdunlink() but it can + * actually never happen. + */ + if (forknum == MAIN_FORKNUM || forknum == InvalidForkNumber) + { + if (!RelFileLocatorBackendIsTemp(rlocator) && GetSMGRRelationKey(rlocator)) + pg_tde_free_key_map_entry(&rlocator.locator); + } +} + static void tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync) @@ -274,7 +296,7 @@ static const struct f_smgr tde_smgr = { .smgr_close = mdclose, .smgr_create = tde_mdcreate, .smgr_exists = mdexists, - .smgr_unlink = mdunlink, + .smgr_unlink = tde_mdunlink, .smgr_extend = tde_mdextend, .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, diff --git a/contrib/pg_tde/src/transam/pg_tde_xact_handler.c b/contrib/pg_tde/src/transam/pg_tde_xact_handler.c deleted file mode 100644 index 3f0a84df69f9e..0000000000000 --- a/contrib/pg_tde/src/transam/pg_tde_xact_handler.c +++ /dev/null @@ -1,186 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_xact_handler.c - * Transaction handling routines for pg_tde - * - * - * IDENTIFICATION - * src/transam/pg_tde_xact_handler.c - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" -#include "access/xact.h" -#include "utils/memutils.h" -#include "utils/palloc.h" -#include "utils/elog.h" -#include "storage/fd.h" -#include "transam/pg_tde_xact_handler.h" -#include "access/pg_tde_tdemap.h" - -typedef struct PendingMapEntryDelete -{ - off_t map_entry_offset; /* map entry offset */ - RelFileLocator rlocator; /* main for use as relation OID */ - bool atCommit; /* T=delete at commit; F=delete at abort */ - int nestLevel; /* xact nesting level of request */ - struct PendingMapEntryDelete *next; /* linked-list link */ -} PendingMapEntryDelete; - -static PendingMapEntryDelete *pendingDeletes = NULL; /* head of linked list */ - -static void do_pending_deletes(bool isCommit); -static void reassign_pending_deletes_to_parent_xact(void); -static void pending_delete_cleanup(void); - -/* Transaction Callbacks from Backend*/ -static void -pg_tde_xact_callback(XactEvent event, void *arg) -{ - if (event == XACT_EVENT_PARALLEL_ABORT || - event == XACT_EVENT_ABORT) - { - ereport(DEBUG2, errmsg("pg_tde_xact_callback: aborting transaction")); - do_pending_deletes(false); - } - else if (event == XACT_EVENT_COMMIT) - { - do_pending_deletes(true); - pending_delete_cleanup(); - } - else if (event == XACT_EVENT_PREPARE) - { - pending_delete_cleanup(); - } -} - -static void -pg_tde_subxact_callback(SubXactEvent event, SubTransactionId mySubid, - SubTransactionId parentSubid, void *arg) -{ - /* TODO: takle all possible transaction states */ - if (event == SUBXACT_EVENT_ABORT_SUB) - { - ereport(DEBUG2, - errmsg("pg_tde_subxact_callback: aborting subtransaction")); - do_pending_deletes(false); - } - else if (event == SUBXACT_EVENT_COMMIT_SUB) - { - ereport(DEBUG2, - errmsg("pg_tde_subxact_callback: committing subtransaction")); - reassign_pending_deletes_to_parent_xact(); - } -} - -void -RegisterTdeXactCallbacks(void) -{ - RegisterXactCallback(pg_tde_xact_callback, NULL); - RegisterSubXactCallback(pg_tde_subxact_callback, NULL); -} - -void -RegisterEntryForDeletion(const RelFileLocator *rlocator, off_t map_entry_offset, bool atCommit) -{ - PendingMapEntryDelete *pending; - - pending = (PendingMapEntryDelete *) MemoryContextAlloc(TopMemoryContext, sizeof(PendingMapEntryDelete)); - pending->map_entry_offset = map_entry_offset; - pending->rlocator = *rlocator; - pending->atCommit = atCommit; /* delete if abort */ - pending->nestLevel = GetCurrentTransactionNestLevel(); - pending->next = pendingDeletes; - pendingDeletes = pending; -} - -/* - * do_pending_deletes() -- Take care of file deletes at end of xact. - * - * This also runs when aborting a subxact; we want to clean up a failed - * subxact immediately. - * - */ -static void -do_pending_deletes(bool isCommit) -{ - int nestLevel = GetCurrentTransactionNestLevel(); - PendingMapEntryDelete *pending; - PendingMapEntryDelete *prev; - PendingMapEntryDelete *next; - - prev = NULL; - for (pending = pendingDeletes; pending != NULL; pending = next) - { - next = pending->next; - if (pending->nestLevel != nestLevel) - { - /* outer-level entries should not be processed yet */ - prev = pending; - continue; - } - - /* unlink list entry first, so we don't retry on failure */ - if (prev) - prev->next = next; - else - pendingDeletes = next; - /* do deletion if called for */ - if (pending->atCommit == isCommit) - { - ereport(LOG, - errmsg("pg_tde_xact_callback: deleting entry at offset %d", - (int) (pending->map_entry_offset))); - pg_tde_free_key_map_entry(&pending->rlocator, pending->map_entry_offset); - } - pfree(pending); - /* prev does not change */ - - } -} - - -/* - * reassign_pending_deletes_to_parent_xact() -- Adjust nesting level of pending deletes. - * - * There are several cases to consider: - * 1. Only top level transaction can perform on-commit deletes. - * 2. Subtransaction and top level transaction can perform on-abort deletes. - * So we have to decrement the nesting level of pending deletes to reassing them to the parent transaction - * if subtransaction was not self aborted. In other words if subtransaction state is commited all its pending - * deletes are reassigned to the parent transaction. - */ -static void -reassign_pending_deletes_to_parent_xact(void) -{ - PendingMapEntryDelete *pending; - int nestLevel = GetCurrentTransactionNestLevel(); - - for (pending = pendingDeletes; pending != NULL; pending = pending->next) - { - if (pending->nestLevel == nestLevel) - pending->nestLevel--; - } -} - -/* - * pending_delete_cleanup -- Clean up after a successful PREPARE or COMMIT - * - * What we have to do here is throw away the in-memory state about pending - * file deletes. It's all been recorded in the 2PC state file and - * it's no longer our job to worry about it. - */ -static void -pending_delete_cleanup(void) -{ - PendingMapEntryDelete *pending; - PendingMapEntryDelete *next; - - for (pending = pendingDeletes; pending != NULL; pending = next) - { - next = pending->next; - pendingDeletes = next; - pfree(pending); - } -} From 5f279ad13b82f003a12e2b584d4fd3e4e1ff5391 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 19 Apr 2025 05:07:52 +0200 Subject: [PATCH 085/796] Fix broken reuse of deleted entries in key map file Since we tried to check if flags & MAP_ENTRY_EMPTY was true when searching for empty entries the code was broken since x & 0 always is false. We fix this by refactoring pg_tde_read_one_map_entry() so the filtering of the entries is done outside the function. This make implementing search for empty entries much easier. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 92 +++++-------------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 1 - 2 files changed, 24 insertions(+), 69 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 316e0b0940807..5d6d3f385331c 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -113,7 +113,7 @@ static TDEMapEntry *pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); -static bool pg_tde_read_one_map_entry(int fd, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset); +static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset); static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); static int pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos); static InternalKey *pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type); @@ -447,16 +447,12 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ while (1) { TDEMapEntry read_map_entry; - bool found; prev_pos = curr_pos; - found = pg_tde_read_one_map_entry(map_fd, NULL, MAP_ENTRY_EMPTY, &read_map_entry, &curr_pos); + if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) + break; - /* - * We either reach EOF or found an empty slot in the middle of the - * file - */ - if (prev_pos == curr_pos || found) + if (read_map_entry.flags == MAP_ENTRY_EMPTY) break; } @@ -517,16 +513,12 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri while (1) { TDEMapEntry read_map_entry; - bool found; prev_pos = curr_pos; - found = pg_tde_read_one_map_entry(map_fd, NULL, MAP_ENTRY_EMPTY, &read_map_entry, &curr_pos); + if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) + break; - /* - * We either reach EOF or found an empty slot in the middle of the - * file - */ - if (prev_pos == curr_pos || found) + if (read_map_entry.flags == MAP_ENTRY_EMPTY) break; } @@ -567,17 +559,13 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator) while (1) { - TDEMapEntry read_map_entry; + TDEMapEntry map_entry; off_t prev_pos = curr_pos; - bool found; - - found = pg_tde_read_one_map_entry(map_fd, rlocator, MAP_ENTRY_VALID, &read_map_entry, &curr_pos); - /* We've reached EOF */ - if (curr_pos == prev_pos) + if (!pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) break; - if (found) + if (map_entry.flags != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator->spcOid && map_entry.relNumber == rlocator->relNumber) { TDEMapEntry empty_map_entry = { .flags = MAP_ENTRY_EMPTY, @@ -653,22 +641,15 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p while (1) { InternalKey *rel_key_data; - off_t old_prev_pos, - new_prev_pos; + off_t new_prev_pos; TDEMapEntry read_map_entry, write_map_entry; RelFileLocator rloc; - bool found; - old_prev_pos = old_curr_pos; - found = pg_tde_read_one_map_entry(old_fd, NULL, MAP_ENTRY_VALID, &read_map_entry, &old_curr_pos); - - /* We either reach EOF */ - if (old_prev_pos == old_curr_pos) + if (!pg_tde_read_one_map_entry(old_fd, &read_map_entry, &old_curr_pos)) break; - /* We didn't find a valid entry */ - if (found == false) + if (read_map_entry.flags == MAP_ENTRY_EMPTY) continue; rloc.spcOid = read_map_entry.spcOid; @@ -916,8 +897,9 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) } /* - * Returns the index of the read map if we find a valid match; e.g. flags is set to - * MAP_ENTRY_VALID and the relNumber and spcOid matches the one provided in rlocator. + * Returns the entry of the map if we find a valid match; e.g. flags is not + * set to MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one + * provided in rlocator. */ static TDEMapEntry * pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path) @@ -926,30 +908,20 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_ TDEMapEntry *map_entry = palloc_object(TDEMapEntry); off_t curr_pos = 0; + Assert(rlocator != NULL); + map_fd = pg_tde_open_file_read(db_map_path, &curr_pos); - /* - * Read until we find an empty slot. Otherwise, read until end. This seems - * to be less frequent than vacuum. So let's keep this function here - * rather than overloading the vacuum process. - */ while (1) { - bool found; - off_t prev_pos = curr_pos; - - found = pg_tde_read_one_map_entry(map_fd, rlocator, key_type, map_entry, &curr_pos); - - /* We've reached EOF */ - if (curr_pos == prev_pos) + if (!pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { close(map_fd); pfree(map_entry); return NULL; } - /* We found a valid entry for the relation */ - if (found) + if ((map_entry->flags & key_type) && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) { close(map_fd); return map_entry; @@ -1059,26 +1031,17 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader /* - * Returns true if a valid map entry if found. Otherwise, it only increments - * the offset and returns false. If the same offset value is set, it indicates - * to the caller that nothing was read. - * - * If a non-NULL rlocator is provided, the function compares the read value - * against the relNumber of rlocator. It sets found accordingly. - * - * The caller is reponsible for identifying that we have reached EOF by - * comparing old and new value of the offset. + * Returns true if a map entry if found or false if we have reached the end of + * the file. */ static bool -pg_tde_read_one_map_entry(int map_file, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset) +pg_tde_read_one_map_entry(int map_file, TDEMapEntry *map_entry, off_t *offset) { - bool found; off_t bytes_read = 0; Assert(map_entry); Assert(offset); - /* Read the entry at the given offset */ bytes_read = pg_pread(map_file, map_entry, MAP_ENTRY_SIZE, *offset); /* We've reached the end of the file. */ @@ -1087,14 +1050,7 @@ pg_tde_read_one_map_entry(int map_file, const RelFileLocator *rlocator, int flag *offset += bytes_read; - /* We found a valid entry */ - found = map_entry->flags & flags; - - /* If a valid rlocator is provided, let's compare and set found value */ - if (rlocator != NULL) - found &= map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber; - - return found; + return true; } /* diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index da0f46691410d..3c783ec253feb 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -19,7 +19,6 @@ #define TDE_KEY_TYPE_GLOBAL 0x04 #define TDE_KEY_TYPE_WAL_UNENCRYPTED 0x08 #define TDE_KEY_TYPE_WAL_ENCRYPTED 0x10 -#define MAP_ENTRY_VALID (TDE_KEY_TYPE_SMGR | TDE_KEY_TYPE_GLOBAL) #define INTERNAL_KEY_LEN 16 #define INTERNAL_KEY_IV_LEN 16 From 6f1bb54ac5fd6f376dd20a6af901c18ba7b3a368 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 19 Apr 2025 05:33:36 +0200 Subject: [PATCH 086/796] Simplify pg_tde_find_map_entry() now that reading is simpler Now that pg_tde_read_one_map_entry() is simpler it makes sense to also simplify the pg_tde_find_map_entry() function. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 40 ++++++++++------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 5d6d3f385331c..dcd798f9c1fa0 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -109,7 +109,7 @@ static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; static InternalKey *pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type); -static TDEMapEntry *pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path); +static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); @@ -846,7 +846,7 @@ pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo static InternalKey * pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) { - TDEMapEntry *map_entry; + TDEMapEntry map_entry; TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); char db_map_path[MAXPGPATH] = {0}; @@ -862,10 +862,7 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) LWLockAcquire(lock_pk, LW_SHARED); - /* Read the map entry and get the index of the relation key */ - map_entry = pg_tde_find_map_entry(rlocator, key_type, db_map_path); - - if (map_entry == NULL) + if (!pg_tde_find_map_entry(rlocator, key_type, db_map_path, &map_entry)) { LWLockRelease(lock_pk); return NULL; @@ -889,7 +886,7 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) errmsg("principal key not configured"), errhint("create one using pg_tde_set_key before using encrypted tables")); - rel_key = tde_decrypt_rel_key(principal_key, map_entry); + rel_key = tde_decrypt_rel_key(principal_key, &map_entry); LWLockRelease(lock_pk); @@ -897,36 +894,33 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) } /* - * Returns the entry of the map if we find a valid match; e.g. flags is not - * set to MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one - * provided in rlocator. + * Returns true if we find a valid match; e.g. flags is not set to + * MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one provided in + * rlocator. */ -static TDEMapEntry * -pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path) +static bool +pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path, TDEMapEntry *map_entry) { File map_fd; - TDEMapEntry *map_entry = palloc_object(TDEMapEntry); off_t curr_pos = 0; + bool found = false; Assert(rlocator != NULL); map_fd = pg_tde_open_file_read(db_map_path, &curr_pos); - while (1) + while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { - if (!pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) - { - close(map_fd); - pfree(map_entry); - return NULL; - } - if ((map_entry->flags & key_type) && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) { - close(map_fd); - return map_entry; + found = true; + break; } } + + close(map_fd); + + return found; } bool From f3719a73b4336f547e5287bfbb16c87417c7d022 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 21:26:23 +0200 Subject: [PATCH 087/796] Use restart TAP helper and do not assert result There is a restart function so there is not and need to call first stop and then start. And since by default a start, stop or restart call does not return on error it is totally pointless to assert anything about the return value. And since PostgreSQL's own tests also are fine with just bailing out on error we do the same. While at it we also always call these three functions without parentheses to be consistent. --- contrib/pg_tde/t/001_basic.pl | 14 +++----- contrib/pg_tde/t/002_rotate_key.pl | 34 +++++-------------- contrib/pg_tde/t/003_remote_config.pl | 10 ++---- contrib/pg_tde/t/004_file_config.pl | 16 ++++----- contrib/pg_tde/t/005_multiple_extensions.pl | 22 +++++------- contrib/pg_tde/t/006_remote_vault_config.pl | 10 ++---- contrib/pg_tde/t/007_tde_heap.pl | 14 +++----- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 10 ++---- contrib/pg_tde/t/009_wal_encrypt.pl | 22 ++++-------- contrib/pg_tde/t/010_change_key_provider.pl | 28 +++++---------- contrib/pg_tde/t/011_unlogged_tables.pl | 9 ++--- 11 files changed, 59 insertions(+), 130 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index cf3b95780a195..c9619b104e9f2 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -12,9 +12,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -27,9 +25,7 @@ ); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" @@ -49,9 +45,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); @@ -73,7 +67,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 2d076333142e4..f590a81ac4be6 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -12,9 +12,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -23,9 +21,7 @@ ); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" @@ -61,9 +57,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" @@ -80,9 +74,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" @@ -99,9 +91,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" @@ -121,9 +111,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" @@ -138,9 +126,7 @@ # Things still work after a restart PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; # But now can't be changed to another global provider PGTDE::psql($node, 'postgres', @@ -169,13 +155,11 @@ 'ALTER SYSTEM RESET pg_tde.inherit_global_providers;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index b8f7978ea4848..1dc06e6f8ec13 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -55,9 +55,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start(); -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -77,9 +75,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); @@ -87,7 +83,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; system("kill $pid"); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index a06a8cf6b7868..948b4d345976d 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -9,16 +9,14 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PostgreSQL::Test::Cluster->new('main'); -$node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - open my $conf2, '>>', "/tmp/datafile-location"; print $conf2 "/tmp/keyring_data_file\n"; close $conf2; -my $rt_value = $node->start(); -ok($rt_value == 1, "Start Server"); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -38,9 +36,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); @@ -48,7 +44,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 85b5e8a1514c6..3230ab4a0171d 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -17,6 +17,10 @@ "pg_tde test case only for PPG server package install with extensions."; } +open my $conf2, '>>', "/tmp/datafile-location"; +print $conf2 "/tmp/keyring_data_file\n"; +close $conf2; + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', @@ -26,13 +30,7 @@ "pg_stat_monitor.pgsm_bucket_time = 360000"); $node->append_conf('postgresql.conf', "pg_stat_monitor.pgsm_normalized_query = 'yes'"); - -open my $conf2, '>>', "/tmp/datafile-location"; -print $conf2 "/tmp/keyring_data_file\n"; -close $conf2; - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; # Create PGSM extension my ($cmdret, $stdout, $stderr) = $node->psql( @@ -119,11 +117,11 @@ ok($cmdret == 0, "CREATE postgis_tiger_geocoder EXTENSION"); PGTDE::append_to_debug_file($stdout); -$rt_value = $node->psql( +$node->psql( 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", extra_params => ['-a']); -$rt_value = $node->psql( +$node->psql( 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", extra_params => ['-a']); @@ -147,9 +145,7 @@ PGTDE::append_to_result_file($stdout); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; $stdout = $node->safe_psql( 'postgres', @@ -210,7 +206,7 @@ ok($cmdret == 0, "DROP PGTDE EXTENSION"); PGTDE::append_to_debug_file($stdout); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index d7f274159c1c7..1012d9168dcce 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -64,9 +64,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start(); -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -86,9 +84,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); @@ -96,7 +92,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; system("kill -9 $pid"); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index bb5ec672fa8b3..09e72806daa3c 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -12,9 +12,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -23,9 +21,7 @@ ); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" @@ -103,9 +99,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; sub verify_table { @@ -186,7 +180,7 @@ sub verify_table PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 6697fe6cc1af0..9369940d9e8a9 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -12,9 +12,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', "SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION '';" @@ -54,9 +52,7 @@ ); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'tbc', 'SELECT * FROM country_table;'); @@ -65,7 +61,7 @@ PGTDE::psql($node, 'postgres', 'DROP DATABASE tbc;'); PGTDE::psql($node, 'postgres', 'DROP TABLESPACE test_tblspace;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index a02888901ce50..e4ec0b7dc888d 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -15,9 +15,7 @@ $node->append_conf('postgresql.conf', "wal_level = 'logical'"); # NOT testing that it can't start: the test framework doesn't have an easy way to do this #$node->append_conf('postgresql.conf', "pg_tde.wal_encrypt = 1"}); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); @@ -32,9 +30,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_result_file("-- server restart with wal encryption"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); @@ -50,9 +46,7 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = off;'); PGTDE::append_to_result_file("-- server restart without wal encryption"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); @@ -61,18 +55,14 @@ PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_result_file("-- server restart with wal encryption"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); PGTDE::psql($node, 'postgres', 'INSERT INTO test_wal (k) VALUES (5), (6);'); PGTDE::append_to_result_file("-- server restart with still wal encryption"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; PGTDE::psql($node, 'postgres', "SHOW pg_tde.wal_encrypt;"); @@ -86,7 +76,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 0ed471d45f715..60b40243222bf 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -10,17 +10,15 @@ PGTDE::setup_files_dir(basename($0)); -my $node = PostgreSQL::Test::Cluster->new('main'); -$node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - unlink('/tmp/change_key_provider_1.per'); unlink('/tmp/change_key_provider_2.per'); unlink('/tmp/change_key_provider_3.per'); unlink('/tmp/change_key_provider_4.per'); -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); @@ -57,9 +55,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; # Verify PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -78,9 +74,7 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; # Verify PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -92,9 +86,7 @@ move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; # Verify PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -127,9 +119,7 @@ ); PGTDE::append_to_result_file("-- server restart"); -$node->stop(); -$rt_value = $node->start(); -ok($rt_value == 1, "Restart Server"); +$node->restart; # Verify PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); @@ -150,7 +140,7 @@ PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/011_unlogged_tables.pl index 8ad9c879efd5e..cdf898c54f845 100644 --- a/contrib/pg_tde/t/011_unlogged_tables.pl +++ b/contrib/pg_tde/t/011_unlogged_tables.pl @@ -12,9 +12,7 @@ my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); - -my $rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', @@ -35,14 +33,13 @@ $node->kill9(); PGTDE::append_to_result_file("-- server start"); -$rt_value = $node->start; -ok($rt_value == 1, "Start Server"); +$node->start; PGTDE::psql($node, 'postgres', "TABLE t;"); PGTDE::psql($node, 'postgres', "INSERT INTO t SELECT generate_series(1, 4);"); -$node->stop(); +$node->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); From 0d86245ccdc594705c1f7096853fd9623e574cbe Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Apr 2025 19:08:33 +0300 Subject: [PATCH 088/796] XLog a key rotation event rather than the result Before this commit, we Xlogged the binary result of the _map file content during key rotation. This led to issues: 1. Replicas would rewrite their own WAL keys with the primary's ones. And WAL keys are different on replicas. The same would have happened with SMGR keys since we're also planning to have them different across replicas. 2. The crash recovery would rewrite the latest WAL key as it's being created before redo. This commit switches to rather Xlogging the event of rotation (to which key should rotate) and lets redo/replicas perform the actual rotation. Fixes PG-1468, PG-1541 --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 52 ++++++++++-------- .../pg_tde/src/catalog/tde_principal_key.c | 55 +++++++++++++++++-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- .../src/include/catalog/tde_principal_key.h | 4 +- 4 files changed, 83 insertions(+), 30 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index dcd798f9c1fa0..1013ad00b1c2e 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -618,7 +618,7 @@ finalize_key_rotation(const char *path_old, const char *path_new) * Rotate keys and generates the WAL record for it. */ void -pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key) +pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog) { TDESignedPrincipalKeyInfo new_signed_key_info; off_t old_curr_pos, @@ -627,8 +627,6 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p new_fd; char old_path[MAXPGPATH], new_path[MAXPGPATH]; - XLogPrincipalKeyRotate *xlrec; - off_t xlrec_size; pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); @@ -668,30 +666,38 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p } close(old_fd); - - /* Build WAL record containing the new file */ - xlrec_size = SizeoOfXLogPrincipalKeyRotate + new_curr_pos; - - xlrec = (XLogPrincipalKeyRotate *) palloc(xlrec_size); - xlrec->databaseId = principal_key->keyInfo.databaseId; - xlrec->file_size = new_curr_pos; - - if (pg_pread(new_fd, xlrec->buff, xlrec->file_size, 0) == -1) - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not write WAL for key rotation: %m")); - close(new_fd); - /* Insert the XLog record */ - XLogBeginInsert(); - XLogRegisterData((char *) xlrec, xlrec_size); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_PRINCIPAL_KEY); + /* + * Do the final steps - replace the current _map with the file with new + * data + */ + finalize_key_rotation(old_path, new_path); - pfree(xlrec); + /* + * We do WAL writes past the event ("the write behind logging") rather + * than before ("the write ahead") because we need logging here only for + * replication purposes. The rotation results in data written and fsynced + * to disk. Which in most cases would happen way before it's written to + * the WAL disk file. As WAL will be flushed at the end of the + * transaction, on its commit, hence after this function returns (there is + * also a bg writer, but the commit is what is guaranteed). And it makes + * sense to replicate the event only after its effect has been + * successfully applied to the source. + */ + if (write_xlog) + { + XLogPrincipalKeyRotate xlrec; - /* Do the final steps */ - finalize_key_rotation(old_path, new_path); + xlrec.databaseId = principal_key->keyInfo.databaseId; + xlrec.keyringId = principal_key->keyInfo.keyringId; + memcpy(xlrec.keyName, new_principal_key->keyInfo.name, sizeof(new_principal_key->keyInfo.name)); + + /* Insert the XLog record */ + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(XLogPrincipalKeyRotate)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_PRINCIPAL_KEY); + } } /* diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index a3457e7f1de8e..099a5629dbd3b 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -349,7 +349,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, else { /* key rotation */ - pg_tde_perform_rotate_key(curr_principal_key, new_principal_key); + pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, true); if (!TDEisInGlobalSpace(curr_principal_key->keyInfo.databaseId)) { @@ -370,12 +370,59 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, void xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) { + TDEPrincipalKey *curr_principal_key; + TDEPrincipalKey *new_principal_key; + GenericKeyring *new_keyring; + KeyInfo *keyInfo; + KeyringReturnCodes kr_ret; + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - pg_tde_write_map_keydata_file(xlrec->file_size, xlrec->buff); - clear_principal_key_cache(xlrec->databaseId); + curr_principal_key = GetPrincipalKeyNoDefault(xlrec->databaseId, LW_EXCLUSIVE); + + /* Should not happen */ + if (curr_principal_key == NULL) + { + ereport(ERROR, errmsg("failed to retrieve current principal key for database %u.", xlrec->databaseId)); + } + + new_keyring = GetKeyProviderByID(xlrec->keyringId, xlrec->databaseId); + keyInfo = KeyringGetKey(new_keyring, xlrec->keyName, &kr_ret); + + if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE) + { + ereport(ERROR, + errmsg("failed to retrieve principal key from keyring provider: \"%s\"", new_keyring->provider_name), + errdetail("Error code: %d", kr_ret)); + } + + /* The new key should be on keyring by this time */ + if (keyInfo == NULL) + { + ereport(ERROR, errmsg("failed to retrieve principal key from keyring for database %u.", xlrec->databaseId)); + } + + new_principal_key = palloc_object(TDEPrincipalKey); + new_principal_key->keyInfo.databaseId = xlrec->databaseId; + new_principal_key->keyInfo.keyringId = new_keyring->keyring_id; + memcpy(new_principal_key->keyInfo.name, keyInfo->name, TDE_KEY_NAME_LEN); + gettimeofday(&new_principal_key->keyInfo.creationTime, NULL); + new_principal_key->keyLength = keyInfo->data.len; + + memcpy(new_principal_key->keyData, keyInfo->data.data, keyInfo->data.len); + + pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, false); + + if (!TDEisInGlobalSpace(curr_principal_key->keyInfo.databaseId)) + { + clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); + push_principal_key_to_cache(new_principal_key); + } LWLockRelease(tde_lwlock_enc_keys()); + + pfree(new_keyring); + pfree(new_principal_key); } /* @@ -997,7 +1044,7 @@ pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey newKey->keyInfo.databaseId = oldKey->keyInfo.databaseId; /* key rotation */ - pg_tde_perform_rotate_key(oldKey, newKey); + pg_tde_perform_rotate_key(oldKey, newKey, true); if (!TDEisInGlobalSpace(newKey->keyInfo.databaseId)) { diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 3c783ec253feb..e0c40827f1173 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -119,7 +119,7 @@ extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); -extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key); +extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); extern void pg_tde_write_map_keydata_file(off_t size, char *file_data); const char *tde_sprint_key(InternalKey *k); diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index 29ea86975ca44..d7ee7806154ef 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -37,8 +37,8 @@ typedef struct TDEPrincipalKey typedef struct XLogPrincipalKeyRotate { Oid databaseId; - off_t file_size; - char buff[FLEXIBLE_ARRAY_MEMBER]; + Oid keyringId; + char keyName[PRINCIPAL_KEY_NAME_LEN]; } XLogPrincipalKeyRotate; #define SizeoOfXLogPrincipalKeyRotate offsetof(XLogPrincipalKeyRotate, buff) From ecabb8b9c27fcfb5df11e4b71aaf9f91da270e3b Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 17 Apr 2025 16:46:57 +0200 Subject: [PATCH 089/796] Do missing renamings in documentation --- .../pg_tde/documentation/docs/architecture.md | 61 ++++++++++--------- .../documentation/docs/command-line-tools.md | 4 +- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 573b3023c006b..57b0240168a65 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -199,30 +199,30 @@ If `pg_tde.inherit_global_providers` is `OFF`, global providers are only used fo To add a global provider: ```sql -pg_tde_add_global_key_provider_(‘provider_name', ... details ...) +pg_tde_add_global_key_provider_('provider_name', ... details ...) ``` To add a database specific provider: ```sql -pg_tde_add_key_provider_(‘provider_name', ... details ...) +pg_tde_add_database_key_provider_('provider_name', ... details ...) ``` Note that in these functions do not verify the parameters. -For that, see `pg_tde_verify_principal_key`. +For that, see `pg_tde_verify_key`. ### Changing providers To change a value of a global provider: ```sql -pg_tde_modify_global_key_provider_(‘provider_name', ... details ...) +pg_tde_change_global_key_provider_('provider_name', ... details ...) ``` To change a value of a database specific provider: ```sql -pg_tde_modify_key_provider_(‘provider_name', ... details ...) +pg_tde_change_database_key_provider_('provider_name', ... details ...) ``` These functions also allow changing the type of a provider. @@ -231,16 +231,16 @@ The functions however do not migrate any data. They are expected to be used during infrastructure migration, for example when the address of a server changes. Note that in these functions do not verify the parameters. -For that, see `pg_tde_verify_principal_key`. +For that, see `pg_tde_verify_key`. ### Changing providers from the command line -To change a provider from a command line, `pg_tde` provides the `pg_tde_modify_key_provider` command line tool. +To change a provider from a command line, `pg_tde` provides the `pg_tde_change_key_provider` command line tool. This tool work similarly to the above functions, with the following syntax: ```bash -pg_tde_modify_key_provider ... details ... +pg_tde_change_key_provider ... details ... ``` Note that since this tool is expected to be offline, it bypasses all permission checks! @@ -252,8 +252,8 @@ This is also the reason why it requires a `dbOid` instead of a name, as it has n Providers can be deleted by the ```sql -pg_tde_delete_key_provider(provider_name) -pg_tde_delete_global_key_provider(provider_name) +pg_tde_delete_database_key_provider(provider_name) +pg_tde_delete_global_key_provider(provider_name) ``` functions. @@ -269,7 +269,7 @@ Making this check makes more sense than potentially making some databases inacce `Pg_tde` provides 2 functions to show providers: -* `pg_tde_list_all_key_providers()` +* `pg_tde_list_all_database_key_providers()` * `pg_tde_list_all_global_key_providers()` These functions only return a list of provider names, without any details about the type/configuration. @@ -277,8 +277,8 @@ These functions only return a list of provider names, without any details about There's also two function to query the details of providers: ```sql -pg_tde_show_key_provider_configuration(‘provider-name') -pg_tde_show_global_key_provider_configuration(‘provider-name') +pg_tde_show_key_provider_configuration('provider-name') +pg_tde_show_global_key_provider_configuration('provider-name') ``` These functions display the provider type and configuration details, but won't show the sensitive parameters, such as passwords or authentication keys. @@ -287,11 +287,11 @@ These functions display the provider type and configuration details, but won't s `Pg_tde` implements access control based on execution rights on the administration functions. -For provider administration, it provides two pair of functions: +For keys and providers administration, it provides two pair of functions: ```sql -pg_tde_(grant/revoke)_local_provider_management_to_role -pg_tde_(grant/revoke)_global_provider_management_to_role +pg_tde_(grant/revoke)_database_key_management_to_role +pg_tde_(grant/revoke)_global_key_management_to_role ``` There's one special behavior: @@ -303,9 +303,9 @@ When `pg_tde.inherit_global_providers` is OFF, they can't execute the function a Principal keys can be created or rotated using the following functions: ```sql -pg_tde_set_principal_key(‘key-name', ‘provider-name', ensure_new_key) -pg_tde_set_global_principal_key(‘key-name', ‘provider-name', ensure_new_key) -pg_tde_set_server_principal_key(‘key-name', ‘provider-name', ensure_new_key) +pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) ``` `ensure_new_key` is a boolean parameter defaulting to false. @@ -313,15 +313,15 @@ If it is true, the function might return an error instead of setting the key, if ### Default principal key -With `pg_tde.inherit_global_key_providers`, it is also possible to set up a default global principal key, which will be used by any database which has the `pg_tde` extension enabled, but doesn't have a database specific principal key configured using `pg_tde_set_(global_)principal_key`. +With `pg_tde.inherit_global_key_providers`, it is also possible to set up a default global principal key, which will be used by any database which has the `pg_tde` extension enabled, but doesn't have a database specific principal key configured using `pg_tde_set_key_using_(global/database)_key_provider`. With this feature, it is possible for the entire database server to easily use the same principal key for all databases, completely disabling multi-tenency. A default key can be managed with the following functions: ```sql -pg_tde_set_default_principal_key(‘key-name', ‘provider-name', ensure_new_key) -pg_tde_drop_default_principal_key() -- not yet implemented +pg_tde_set_default_key('key-name', 'provider-name', ensure_new_key) +pg_tde_drop_default_key() -- not yet implemented ``` `DROP` is only possible if there's no table currently using the default principal key. @@ -330,7 +330,7 @@ Changing the default principal key will rotate the encryption of internal keys f ### Removing key (not yet implemented) -`pg_tde_drop_principal_key` removes the principal key for the current database. +`pg_tde_drop_key` removes the principal key for the current database. If the current database has any encrypted tables, and there isn't a default principal key configured, it reports an error instead. If there are encrypted tables, but there's also a global default principal key, internal keys will be encrypted with the default key. @@ -338,11 +338,13 @@ It isn't possible to remove the WAL (server) principal key. ### Current key details -`pg_tde_principal_key_info()` returns the name of the current principal key, and the provider it uses. +`pg_tde_key_info()` returns the name of the current principal key, and the provider it uses. -`pg_tde_global_principal_key_info(‘PG_TDE_GLOBAL')` does the same for the server key. +`pg_tde_server_key_info()` does the same for the server key. -`pg_tde_verify_principal_key()` checks that the key provider is accessible, that the current principal key can be downloaded from it, and that it is the same as the current key stored in memory - if any of these fail, it reports an appropriate error. +`pg_tde_default_key_info()` does the same for the default key. + +`pg_tde_verify_key()` checks that the key provider is accessible, that the current principal key can be downloaded from it, and that it is the same as the current key stored in memory - if any of these fail, it reports an appropriate error. ### Listing all active keys (not yet implemented) @@ -351,13 +353,14 @@ SUPERusers are able to use the following function: `pg_tde_list_active_keys()` Which reports all the actively used keys by all databases on the current server. -Similarly to `pg_tde_show_current_principal_key`, it only shows names and associated providers, it doesn't reveal any sensitive information about the providers. +Similarly to `pg_tde_key_info()`, it only shows names and associated providers, it doesn't reveal any sensitive information about the providers. ### Key permissions -Users with management permissions to a specific database `(pg_tde_(grant/revoke)_provider_management_to_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. +Users with management permissions to a specific database `(pg_tde_(grant/revoke)_(global/databse)_key_management_to_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. -Also, the `pg_tde_(grant/revoke)_key_management_to_role` function deals with only the specific permission for the above function: +// TODO: We don't have such permissions subset +Also, the `pg_tde_(grant/revoke)_(global/database)_key_management_to_role` function deals with only the specific permission for the above function: it allows a user to change the key for the database, but not to modify the provider configuration. ### Creating encrypted tables diff --git a/contrib/pg_tde/documentation/docs/command-line-tools.md b/contrib/pg_tde/documentation/docs/command-line-tools.md index e5b2763529bd4..a737505c4861c 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools.md @@ -12,11 +12,11 @@ Its only intended use is to fix servers that can't start up because of inaccessi For example, you restore from an old backup and the address of the key provider changed in the meantime. You can use this tool to correct the configuration, allowing the server to start up. -Use this tool **only when the server is offline.** To modify the key provider configuration when the server is up and running, use the [`pg_tde_change_key_provider_`](functions.md#change-an-existing-provider) SQL functions. +Use this tool **only when the server is offline.** To modify the key provider configuration when the server is up and running, use the [`pg_tde_change_(global/database)_key_provider_`](functions.md#change-an-existing-provider) SQL functions. ### Usage -To modify the key provider configuration, specify all parameters depending on the provider type in the same way as you do when using the [`pg_tde_change_key_provider_`](functions.md#change-an-existing-provider) SQL functions. +To modify the key provider configuration, specify all parameters depending on the provider type in the same way as you do when using the [`pg_tde_change_(global/database)_key_provider_`](functions.md#change-an-existing-provider) SQL functions. The general syntax is as follows: From 3095a6afe036ec15bab90d5a6de11f964834e999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 22 Apr 2025 11:44:19 +0200 Subject: [PATCH 090/796] Add 71da1f03f2f0ff18ed11e4ba6b07b6bd56705a5d to .git-blame-ignore-revs --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 3538e0cdc757c..7e1c817416c9c 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -14,6 +14,9 @@ # # $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso +71da1f03f2f0ff18ed11e4ba6b07b6bd56705a5d # 2025-04-22 11:40:24 +0200 +# Run pgperltidy + c739ae9e288c095cfe1b91ce27a2f2c075ed5da4 # 2024-08-26 16:16:09 -0700 # Fix identation. From 2b417dad54b659035369346591979c9e36e89cfa Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Apr 2025 19:35:12 +0200 Subject: [PATCH 091/796] PG-1366 Clean up architecture documentation and make it up to date The architecture documentation was outdated so this makes it up to date plus improves various minor issues found while updating the documentation. --- .../pg_tde/documentation/docs/architecture.md | 147 +++++++----------- 1 file changed, 59 insertions(+), 88 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 57b0240168a65..a216d1a7c9a92 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -9,7 +9,7 @@ Let's break down what it means. * Encrypting either every table in every database or only some tables in some databases * Encryption keys can be stored on various external key storage servers including Hashicorp Vault, KMIP servers. * Using one key for everything or different keys for different databases -* Storing every key at the same key storage, or using different storages for different databases +* Storing every key on the same key storage, or using different storages for different databases * Handling permissions: who can manage database specific or global permissions, who can create encrypted or not encrypted tables **Complete** means that `pg_tde` aims to encrypt data at rest. @@ -18,6 +18,7 @@ Let's break down what it means. * Table data files * Indexes +* Seqeunces * Temporary tables * Write Ahead Log (WAL) * System tables (not yet implemented) @@ -44,35 +45,30 @@ In the future these could be extracted into separate shared libraries with an op ### Two-key hierarchy -`pg_tde` uses two keys for encryption: +`pg_tde` uses two kinds of keys for encryption: -* Internal keys to encrypt the data. They are stored internally near the data that they encrypt. -* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in the Key Management System (KMS) using the key provider API. +* Internal keys to encrypt the data. They are stored in PostgreSQL's data directory under `$PGDATA/pg_tde``. +* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in a Key Management System (KMS) using the key provider API. `pg_tde` uses one principal key per database. Every internal key for the given database is encrypted using this principal key. -Internal keys are used for specific database files: each file with a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) and has a different internal key. +Internal keys are used for specific database files: each file with a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) has a different internal key. This means that, for example, a table with 4 indexes will have at least 5 internal keys - one for the table, and one for each index. -If a table has additional files, such as sequence(s) or a TOAST table, those files will also have separate keys. +If a table has additional associated relations, such as sequences or a TOAST table, those relations will also have separate keys. ### Encryption algorithm `pg_tde` currently uses the following encryption algorithms: -* `AES-CBC-128` algorithm for encrypting most database files. +* `AES-128-CBC` for encrypting database files; encrypted with internal keys. - Here's how it works: +* `AES-128-CTR` for WAL encryption; encrypted with internal keys. - First the internal keys for data files are encrypted using the principal key with the `AES-CBC-128` algorithm. Then the data file itself is again encrypted using `AES-CBC-128` with the internal key. +* `AES-128-GCM` for encrypting internal keys; encrypted with the principal key. -* `AES-CTR-128` algorithm for WAL encryption. - - The workflow is similar: WAL pages are first encrypted with the internal key. Then the internal key is encrypted with the global principal key. - - -The support for other cipher lengths / algorithms is planned in the future. +Support for other cipher lengths / algorithms is planned in the future. ### Encryption workflow @@ -84,18 +80,19 @@ The `tde_heap` access method is the same as the `heap` one. It uses the same fun The initial decision what to encrypt is made using the `postgres` event trigger mechanism: if a `CREATE TABLE` or `ALTER TABLE` statement uses the `tde_heap` clause, the newly created data files are marked as encrypted. Then file operations encrypt or decrypt the data. -Later decisions are made using a slightly modified Storage Manager (SMGR) API: -when a database file is re-created with a different ID as a result of a `TRUNCATE` or a `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. +Later decisions are made using a slightly modified Storage Manager (SMGR) API: when a database file is re-created with a different ID as a result of a `TRUNCATE` or a `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. ### WAL encryption -WAL encryption is controlled globally via a global GUC variable that requires a server restart. +WAL encryption is controlled globally via a global GUC variable, `pg_tde.wal_encrypt`, that requires a server restart. + +WAL keys also contain the [LSN](https://www.postgresql.org/docs/17/wal-internals.html) of the first WAL write after key creation. This allows `pg_tde` to know which WAL ranges are encrypted or not and with which key. -The variable only controls writes so that only WAL writes are encrypted when WAL encryption is enabled. This means that WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. +The setting only controls writes so that only WAL writes are encrypted when WAL encryption is enabled. This means that WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. -`pg_tde` keeps track of the encryption status of WAL records using internal keys. Every time the encryption status of WAL changes, it writes a new internal key for WAL. When the encryption is enabled, this internal key contains a valid encryption key. When the encrpytion is disabled, it only contains a flag signaling that WAL encryption ended. +`pg_tde` keeps track of the encryption status of WAL records using internal keys. When the server is restarted it writes a new internal key if WAL encryption is enabled, or if it is disabled and was previously enabled it writes a dummy key signalling that WAL encryption ended. -With this information, the WAL reader code can decide if a specific WAL record has to be decrypted or not. +With this information the WAL reader code can decide if a specific WAL record has to be decrypted or not and which key it should use to decrypt it. ### Encrypting other access methods @@ -107,31 +104,28 @@ In theory, it is also possible to encrypt any other table access method that goe ### Storage Manager (SMGR) API -`pg_tde` relies on a slightly modified version of the SMGR API. -These modifications include: +`pg_tde` relies on a slightly modified version of the SMGR API. These modifications include: * Making the API generally extensible, where extensions can inject custom code into the storage manager * Adding tracking information for files. When a new file is created for an existing relation, references to the existing file are also passed to the SMGR functions With these modifications, the `pg_tde` extension can implement an additional layer on top of the normal Magnetic Disk SMGR API: if the related table is encrypted, `pg_tde` encrypts a file before writing it to the disk and, similarly, decrypts it after reading when needed. -## Key and key providers management +## Key and key provider management ### Principal key rotation You can rotate principal keys to comply with common policies and to handle situations with potentially exposed principal keys. -Rotation means that `pg_tde` generates a new version of the principal key, and re-encrypts the associated internal keys with the new key. The old key is kept as is at the same location, because it may still be needed to decrypt backups or other databases. +Rotation means that `pg_tde` generates a new version of the principal key, and re-encrypts the associated internal keys with the new key. The old principal key is kept as is at the same location, because it may still be needed to decrypt backups or other databases. ### Internal key regeneration Internal keys for tables, indexes and other data files are fixed once a file is created. There's no way to re-encrypt a file. -WAL internal keys are also fixed to the respective ranges. And there is no easy way to generate a new internal WAL key without turning off and on WAL encryption. +There are workarounds for this, because operations that move the table data to a new file, such as `VACUUM FULL` or an `ALTER TABLE` that rewrites the file will create a new key for the new file, essentially rotating the internal key. This however means taking an exclusive lock on the table for the duration of the operation, which might not be desirable for huge tables. -There are workarounds for this, because operations that move the table data to a new file, such as `VACUUM FULL` or an `ALTER TABLE` that rewrites the file will create a new key for the new file, essentially rotating the internal key. -This however means taking an exclusive lock on the table for the duration of the operation, which might not be desirable for huge tables. -WAL internal key are also fixed to the respective ranges. And there is no easy way to generate a new WAL key without turning off and on WAL encryption. +WAL internal keys are also fixed to the respective ranges. To generate a new WAL key you need to restart the database. ### Internal key storage @@ -140,13 +134,15 @@ Internal keys and `pg_tde` metadata in general are kept in a single `$PGDATA/pg_ * Encrypted internal keys and internal key mapping to tables * Information about the key providers -Also, the `$PGDATA/pg_tde` directory has a special global section marked with the OID `607`, which includes the global key providers and global internal keys. +Also, the `$PGDATA/pg_tde` directory has a special global section marked with the OID `1664`, which includes the global key providers and global internal keys. -The global section is used for WAL encryption. Specific databases can use the global section too, for scenarios where users configure individual principal keys for databases but use the same global key provider. For this purpose, you must enable the global provider inheritance. +The global section is used for WAL encryption. Specific databases can use the global section too, for scenarios where users configure individual principal keys for databases but use the same global key provider. For this purpose, you must enable the global provider inheritance. + +The global default principal key uses the special OID `1663`. ### Key providers (principal key storage) -Principal keys are stored externally in Key Management Stores (KMS). In `pg_tde`a KMS is defined as an external key provider. +Principal keys are stored externally in a Key Management Services (KMS). In `pg_tde`a KMS is defined as an external key provider. The following key providers are supported: @@ -155,12 +151,14 @@ The following key providers are supported: * KMIP compatible servers * A local file storage. This storage is intended only for development and testing and is not recommended for production use. -For each key provider, `pg_tde` requires a detailed configuration, including the address of the service and the authentication information. +For each key provider `pg_tde` requires a detailed configuration including the address of the service and the authentication information. + +With these details `pg_tde` does the following based on user operations: -With these details, `pg_tde` does the following based on user operations: +* Uploads a new principal key to it after this key is created +* Retrieves the principal key from the service when it is required for decryption -* Communicates with the service and uploads a new principal key to it after this key is created -* Retrieves the principal key from the service when it is required for decryption +Retreival of the principal key is cached so it only happens when necessary. ### Key provider management @@ -176,25 +174,25 @@ Key provider information authentication details is a sensitive information. It i To safeguard key providers' sensitive information, `pg_tde` supports references to external services. Instead of specifying authentication details directly, users specify the reference to the external service where it is stored. `pg_tde` then downloads the provider's authentication details when needed. -The supported external services are HTTP and external file references. Upon request, other services such as Kubernetes secrets can be added. +The currently supported external services are HTTP and external file references. ## User interface -### Setting up pg_tde +### Setting up `pg_tde` To use `pg_tde`, users are required to: -* Add pg_tde to the `shared_preload_libraries` in `postgresql.conf`, as this is required for the SMGR extensions +* Add `pg_tde` to the `shared_preload_libraries` in `postgresql.conf` as this is required for the SMGR extensions * Execute `CREATE EXTENSION pg_tde` in the databases where they want to use encryption * Optionally, enable `pg_tde.wal_encrypt` in `postgresql.conf` * Optionally, disable `pg_tde.inherit_global_providers` in `postgresql.conf` (enabled by default) ### Adding providers -Keyring providers can be added to either the GLOBAL or to the database specific scope. +Keyring providers can be added to either the global or to the database specific scope. -If `pg_tde.inherit_global_providers` is `ON`, global providers are visible for all databases, and can be used. -If `pg_tde.inherit_global_providers` is `OFF`, global providers are only used for WAL encryption. +If `pg_tde.inherit_global_providers` is `on`, global providers are visible for all databases, and can be used. +If `pg_tde.inherit_global_providers` is `off`, global providers are only used for WAL encryption. To add a global provider: @@ -208,8 +206,7 @@ To add a database specific provider: pg_tde_add_database_key_provider_('provider_name', ... details ...) ``` -Note that in these functions do not verify the parameters. -For that, see `pg_tde_verify_key`. +Note that in these functions do not verify the parameters. For that, see `pg_tde_verify_key`. ### Changing providers @@ -227,11 +224,9 @@ pg_tde_change_database_key_provider_('provider_name', ... details ...) These functions also allow changing the type of a provider. -The functions however do not migrate any data. -They are expected to be used during infrastructure migration, for example when the address of a server changes. +The functions however do not migrate any data. They are expected to be used during infrastructure migration, for example when the address of a server changes. -Note that in these functions do not verify the parameters. -For that, see `pg_tde_verify_key`. +Note that in these functions do not verify the parameters. For that, see `pg_tde_verify_key`. ### Changing providers from the command line @@ -262,41 +257,28 @@ For database specific providers, the function first checks if the provider is us For global providers, the function checks if the provider is used anywhere, WAL or any specific database, and returns an error if it is. -This somewhat goes against the principle that `pg_tde` shouldn't interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. -Making this check makes more sense than potentially making some databases inaccessible. +This somewhat goes against the principle that `pg_tde` shouldn't interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. Making this check makes more sense than potentially making some databases inaccessible. ### Listing/querying providers -`Pg_tde` provides 2 functions to show providers: +`pg_tde` provides 2 functions to show providers: * `pg_tde_list_all_database_key_providers()` * `pg_tde_list_all_global_key_providers()` -These functions only return a list of provider names, without any details about the type/configuration. - -There's also two function to query the details of providers: - -```sql -pg_tde_show_key_provider_configuration('provider-name') -pg_tde_show_global_key_provider_configuration('provider-name') -``` - -These functions display the provider type and configuration details, but won't show the sensitive parameters, such as passwords or authentication keys. +These functions return a list of provider names, type and configuration. ### Provider permissions -`Pg_tde` implements access control based on execution rights on the administration functions. +`pg_tde` implements access control based on execution rights on the administration functions. For keys and providers administration, it provides two pair of functions: ```sql pg_tde_(grant/revoke)_database_key_management_to_role -pg_tde_(grant/revoke)_global_key_management_to_role ``` -There's one special behavior: -When `pg_tde.inherit_global_providers` is ON, users with database local permissions can list global providers, but can't use the show function to query configuration details. -When `pg_tde.inherit_global_providers` is OFF, they can't execute the function at all, it will return an error. +There's one special behavior: When `pg_tde.inherit_global_providers` is `on`, users with database local permissions can list global providers, but can't use the show function to query configuration details. When `pg_tde.inherit_global_providers` is `off`, they can't execute the function at all, it will return an error. ### Creating and rotating keys @@ -308,8 +290,7 @@ pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) ``` -`ensure_new_key` is a boolean parameter defaulting to false. -If it is true, the function might return an error instead of setting the key, if it already exists on the provider. +`ensure_new_key` is a boolean parameter defaulting to false. If it is `true` the function might return an error instead of setting the key if it already exists on the provider. ### Default principal key @@ -330,9 +311,7 @@ Changing the default principal key will rotate the encryption of internal keys f ### Removing key (not yet implemented) -`pg_tde_drop_key` removes the principal key for the current database. -If the current database has any encrypted tables, and there isn't a default principal key configured, it reports an error instead. -If there are encrypted tables, but there's also a global default principal key, internal keys will be encrypted with the default key. +`pg_tde_drop_key` removes the principal key for the current database. If the current database has any encrypted tables, and there isn't a default principal key configured, it reports an error instead. If there are encrypted tables, but there's also a global default principal key, internal keys will be encrypted with the default key. It isn't possible to remove the WAL (server) principal key. @@ -352,30 +331,23 @@ SUPERusers are able to use the following function: `pg_tde_list_active_keys()` -Which reports all the actively used keys by all databases on the current server. -Similarly to `pg_tde_key_info()`, it only shows names and associated providers, it doesn't reveal any sensitive information about the providers. +Which reports all the actively used keys by all databases on the current server. Similarly to `pg_tde_key_info()`, it only shows names and associated providers, it doesn't reveal any sensitive information about the providers. ### Key permissions Users with management permissions to a specific database `(pg_tde_(grant/revoke)_(global/databse)_key_management_to_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. -// TODO: We don't have such permissions subset -Also, the `pg_tde_(grant/revoke)_(global/database)_key_management_to_role` function deals with only the specific permission for the above function: -it allows a user to change the key for the database, but not to modify the provider configuration. +Also the `pg_tde_(grant/revoke)_database_key_management_to_role` function deals with only the specific permission for the above function: it allows a user to change the key for the database, but not to modify the provider configuration. ### Creating encrypted tables -To create an encrypted table or modify an existing table to be encrypted, simply use `USING tde_heap` in the `CREATE` / `ALTER TABLE` statement. - -### Changing the pg_tde.inherit_global_keys setting - -It is possible for users to use pg_tde with `inherit_global_keys=ON`, refer to global keys / keyrings in databases, and then change this setting to OFF. +To create an encrypted table or modify an existing table to be encrypted, simply use `USING tde_heap` in the `CREATE` statement. -In this case, existing references to global providers, or the global default principal key will remain working as before, but new references to the global scope can't be made. +### Changing the `pg_tde.inherit_global_keys` setting -### Using command line tools with encrypted WAL +It is possible for users to use `pg_tde` with `inherit_global_keys = on`, refer to global keys / keyrings in databases, and then change this setting to `off`. -TODO +In this case existing references to global providers, or the global default principal key will remain working as before, but new references to the global scope can't be made. ## Typical setup scenarios @@ -402,16 +374,15 @@ encryption is managed by the admins, normal users only have to create tables wit Note: setting the `default_table_access_method` to `tde_heap` is possible, but instead of `ALTER SYSTEM` only per database using `ALTER DATABASE`, after a principal key is configured for that specific database. -Alternatively, `ALTER SYSTEM` is possible, but table creation in the database will fail if there's no principal key for the database, that has to be created first. +Alternatively `ALTER SYSTEM` is possible, but table creation in the database will fail if there's no principal key for the database, that has to be created first. ### Complete multi tenancy -1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` (that's not multi tenant currently) +1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` (that's not multi tenant currently) 2. `CREATE EXTENSION pg_tde;` in any database 2. Adding a global key provider for WAL 3. Changing the WAL encryption to use the proper global key provider -No default configuration: -key providers / principal keys are configured as a per database level, permissions are managed per database +No default configuration: key providers / principal keys are configured as a per database level, permissions are managed per database Same note about `default_table_access_method` as above - but in a multi tenant setup, `ALTER SYSTEM` doesn't make much sense. From 5ae34a248afad668dc47c0bbfd91e30b19ca76f6 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 16:51:20 +0200 Subject: [PATCH 092/796] PG-1366 Remove too detailed and incorrect comment from achitecture docs The fucntions for lsiting keys do not look at inherit_global_providers but even if they did it does not seem like something which would belong in the architecture documentation. --- contrib/pg_tde/documentation/docs/architecture.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index a216d1a7c9a92..1131eda0166fc 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -278,8 +278,6 @@ For keys and providers administration, it provides two pair of functions: pg_tde_(grant/revoke)_database_key_management_to_role ``` -There's one special behavior: When `pg_tde.inherit_global_providers` is `on`, users with database local permissions can list global providers, but can't use the show function to query configuration details. When `pg_tde.inherit_global_providers` is `off`, they can't execute the function at all, it will return an error. - ### Creating and rotating keys Principal keys can be created or rotated using the following functions: From 86a43fc484a2d34c6ffb2e0be6b753aa41e93642 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Tue, 22 Apr 2025 17:56:18 +0300 Subject: [PATCH 093/796] Update a small fix to sequences Fixed sequences from seqeunces --- contrib/pg_tde/documentation/docs/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 1131eda0166fc..3be3f4f78ab3f 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -18,7 +18,7 @@ Let's break down what it means. * Table data files * Indexes -* Seqeunces +* Sequences * Temporary tables * Write Ahead Log (WAL) * System tables (not yet implemented) From 0cc1d566819acec6feac82e3b136e4d4adeace15 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 18:17:10 +0200 Subject: [PATCH 094/796] Fix whitespace in pg_tde tools Makefile --- contrib/pg_tde/Makefile.tools | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/Makefile.tools b/contrib/pg_tde/Makefile.tools index 0d28b8292fd20..45a38138108c7 100644 --- a/contrib/pg_tde/Makefile.tools +++ b/contrib/pg_tde/Makefile.tools @@ -1,18 +1,18 @@ TDE_XLOG_OBJS = \ - src/access/pg_tde_xlog_encrypt.frontend + src/access/pg_tde_xlog_encrypt.frontend TDE_OBJS = \ - src/access/pg_tde_tdemap.frontend \ - src/catalog/tde_keyring.frontend \ - src/catalog/tde_keyring_parse_opts.frontend \ - src/catalog/tde_principal_key.frontend \ - src/common/pg_tde_utils.frontend \ - src/encryption/enc_aes.frontend \ - src/encryption/enc_tde.frontend \ - src/keyring/keyring_api.frontend \ - src/keyring/keyring_curl.frontend \ - src/keyring/keyring_file.frontend \ - src/keyring/keyring_vault.frontend \ + src/access/pg_tde_tdemap.frontend \ + src/catalog/tde_keyring.frontend \ + src/catalog/tde_keyring_parse_opts.frontend \ + src/catalog/tde_principal_key.frontend \ + src/common/pg_tde_utils.frontend \ + src/encryption/enc_aes.frontend \ + src/encryption/enc_tde.frontend \ + src/keyring/keyring_api.frontend \ + src/keyring/keyring_curl.frontend \ + src/keyring/keyring_file.frontend \ + src/keyring/keyring_vault.frontend \ src/libkmip/libkmip/src/kmip.frontend \ src/libkmip/libkmip/src/kmip_bio.frontend \ src/libkmip/libkmip/src/kmip_locate.frontend \ @@ -20,7 +20,6 @@ TDE_OBJS = \ src/keyring/keyring_kmip.frontend \ src/keyring/keyring_kmip_impl.frontend - TDE_OBJS2 = $(TDE_OBJS:%=$(top_srcdir)/contrib/pg_tde/%) TDE_XLOG_OBJS2 = $(TDE_XLOG_OBJS:%=$(top_srcdir)/contrib/pg_tde/%) @@ -35,5 +34,4 @@ libtdexlog.a: $(TDE_XLOG_OBJS2) rm -f $@ $(AR) $(AROPT) $@ $^ - -tdelibs: libtde.a libtdexlog.a \ No newline at end of file +tdelibs: libtde.a libtdexlog.a From b2fb3ffb45246f74aee5553a75568bb0054eec45 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 18:17:38 +0200 Subject: [PATCH 095/796] Rename pg_tde_xlog_encrypt.{c,h} to pg_tde_xlog_smgr.{c,h} The new name makes it clearer what the files does plus matches our naming conventions with the relation data storage manager. --- contrib/pg_tde/Makefile | 2 +- contrib/pg_tde/Makefile.tools | 2 +- contrib/pg_tde/meson.build | 4 ++-- .../access/{pg_tde_xlog_encrypt.c => pg_tde_xlog_smgr.c} | 6 +++--- .../access/{pg_tde_xlog_encrypt.h => pg_tde_xlog_smgr.h} | 8 ++++---- contrib/pg_tde/src/pg_tde.c | 2 +- src/bin/pg_waldump/pg_waldump.c | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) rename contrib/pg_tde/src/access/{pg_tde_xlog_encrypt.c => pg_tde_xlog_smgr.c} (99%) rename contrib/pg_tde/src/include/access/{pg_tde_xlog_encrypt.h => pg_tde_xlog_smgr.h} (73%) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 2c3567cbd6e63..53c1e57812fc9 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -31,7 +31,7 @@ OBJS = src/encryption/enc_tde.o \ src/encryption/enc_aes.o \ src/access/pg_tde_tdemap.o \ src/access/pg_tde_xlog.o \ -src/access/pg_tde_xlog_encrypt.o \ +src/access/pg_tde_xlog_smgr.o \ src/keyring/keyring_curl.o \ src/keyring/keyring_file.o \ src/keyring/keyring_vault.o \ diff --git a/contrib/pg_tde/Makefile.tools b/contrib/pg_tde/Makefile.tools index 45a38138108c7..4832c74c9db68 100644 --- a/contrib/pg_tde/Makefile.tools +++ b/contrib/pg_tde/Makefile.tools @@ -1,5 +1,5 @@ TDE_XLOG_OBJS = \ - src/access/pg_tde_xlog_encrypt.frontend + src/access/pg_tde_xlog_smgr.frontend TDE_OBJS = \ src/access/pg_tde_tdemap.frontend \ diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 9f28aef15af42..4e72b8d54b352 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -3,7 +3,7 @@ curldep = dependency('libcurl') pg_tde_sources = files( 'src/access/pg_tde_tdemap.c', 'src/access/pg_tde_xlog.c', - 'src/access/pg_tde_xlog_encrypt.c', + 'src/access/pg_tde_xlog_smgr.c', 'src/catalog/tde_keyring.c', 'src/catalog/tde_keyring_parse_opts.c', 'src/catalog/tde_principal_key.c', @@ -25,7 +25,7 @@ pg_tde_sources = files( tde_frontend_sources = files( 'src/access/pg_tde_tdemap.c', - 'src/access/pg_tde_xlog_encrypt.c', + 'src/access/pg_tde_xlog_smgr.c', 'src/catalog/tde_keyring.c', 'src/catalog/tde_keyring_parse_opts.c', 'src/catalog/tde_principal_key.c', diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c similarity index 99% rename from contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c rename to contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 706f8a442d8e7..f6b0a69cf0a51 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_encrypt.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -1,11 +1,11 @@ /*------------------------------------------------------------------------- * - * pg_tde_xlog_encrypt.c + * pg_tde_xlog_smgr.c * Encrypted XLog storage manager * * * IDENTIFICATION - * src/access/pg_tde_xlog_encrypt.c + * src/access/pg_tde_xlog_smgr.c * *------------------------------------------------------------------------- */ @@ -25,7 +25,7 @@ #include "utils/memutils.h" #include "access/pg_tde_tdemap.h" -#include "access/pg_tde_xlog_encrypt.h" +#include "access/pg_tde_xlog_smgr.h" #include "catalog/tde_global_space.h" #include "encryption/enc_tde.h" diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_encrypt.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h similarity index 73% rename from contrib/pg_tde/src/include/access/pg_tde_xlog_encrypt.h rename to contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index c501bb637253d..ce8df4b8769e8 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_encrypt.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -1,13 +1,13 @@ /*------------------------------------------------------------------------- * - * pg_tde_xlog_encrypt.h + * pg_tde_xlog_smgr.h * Encrypted XLog storage manager * *------------------------------------------------------------------------- */ -#ifndef PG_TDE_XLOGENCRYPT_H -#define PG_TDE_XLOGENCRYPT_H +#ifndef PG_TDE_XLOGSMGR_H +#define PG_TDE_XLOGSMGR_H #include "postgres.h" @@ -15,4 +15,4 @@ extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); -#endif /* PG_TDE_XLOGENCRYPT_H */ +#endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 77ee55609a8c9..8576c41c7d923 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -18,7 +18,7 @@ #include "storage/lwlock.h" #include "storage/shmem.h" #include "access/pg_tde_xlog.h" -#include "access/pg_tde_xlog_encrypt.h" +#include "access/pg_tde_xlog_smgr.h" #include "encryption/enc_aes.h" #include "access/pg_tde_tdemap.h" #include "access/xlog.h" diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index 0ee07d33f4fa3..171d6a52955fa 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -34,7 +34,7 @@ #ifdef PERCONA_EXT #include "access/pg_tde_fe_init.h" -#include "access/pg_tde_xlog_encrypt.h" +#include "access/pg_tde_xlog_smgr.h" #include "access/xlog_smgr.h" #include "catalog/tde_global_space.h" #endif From 332064b8bb167389720d7687b315565f9743e85a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:18:12 +0200 Subject: [PATCH 096/796] Remove useless comments in pg_tde_tdemap.c --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 1013ad00b1c2e..8ab58500b5031 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -255,7 +255,6 @@ pg_tde_delete_tde_files(Oid dbOid) { char db_map_path[MAXPGPATH] = {0}; - /* Set the file paths */ pg_tde_set_db_file_path(dbOid, db_map_path); /* Remove file without emitting any error */ @@ -295,7 +294,6 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) char db_map_path[MAXPGPATH] = {0}; TDESignedPrincipalKeyInfo signed_key_Info; - /* Set the file paths */ pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); ereport(DEBUG2, errmsg("pg_tde_save_principal_key")); @@ -430,7 +428,6 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ Assert(rlocator); - /* Set the file paths */ pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); @@ -478,7 +475,6 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ curr_pos = prev_pos; pg_tde_write_one_map_entry(map_fd, &write_map_entry, &prev_pos, db_map_path); - /* Let's close the file. */ close(map_fd); } @@ -496,7 +492,6 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri off_t curr_pos = 0; off_t prev_pos = 0; - /* Set the file paths */ pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); @@ -529,7 +524,6 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri curr_pos = prev_pos; pg_tde_write_one_map_entry(map_fd, write_map_entry, &prev_pos, db_map_path); - /* Let's close the file. */ close(map_fd); LWLockRelease(tde_lwlock_enc_keys()); @@ -716,7 +710,6 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) /* Let's get the header. Buff should start with the map file header. */ fheader = (TDEFileHeader *) file_data; - /* Set the file paths */ pg_tde_set_db_file_path(fheader->signed_key_info.data.databaseId, db_map_path); /* Initialize the new file and set the name */ @@ -860,7 +853,6 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) Assert(rlocator); - /* Get the file paths */ pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); if (access(db_map_path, F_OK) == -1) @@ -1090,7 +1082,6 @@ pg_tde_get_principal_key_info(Oid dbOid) off_t bytes_read = 0; char db_map_path[MAXPGPATH] = {0}; - /* Set the file paths */ pg_tde_set_db_file_path(dbOid, db_map_path); /* From 676d637f5150b47fc97c22bacbc5ec9239dfdda7 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:17:44 +0200 Subject: [PATCH 097/796] Do not initialize local variables unnecessarily By initializing them to something it makes it look like there is an intent behind it which there in all of these cases is not. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 38 +++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 8ab58500b5031..f31613e7cc3c9 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -253,7 +253,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat void pg_tde_delete_tde_files(Oid dbOid) { - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; pg_tde_set_db_file_path(dbOid, db_map_path); @@ -266,7 +266,7 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) { int map_fd; off_t curr_pos; - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); @@ -289,9 +289,9 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) { - int map_fd = -1; + int map_fd; off_t curr_pos = 0; - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; TDESignedPrincipalKeyInfo signed_key_Info; pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); @@ -419,8 +419,8 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog) { - char db_map_path[MAXPGPATH] = {0}; - int map_fd = -1; + char db_map_path[MAXPGPATH]; + int map_fd; off_t curr_pos = 0; off_t prev_pos = 0; TDEMapEntry write_map_entry; @@ -487,8 +487,8 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info) { - char db_map_path[MAXPGPATH] = {0}; - int map_fd = -1; + char db_map_path[MAXPGPATH]; + int map_fd; off_t curr_pos = 0; off_t prev_pos = 0; @@ -701,10 +701,10 @@ void pg_tde_write_map_keydata_file(off_t file_size, char *file_data) { TDEFileHeader *fheader; + char db_map_path[MAXPGPATH]; char path_new[MAXPGPATH]; int fd_new; off_t curr_pos = 0; - char db_map_path[MAXPGPATH] = {0}; bool is_err = false; /* Let's get the header. Buff should start with the map file header. */ @@ -746,7 +746,7 @@ void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) { LWLock *lock_pk = tde_lwlock_enc_keys(); - int fd = -1; + int fd; off_t read_pos, write_pos, last_key_idx; @@ -848,7 +848,7 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) TDEMapEntry map_entry; TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; InternalKey *rel_key; Assert(rlocator); @@ -1059,7 +1059,7 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid /* Read the encrypted key */ if (pg_pread(fd, map_entry, MAP_ENTRY_SIZE, read_pos) != MAP_ENTRY_SIZE) { - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; pg_tde_set_db_file_path(databaseId, db_map_path); ereport(FATAL, @@ -1076,11 +1076,11 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid TDESignedPrincipalKeyInfo * pg_tde_get_principal_key_info(Oid dbOid) { - int fd = -1; + char db_map_path[MAXPGPATH]; + int fd; TDEFileHeader fheader; TDESignedPrincipalKeyInfo *signed_key_info = NULL; off_t bytes_read = 0; - char db_map_path[MAXPGPATH] = {0}; pg_tde_set_db_file_path(dbOid, db_map_path); @@ -1198,12 +1198,12 @@ InternalKey * pg_tde_read_last_wal_key(void) { RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; - int fd = -1; - int file_idx = 0; + int fd; + int file_idx; TDEMapEntry map_entry; InternalKey *rel_key_data; off_t fsize; @@ -1242,11 +1242,11 @@ WALKeyCacheRec * pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) { RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH] = {0}; + char db_map_path[MAXPGPATH]; off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; - int fd = -1; + int fd; int keys_count; WALKeyCacheRec *return_wal_rec = NULL; From 0b2dbd2ccdd06fa6aba6ef56082149177146e379 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:15:02 +0200 Subject: [PATCH 098/796] Consistently update the position in a pointer passed to the function This UX is not nice but let's at least be consistent about it. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index f31613e7cc3c9..7d9876c347128 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -126,7 +126,7 @@ static InternalKey *pg_tde_create_local_key(const RelFileLocator *newrlocator, u static void pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type); static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); -static off_t pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); +static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); @@ -380,7 +380,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ /* * Based on the given arguments,write the entry into the key map file. */ -static off_t +static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path) { int bytes_written = 0; @@ -401,7 +401,7 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, errmsg("could not fsync file \"%s\": %m", db_map_path)); } - return (*offset + bytes_written); + *offset += bytes_written; } /* @@ -633,7 +633,6 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p while (1) { InternalKey *rel_key_data; - off_t new_prev_pos; TDEMapEntry read_map_entry, write_map_entry; RelFileLocator rloc; @@ -652,9 +651,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p rel_key_data = tde_decrypt_rel_key(principal_key, &read_map_entry); pg_tde_initialize_map_entry(&write_map_entry, new_principal_key, &rloc, rel_key_data); - /* Write the given entry at the location pointed by prev_pos */ - new_prev_pos = new_curr_pos; - new_curr_pos = pg_tde_write_one_map_entry(new_fd, &write_map_entry, &new_prev_pos, new_path); + pg_tde_write_one_map_entry(new_fd, &write_map_entry, &new_curr_pos, new_path); pfree(rel_key_data); } From 15ea8358f3ca85ff5b1c904f35c886ba4c08539f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:16:53 +0200 Subject: [PATCH 099/796] Do not switch between using prev_pos and curr_pos Be consistent about always passing curr_pos when reading or writing the map file. The code is easier to understand if only one variable is used for positioning in the file. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 7d9876c347128..a754c9c0b2bc3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -422,7 +422,6 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ char db_map_path[MAXPGPATH]; int map_fd; off_t curr_pos = 0; - off_t prev_pos = 0; TDEMapEntry write_map_entry; TDESignedPrincipalKeyInfo signed_key_Info; @@ -434,7 +433,6 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ /* Open and validate file for basic correctness. */ map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, false, &curr_pos); - prev_pos = curr_pos; /* * Read until we find an empty slot. Otherwise, read until end. This seems @@ -444,13 +442,19 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ while (1) { TDEMapEntry read_map_entry; + off_t prev_pos = curr_pos; - prev_pos = curr_pos; if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) + { + curr_pos = prev_pos; break; + } if (read_map_entry.flags == MAP_ENTRY_EMPTY) + { + curr_pos = prev_pos; break; + } } /* Initialize map entry and encrypt key */ @@ -468,12 +472,8 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); } - /* - * Write the given entry at the location pointed by prev_pos; i.e. the - * free entry - */ - curr_pos = prev_pos; - pg_tde_write_one_map_entry(map_fd, &write_map_entry, &prev_pos, db_map_path); + /* Write the given entry at curr_pos; i.e. the free entry. */ + pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); close(map_fd); } @@ -490,7 +490,6 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri char db_map_path[MAXPGPATH]; int map_fd; off_t curr_pos = 0; - off_t prev_pos = 0; pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); @@ -498,7 +497,6 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri /* Open and validate file for basic correctness. */ map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos); - prev_pos = curr_pos; /* * Read until we find an empty slot. Otherwise, read until end. This seems @@ -508,21 +506,23 @@ pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPri while (1) { TDEMapEntry read_map_entry; + off_t prev_pos = curr_pos; - prev_pos = curr_pos; if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) + { + curr_pos = prev_pos; break; + } if (read_map_entry.flags == MAP_ENTRY_EMPTY) + { + curr_pos = prev_pos; break; + } } - /* - * Write the given entry at the location pointed by prev_pos; i.e. the - * free entry - */ - curr_pos = prev_pos; - pg_tde_write_one_map_entry(map_fd, write_map_entry, &prev_pos, db_map_path); + /* Write the given entry at curr_pos; i.e. the free entry. */ + pg_tde_write_one_map_entry(map_fd, write_map_entry, &curr_pos, db_map_path); close(map_fd); From 1885236fa9f968edb650a046988da95ad87adc63 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:45:09 +0200 Subject: [PATCH 100/796] Simplify error handling in pg_tde_write_map_keydata_file() --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index a754c9c0b2bc3..3882fb82973c2 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -702,7 +702,6 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) char path_new[MAXPGPATH]; int fd_new; off_t curr_pos = 0; - bool is_err = false; /* Let's get the header. Buff should start with the map file header. */ fheader = (TDEFileHeader *) file_data; @@ -717,23 +716,22 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) ereport(WARNING, errcode_for_file_access(), errmsg("could not write tde file \"%s\": %m", path_new)); - is_err = true; - goto FINALIZE; + close(fd_new); + return; } + if (pg_fsync(fd_new) != 0) { ereport(WARNING, errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", path_new)); - is_err = true; - goto FINALIZE; + close(fd_new); + return; } -FINALIZE: close(fd_new); - if (!is_err) - finalize_key_rotation(db_map_path, path_new); + finalize_key_rotation(db_map_path, path_new); } /* It's called by seg_write inside crit section so no pallocs, hence From 14a2bd2849c6a0bf105c41831b2850b1319ca8c7 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 20:48:56 +0200 Subject: [PATCH 101/796] Clean up a couple comments in pg_tde_tdemap.c --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 3882fb82973c2..5b8189bd48615 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -163,9 +163,7 @@ pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type errhint("create one using pg_tde_set_key before using encrypted tables")); } - /* - * Add the encrypted key to the key map data file structure. - */ + /* Add the encrypted key to the key map data file structure. */ pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key, true); LWLockRelease(lock_pk); @@ -734,7 +732,8 @@ pg_tde_write_map_keydata_file(off_t file_size, char *file_data) finalize_key_rotation(db_map_path, path_new); } -/* It's called by seg_write inside crit section so no pallocs, hence +/* + * It's called by seg_write inside crit section so no pallocs, hence * needs keyfile_path */ void From 043232e31ed06de6ce4e416f24a03ae1e39ddea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 10 Apr 2025 13:39:43 +0200 Subject: [PATCH 102/796] Change some keyring function to return void No callers cared about the return value of these functions anyway. --- contrib/pg_tde/src/catalog/tde_keyring.c | 25 +++++++++---------- .../pg_tde/src/include/catalog/tde_keyring.h | 12 ++++----- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 34a267b400846..eb0bd48bf943a 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -62,9 +62,9 @@ static GenericKeyring *load_keyring_provider_from_record(KeyringProviderRecord * static inline void get_keyring_infofile_path(char *resPath, Oid dbOid); static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider); -static uint32 write_key_provider_info(KeyringProviderRecord *provider, - Oid database_id, off_t position, - bool error_if_exists, bool write_xlog); +static void write_key_provider_info(KeyringProviderRecord *provider, + Oid database_id, off_t position, + bool error_if_exists, bool write_xlog); #ifdef FRONTEND @@ -188,10 +188,10 @@ GetAllKeyringProviders(Oid dbOid) return scan_key_provider_file(PROVIDER_SCAN_ALL, NULL, dbOid); } -uint32 +void redo_key_provider_info(KeyringProviderXLRecord *xlrec) { - return write_key_provider_info(&xlrec->provider, xlrec->database_id, xlrec->offset_in_file, false, false); + write_key_provider_info(&xlrec->provider, xlrec->database_id, xlrec->offset_in_file, false, false); } static void @@ -394,7 +394,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid) #endif /* !FRONTEND */ -static uint32 +static void write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, off_t position, bool error_if_exists, bool write_xlog) { @@ -548,26 +548,25 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, } close(fd); LWLockRelease(tde_provider_info_lock()); - return provider->provider_id; } /* * Save the key provider info to the file */ -uint32 +void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { - return write_key_provider_info(provider, databaseId, -1, true, write_xlog); + write_key_provider_info(provider, databaseId, -1, true, write_xlog); } -uint32 +void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { - return write_key_provider_info(provider, databaseId, -1, false, write_xlog); + write_key_provider_info(provider, databaseId, -1, false, write_xlog); } -uint32 +void delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog) { KeyringProviderRecord kpr; @@ -575,7 +574,7 @@ delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog) memset(&kpr, 0, sizeof(KeyringProviderRecord)); kpr.provider_id = provider_id; - return modify_key_provider_info(&kpr, databaseId, write_xlog); + modify_key_provider_info(&kpr, databaseId, write_xlog); } #ifdef FRONTEND diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 0dae8bc130e93..b14f4d01001e7 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -33,13 +33,13 @@ extern GenericKeyring *GetKeyProviderByName(const char *provider_name, Oid dbOid extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); extern ProviderType get_keyring_provider_from_typename(char *provider_type); extern void InitializeKeyProviderInfo(void); -extern uint32 save_new_key_provider_info(KeyringProviderRecord *provider, - Oid databaseId, bool write_xlog); -extern uint32 modify_key_provider_info(KeyringProviderRecord *provider, +extern void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); -extern uint32 delete_key_provider_info(int provider_id, - Oid databaseId, bool write_xlog); -extern uint32 redo_key_provider_info(KeyringProviderXLRecord *xlrec); +extern void modify_key_provider_info(KeyringProviderRecord *provider, + Oid databaseId, bool write_xlog); +extern void delete_key_provider_info(int provider_id, + Oid databaseId, bool write_xlog); +extern void redo_key_provider_info(KeyringProviderXLRecord *xlrec); extern bool ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf, int buf_len); From ce63840ca94f33463f099cf5dff8ff6c17ecc434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 16 Apr 2025 13:58:27 +0200 Subject: [PATCH 103/796] Remove non-test This regression file didn't really test anything. The tests it was supposed to do was removed here https://github.com/percona/pg_tde/commit/e270322f72f80dfd98f951f30400e270a5d41cee So now it doesn't test anything that key_provider.sql doesn't already do for us. --- contrib/pg_tde/Makefile | 1 - .../expected/keyprovider_dependency.out | 34 ------------------- contrib/pg_tde/meson.build | 1 - contrib/pg_tde/sql/keyprovider_dependency.sql | 11 ------ 4 files changed, 47 deletions(-) delete mode 100644 contrib/pg_tde/expected/keyprovider_dependency.out delete mode 100644 contrib/pg_tde/sql/keyprovider_dependency.sql diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 53c1e57812fc9..e31267a6365bf 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -15,7 +15,6 @@ cache_alloc \ change_access_method \ insert_update_delete \ key_provider \ -keyprovider_dependency \ kmip_test \ partition_table \ pg_tde_is_encrypted \ diff --git a/contrib/pg_tde/expected/keyprovider_dependency.out b/contrib/pg_tde/expected/keyprovider_dependency.out deleted file mode 100644 index 01ae95d792645..0000000000000 --- a/contrib/pg_tde/expected/keyprovider_dependency.out +++ /dev/null @@ -1,34 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - -SELECT pg_tde_add_database_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 2 -(1 row) - -SELECT pg_tde_add_database_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); - pg_tde_add_database_key_provider_vault_v2 -------------------------------------------- - 3 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+----------------------------------------------------------------------------------------------------------------------------------------------- - 1 | mk-file | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | free-file | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_2.per"} - 3 | V2-vault | vault-v2 | {"type" : "vault-v2", "url" : "percona.com/vault-v2/percona", "token" : "vault-token", "mountPath" : "/mount/dev", "caPath" : "ca-cert-auth"} -(3 rows) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','mk-file'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 4e72b8d54b352..c1cfcb20548ce 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -90,7 +90,6 @@ sql_tests = [ 'change_access_method', 'insert_update_delete', 'key_provider', - 'keyprovider_dependency', 'kmip_test', 'partition_table', 'pg_tde_is_encrypted', diff --git a/contrib/pg_tde/sql/keyprovider_dependency.sql b/contrib/pg_tde/sql/keyprovider_dependency.sql deleted file mode 100644 index f8243a5e6bf54..0000000000000 --- a/contrib/pg_tde/sql/keyprovider_dependency.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; - -SELECT pg_tde_add_database_key_provider_file('mk-file','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_add_database_key_provider_file('free-file','/tmp/pg_tde_test_keyring_2.per'); -SELECT pg_tde_add_database_key_provider_vault_v2('V2-vault','vault-token','percona.com/vault-v2/percona','/mount/dev','ca-cert-auth'); - -SELECT * FROM pg_tde_list_all_database_key_providers(); - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','mk-file'); - -DROP EXTENSION pg_tde; From d6eb31f59a9b8288cd9aea3dbf3179c2de7a8a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 17 Apr 2025 19:27:27 +0200 Subject: [PATCH 104/796] Fix error message in keyring_file.c The colon was on the wrong side of the space. --- contrib/pg_tde/src/keyring/keyring_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index e80372065d0d1..22214d24b2e30 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -119,7 +119,7 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) { ereport(ERROR, errcode_for_file_access(), - errmsg("Failed to open keyring file %s :%m", file_keyring->file_name)); + errmsg("Failed to open keyring file %s: %m", file_keyring->file_name)); } /* Write key to the end of file */ curr_pos = lseek(fd, 0, SEEK_END); From 65fa2e241b60059f4bf33feee169c1780cb2faa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 17 Apr 2025 19:31:27 +0200 Subject: [PATCH 105/796] Rename KeyringProviderXLRecord The new name, KeyringProviderRecordInFile, describes what it is rather than what it's used for. But the real reason is that I want to use it for other things than the WAL in future commits. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 4 ++-- contrib/pg_tde/src/catalog/tde_keyring.c | 6 +++--- contrib/pg_tde/src/include/catalog/tde_keyring.h | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 7417d85beecdd..7e5a70fe30007 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -68,7 +68,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { - KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); + KeyringProviderRecordInFile *xlrec = (KeyringProviderRecordInFile *) XLogRecGetData(record); redo_key_provider_info(xlrec); } @@ -109,7 +109,7 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { - KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record); + KeyringProviderRecordInFile *xlrec = (KeyringProviderRecordInFile *) XLogRecGetData(record); appendStringInfo(buf, "db: %u, provider id: %d", xlrec->database_id, xlrec->provider.provider_id); } diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index eb0bd48bf943a..8a08092663005 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -189,7 +189,7 @@ GetAllKeyringProviders(Oid dbOid) } void -redo_key_provider_info(KeyringProviderXLRecord *xlrec) +redo_key_provider_info(KeyringProviderRecordInFile *xlrec) { write_key_provider_info(&xlrec->provider, xlrec->database_id, xlrec->offset_in_file, false, false); } @@ -503,14 +503,14 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, if (write_xlog) { #ifndef FRONTEND - KeyringProviderXLRecord xlrec; + KeyringProviderRecordInFile xlrec; xlrec.database_id = database_id; xlrec.offset_in_file = curr_pos; xlrec.provider = *provider; XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(KeyringProviderXLRecord)); + XLogRegisterData((char *) &xlrec, sizeof(KeyringProviderRecordInFile)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); #else Assert(0); diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index b14f4d01001e7..9b937d0ac7b35 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -22,12 +22,13 @@ typedef struct KeyringProviderRecord ProviderType provider_type; } KeyringProviderRecord; -typedef struct KeyringProviderXLRecord +/* This struct also keeps some context of where the record belongs */ +typedef struct KeyringProviderRecordInFile { Oid database_id; off_t offset_in_file; KeyringProviderRecord provider; -} KeyringProviderXLRecord; +} KeyringProviderRecordInFile; extern GenericKeyring *GetKeyProviderByName(const char *provider_name, Oid dbOid); extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); @@ -39,7 +40,7 @@ extern void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); extern void delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog); -extern void redo_key_provider_info(KeyringProviderXLRecord *xlrec); +extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); extern bool ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf, int buf_len); From 1b8513c21e5ad2f65a792a716e9b003b2844e1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 14 Apr 2025 20:06:33 +0200 Subject: [PATCH 106/796] Refactor some lower level functions in tde_keyring Previously write_key_provider_info() was a bit of a "do everything" function that had very different behavior depending on what parameters was passed to it. This commit reworks it to a "dumb" function that just writes the data without asking questions and have the callers take responsibility for data validity. This is to make it easier to validate the data in different ways depending on the caller's needs without further complicating write_key_provider_info(). --- contrib/pg_tde/src/catalog/tde_keyring.c | 340 +++++++++++------- .../pg_tde/src/catalog/tde_principal_key.c | 2 +- .../pg_tde/src/include/catalog/tde_keyring.h | 2 +- 3 files changed, 210 insertions(+), 134 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 8a08092663005..a0c844726f772 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -60,11 +60,10 @@ static KmipKeyring *load_kmip_keyring_provider_options(char *keyring_options); static void debug_print_kerying(GenericKeyring *keyring); static GenericKeyring *load_keyring_provider_from_record(KeyringProviderRecord *provider); static inline void get_keyring_infofile_path(char *resPath, Oid dbOid); +static int open_keyring_infofile(Oid dbOid, int flags); static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider); -static void write_key_provider_info(KeyringProviderRecord *provider, - Oid database_id, off_t position, - bool error_if_exists, bool write_xlog); +static void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog); #ifdef FRONTEND @@ -191,7 +190,7 @@ GetAllKeyringProviders(Oid dbOid) void redo_key_provider_info(KeyringProviderRecordInFile *xlrec) { - write_key_provider_info(&xlrec->provider, xlrec->database_id, xlrec->offset_in_file, false, false); + write_key_provider_info(xlrec, false); } static void @@ -234,8 +233,6 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) /* reports error if not found */ GenericKeyring *keyring = GetKeyProviderByName(provider_name, dbOid); - pfree(keyring); - nlen = strlen(provider_name); if (nlen >= sizeof(provider.provider_name)) ereport(ERROR, @@ -250,10 +247,13 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) /* Struct will be saved to disk so keep clean */ memset(&provider, 0, sizeof(provider)); - provider.provider_id = 0; + provider.provider_id = keyring->keyring_id; memcpy(provider.provider_name, provider_name, nlen); memcpy(provider.options, options, olen); provider.provider_type = get_keyring_provider_from_typename(provider_type); + + pfree(keyring); + modify_key_provider_info(&provider, dbOid, true); PG_RETURN_INT32(provider.provider_id); @@ -395,47 +395,17 @@ GetKeyProviderByID(int provider_id, Oid dbOid) #endif /* !FRONTEND */ static void -write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, - off_t position, bool error_if_exists, bool write_xlog) +write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) { off_t bytes_written = 0; - off_t curr_pos = 0; int fd; - int seek_pos = -1; - - /* Named max, but global key provider oids are stored as negative numbers! */ - int max_provider_id = 0; char kp_info_path[MAXPGPATH] = {0}; - KeyringProviderRecord existing_provider; - GenericKeyring *record; - Assert(provider != NULL); - - if (error_if_exists && provider->provider_id != 0) - { - ereport(ERROR, - errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid write provider call")); - } - - /* Try to parse the JSON data first: if it doesn't work, don't save it! */ - if (provider->provider_type != UNKNOWN_KEY_PROVIDER) - { - record = load_keyring_provider_from_record(provider); - if (record == NULL) - { - ereport(ERROR, - errcode(ERRCODE_DATA_EXCEPTION), errmsg("Invalid provider options")); - } - else - { - pfree(record); - } - } - - get_keyring_infofile_path(kp_info_path, database_id); - - LWLockAcquire(tde_provider_info_lock(), LW_EXCLUSIVE); + Assert(record != NULL); + Assert(record->offset_in_file >= 0); + Assert(LWLockHeldByMeInMode(tde_provider_info_lock(), LW_EXCLUSIVE)); + get_keyring_infofile_path(kp_info_path, record->database_id); fd = BasicOpenFile(kp_info_path, O_CREAT | O_RDWR | PG_BINARY); if (fd < 0) { @@ -443,94 +413,25 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, errcode_for_file_access(), errmsg("could not open tde file \"%s\": %m", kp_info_path)); } - if (position == -1) - { - /* - * we also need to verify the name conflict and generate the next - * provider ID - */ - int before_pos = curr_pos; - - while (fetch_next_key_provider(fd, &curr_pos, &existing_provider)) - { - if (provider->provider_id != 0 && existing_provider.provider_id == provider->provider_id) - { - seek_pos = before_pos; - break; - } - if (strlen(existing_provider.provider_name) > 0 && strcmp(existing_provider.provider_name, provider->provider_name) == 0) - { - if (error_if_exists) - { - close(fd); - ereport(ERROR, - errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("key provider \"%s\" already exists", provider->provider_name)); - } - else - { - - seek_pos = before_pos; - provider->provider_id = existing_provider.provider_id; - break; - } - } - if (max_provider_id < abs(existing_provider.provider_id)) - max_provider_id = abs(existing_provider.provider_id); - before_pos = curr_pos; - } - if (seek_pos == -1) - { - provider->provider_id = max_provider_id + 1; - - if (database_id == GLOBAL_DATA_TDE_OID) - { - provider->provider_id = -provider->provider_id; - } - curr_pos = lseek(fd, 0, SEEK_END); - } - else - { - curr_pos = lseek(fd, seek_pos, SEEK_CUR); - } - - - /* - * emit the xlog here. So that we can handle partial file write errors - * but cannot make new WAL entries during recovery. - */ - if (write_xlog) - { + /* + * emit the xlog here. So that we can handle partial file write errors but + * cannot make new WAL entries during recovery. + */ + if (write_xlog) + { #ifndef FRONTEND - KeyringProviderRecordInFile xlrec; - - xlrec.database_id = database_id; - xlrec.offset_in_file = curr_pos; - xlrec.provider = *provider; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(KeyringProviderRecordInFile)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); + XLogBeginInsert(); + XLogRegisterData((char *) record, sizeof(KeyringProviderRecordInFile)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); #else - Assert(0); + Assert(0); #endif - } - } - else - { - /* - * we are performing redo, just go to the position received from the - * xlog and write the record there. No need to verify the name - * conflict and generate the provider ID - */ - curr_pos = lseek(fd, position, SEEK_SET); } - /* - * All good, Just add a new provider - */ - bytes_written = pg_pwrite(fd, provider, sizeof(KeyringProviderRecord), curr_pos); + bytes_written = pg_pwrite(fd, &(record->provider), + sizeof(KeyringProviderRecord), + record->offset_in_file); if (bytes_written != sizeof(KeyringProviderRecord)) { close(fd); @@ -547,9 +448,68 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, errmsg("could not fsync file \"%s\": %m", kp_info_path)); } close(fd); - LWLockRelease(tde_provider_info_lock()); } +static void +check_provider_record(KeyringProviderRecord *provider_record) +{ + GenericKeyring *provider; + + if (provider_record->provider_type == UNKNOWN_KEY_PROVIDER) + { + ereport(ERROR, + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("Invalid provider type.")); + } + + /* Validate that the provider record can be properly parsed. */ + provider = load_keyring_provider_from_record(provider_record); + + if (provider == NULL) + { + ereport(ERROR, + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("Invalid provider options.")); + } + + pfree(provider); +} + +/* Returns true if the record is found, false otherwise. */ +static bool +get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, + KeyringProviderRecordInFile *record) +{ + off_t current_file_offset = 0; + off_t next_file_offset = 0; + int fd; + KeyringProviderRecord existing_provider; + + Assert(provider_name != NULL); + Assert(record != NULL); + + fd = open_keyring_infofile(database_id, O_RDONLY); + + while (fetch_next_key_provider(fd, &next_file_offset, &existing_provider)) + { + /* Ignore deleted provider records */ + if (existing_provider.provider_type != UNKNOWN_KEY_PROVIDER + && strcmp(existing_provider.provider_name, provider_name) == 0) + { + record->database_id = database_id; + record->offset_in_file = current_file_offset; + record->provider = existing_provider; + close(fd); + return true; + } + + current_file_offset = next_file_offset; + } + + /* No matching key provider found */ + close(fd); + return false; +} /* * Save the key provider info to the file @@ -557,24 +517,123 @@ write_key_provider_info(KeyringProviderRecord *provider, Oid database_id, void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { - write_key_provider_info(provider, databaseId, -1, true, write_xlog); + off_t next_file_offset; + int fd; + KeyringProviderRecord existing_provider; + int max_provider_id = 0; + int new_provider_id; + KeyringProviderRecordInFile file_record; + + Assert(provider != NULL); + + check_provider_record(provider); + + LWLockAcquire(tde_provider_info_lock(), LW_EXCLUSIVE); + + /* + * Validate that the provider name does not collide with an existing + * provider, find the largest existing provider_id and also find the end + * of file offset for appending the provider record. + */ + fd = open_keyring_infofile(databaseId, O_CREAT | O_RDONLY); + + next_file_offset = 0; + while (fetch_next_key_provider(fd, &next_file_offset, &existing_provider)) + { + /* + * abs() is used here because provider_id is negative for global + * providers. + */ + max_provider_id = Max(max_provider_id, abs(existing_provider.provider_id)); + + /* Ignore deleted records */ + if (existing_provider.provider_type == UNKNOWN_KEY_PROVIDER) + continue; + + if (strcmp(existing_provider.provider_name, provider->provider_name) == 0) + { + close(fd); + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("Key provider \"%s\" already exists.", provider->provider_name)); + } + } + close(fd); + + if (max_provider_id == PG_INT32_MAX) + { + ereport(ERROR, + errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("cannot create key provider, id out of range")); + } + new_provider_id = max_provider_id + 1; + provider->provider_id = (databaseId == GLOBAL_DATA_TDE_OID ? -new_provider_id : new_provider_id); + + file_record.database_id = databaseId; + file_record.offset_in_file = next_file_offset; + file_record.provider = *provider; + + write_key_provider_info(&file_record, true); + + LWLockRelease(tde_provider_info_lock()); } void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) { - write_key_provider_info(provider, databaseId, -1, false, write_xlog); + KeyringProviderRecordInFile record; + + Assert(provider != NULL); + + check_provider_record(provider); + + LWLockAcquire(tde_provider_info_lock(), LW_EXCLUSIVE); + + if (get_keyring_info_file_record_by_name(provider->provider_name, databaseId, &record) == false) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("keyring \"%s\" does not exist", provider->provider_name)); + } + + if (provider->provider_id != record.provider.provider_id) + { + /* This should never happen. */ + ereport(ERROR, + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("provider id mismatch %d is not %d", provider->provider_id, record.provider.provider_id)); + } + + record.provider = *provider; + write_key_provider_info(&record, write_xlog); + + LWLockRelease(tde_provider_info_lock()); } void -delete_key_provider_info(int provider_id, Oid databaseId, bool write_xlog) +delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog) { - KeyringProviderRecord kpr; + int provider_id; + KeyringProviderRecordInFile record; + + Assert(provider_name != NULL); + + LWLockAcquire(tde_provider_info_lock(), LW_EXCLUSIVE); + + if (get_keyring_info_file_record_by_name(provider_name, databaseId, &record) == false) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key provider \"%s\" does not exist", provider_name)); + } - memset(&kpr, 0, sizeof(KeyringProviderRecord)); - kpr.provider_id = provider_id; + /* Preserve provider_id for deleted records in the file. */ + provider_id = record.provider.provider_id; + memset(&(record.provider), 0, sizeof(KeyringProviderRecord)); + record.provider.provider_id = provider_id; + write_key_provider_info(&record, write_xlog); - modify_key_provider_info(&kpr, databaseId, write_xlog); + LWLockRelease(tde_provider_info_lock()); } #ifdef FRONTEND @@ -852,6 +911,23 @@ get_keyring_infofile_path(char *resPath, Oid dbOid) join_path_components(resPath, pg_tde_get_tde_data_dir(), psprintf(PG_TDE_KEYRING_FILENAME, dbOid)); } +static int +open_keyring_infofile(Oid database_id, int flags) +{ + int fd; + char kp_info_path[MAXPGPATH] = {0}; + + get_keyring_infofile_path(kp_info_path, database_id); + fd = BasicOpenFile(kp_info_path, flags | PG_BINARY); + if (fd < 0) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", kp_info_path)); + } + return fd; +} + /* * Fetch the next key provider from the file and update the curr_pos */ diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 099a5629dbd3b..4a3731ff6e381 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -1141,7 +1141,7 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global) errmsg("Can't delete a provider which is currently in use")); } - delete_key_provider_info(provider_id, db_oid, true); + delete_key_provider_info(provider_name, db_oid, true); PG_RETURN_VOID(); } diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 9b937d0ac7b35..0cd7e4470e91e 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -38,7 +38,7 @@ extern void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); extern void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); -extern void delete_key_provider_info(int provider_id, +extern void delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog); extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); From 157230de396d17ac71ebf9c27112377463f3dc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 16 Apr 2025 13:29:01 +0200 Subject: [PATCH 107/796] PG-1419 Validate key provider access This adds some validation to make sure we can access the key provider when it's created to make the user experience a little nicer. The actual access validation is very rudimentary for now but can easily be expanded. --- .../pg_tde/documentation/docs/architecture.md | 2 - contrib/pg_tde/expected/key_provider.out | 3 ++ contrib/pg_tde/expected/key_provider_1.out | 3 ++ contrib/pg_tde/expected/kmip_test.out | 3 ++ contrib/pg_tde/expected/vault_v2_test.out | 3 ++ contrib/pg_tde/sql/key_provider.sql | 3 ++ contrib/pg_tde/sql/kmip_test.sql | 3 ++ contrib/pg_tde/sql/vault_v2_test.sql | 3 ++ contrib/pg_tde/src/catalog/tde_keyring.c | 2 + .../pg_tde/src/include/keyring/keyring_api.h | 2 + contrib/pg_tde/src/keyring/keyring_api.c | 13 +++++++ contrib/pg_tde/src/keyring/keyring_file.c | 20 +++++++++- contrib/pg_tde/src/keyring/keyring_kmip.c | 16 +++++++- contrib/pg_tde/src/keyring/keyring_vault.c | 39 ++++++++++++++++++- 14 files changed, 110 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture.md index 3be3f4f78ab3f..f7cd70e4805a9 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture.md @@ -206,8 +206,6 @@ To add a database specific provider: pg_tde_add_database_key_provider_('provider_name', ... details ...) ``` -Note that in these functions do not verify the parameters. For that, see `pg_tde_verify_key`. - ### Changing providers To change a value of a global provider: diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 202555ae9dad2..51f45f3eb2200 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -160,4 +160,7 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -1 | file-keyring (1 row) +-- Creating a file key provider fails if we can't open or create the file +SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); +ERROR: Failed to open keyring file /cant-create-file-in-root.per: Permission denied DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out index a583cc3dad108..4b072d6f35fcd 100644 --- a/contrib/pg_tde/expected/key_provider_1.out +++ b/contrib/pg_tde/expected/key_provider_1.out @@ -164,4 +164,7 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -2 | file-keyring (2 rows) +-- Creating a file key provider fails if we can't open or create the file +SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); +ERROR: Failed to open keyring file /cant-create-file-in-root.per: Permission denied DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index 9b4949ece005a..3d905bbaeedb3 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -34,4 +34,7 @@ SELECT pg_tde_verify_key(); (1 row) DROP TABLE test_enc; +-- Creating provider fails if we can't connect to kmip server +SELECT pg_tde_add_database_key_provider_kmip('will-not-work','127.0.0.1', 61, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); +ERROR: SSL error: BIO_do_connect failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 06a2fd71949f7..b4ff2a1ffdfac 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -51,4 +51,7 @@ SELECT pg_tde_verify_key(); (1 row) DROP TABLE test_enc; +-- Creating provider fails if we can't connect to vault +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token', 'http://127.0.0.1:61', 'secret', NULL); +ERROR: HTTP(S) request to keyring provider "will-not-work" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 76eb036850a07..78fcc71de83a0 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -52,4 +52,7 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); SELECT pg_tde_delete_global_key_provider('file-keyring2'); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +-- Creating a file key provider fails if we can't open or create the file +SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index 4b647ba11295d..d47467e5fda16 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -19,4 +19,7 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; +-- Creating provider fails if we can't connect to kmip server +SELECT pg_tde_add_database_key_provider_kmip('will-not-work','127.0.0.1', 61, '/tmp/server_certificate.pem', '/tmp/client_key_jane_doe.pem'); + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 4e8b92c97aa2a..205aa1118533f 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -31,4 +31,7 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; +-- Creating provider fails if we can't connect to vault +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token', 'http://127.0.0.1:61', 'secret', NULL); + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index a0c844726f772..9e04ad04f363a 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -472,6 +472,8 @@ check_provider_record(KeyringProviderRecord *provider_record) errmsg("Invalid provider options.")); } + KeyringValidate(provider); + pfree(provider); } diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index 3e3819d3b9120..89be8282f8500 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -61,6 +61,7 @@ typedef struct TDEKeyringRoutine { KeyInfo *(*keyring_get_key) (GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *returnCode); void (*keyring_store_key) (GenericKeyring *keyring, KeyInfo *key); + void (*keyring_validate) (GenericKeyring *keyring); } TDEKeyringRoutine; typedef struct FileKeyring @@ -91,5 +92,6 @@ extern void RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderTy extern KeyInfo *KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *returnCode); extern KeyInfo *KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len); +extern void KeyringValidate(GenericKeyring *keyring); #endif /* KEYRING_API_H */ diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 766d75dc43fd9..f6d9785eef756 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -74,6 +74,7 @@ RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderType type) Assert(routine != NULL); Assert(routine->keyring_get_key != NULL); Assert(routine->keyring_store_key != NULL); + Assert(routine->keyring_validate != NULL); kp = find_key_provider_type(type); if (kp) @@ -148,3 +149,15 @@ KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, uns return key; } + +void +KeyringValidate(GenericKeyring *keyring) +{ + RegisteredKeyProviderType *kp = find_key_provider_type(keyring->type); + + if (kp == NULL) + ereport(ERROR, + errmsg("Key provider of type %d not registered", keyring->type)); + + kp->routine->keyring_validate(keyring); +} diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 22214d24b2e30..5d1f83c1349ef 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -28,10 +28,12 @@ static KeyInfo *get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *return_code); static void set_key_by_name(GenericKeyring *keyring, KeyInfo *key); +static void validate(GenericKeyring *keyring); const TDEKeyringRoutine keyringFileRoutine = { .keyring_get_key = get_key_by_name, - .keyring_store_key = set_key_by_name + .keyring_store_key = set_key_by_name, + .keyring_validate = validate, }; void @@ -143,3 +145,19 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) } close(fd); } + +static void +validate(GenericKeyring *keyring) +{ + FileKeyring *file_keyring = (FileKeyring *) keyring; + int fd = BasicOpenFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); + + if (fd < 0) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("Failed to open keyring file %s: %m", file_keyring->file_name)); + } + + close(fd); +} diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index fbceedd223cda..86b438a920886 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -26,10 +26,12 @@ static void set_key_by_name(GenericKeyring *keyring, KeyInfo *key); static KeyInfo *get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *return_code); +static void validate(GenericKeyring *keyring); const TDEKeyringRoutine keyringKmipRoutine = { .keyring_get_key = get_key_by_name, - .keyring_store_key = set_key_by_name + .keyring_store_key = set_key_by_name, + .keyring_validate = validate, }; void @@ -204,3 +206,15 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode return key; } + +static void +validate(GenericKeyring *keyring) +{ + KmipKeyring *kmip_keyring = (KmipKeyring *) keyring; + KmipCtx ctx; + + kmipSslConnect(&ctx, kmip_keyring, true); + + BIO_free_all(ctx.bio); + SSL_CTX_free(ctx.ssl); +} diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index c302e5d550b72..03a924237195a 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -70,10 +70,12 @@ static bool curl_perform(VaultV2Keyring *keyring, const char *url, CurlString *o static void set_key_by_name(GenericKeyring *keyring, KeyInfo *key); static KeyInfo *get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *return_code); +static void validate(GenericKeyring *keyring); const TDEKeyringRoutine keyringVaultV2Routine = { .keyring_get_key = get_key_by_name, - .keyring_store_key = set_key_by_name + .keyring_store_key = set_key_by_name, + .keyring_validate = validate, }; void @@ -300,6 +302,41 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode return key; } +static void +validate(GenericKeyring *keyring) +{ + VaultV2Keyring *vault_keyring = (VaultV2Keyring *) keyring; + char url[VAULT_URL_MAX_LEN]; + CurlString str; + long httpCode = 0; + + /* + * Validate connection by listing available keys at the root level of the + * mount point + */ + snprintf(url, VAULT_URL_MAX_LEN, "%s/v1/%s/metadata/?list=true", + vault_keyring->vault_url, vault_keyring->vault_mount_path); + + if (!curl_perform(vault_keyring, url, &str, &httpCode, NULL)) + { + ereport(ERROR, + errmsg("HTTP(S) request to keyring provider \"%s\" failed", + vault_keyring->keyring.provider_name)); + } + + /* If the mount point doesn't have any secrets yet, we'll get a 404. */ + if (httpCode != 200 && httpCode != 404) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Listing secrets of \"%s\" at mountpoint \"%s\" failed", + vault_keyring->vault_url, vault_keyring->vault_mount_path)); + } + + if (str.ptr != NULL) + pfree(str.ptr); +} + /* * JSON parser routines * From 4724ecbfc654701ae24c4f0912cceccaa69c00e6 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 23 Apr 2025 14:19:45 +0300 Subject: [PATCH 108/796] Redo of the key provider info now needs a lock Although it may be technically omitted during redo, write_key_provider_info() now checks if there is a lock held --- contrib/pg_tde/src/catalog/tde_keyring.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 9e04ad04f363a..b063bc56ad91c 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -190,7 +190,9 @@ GetAllKeyringProviders(Oid dbOid) void redo_key_provider_info(KeyringProviderRecordInFile *xlrec) { + LWLockAcquire(tde_provider_info_lock(), LW_EXCLUSIVE); write_key_provider_info(xlrec, false); + LWLockRelease(tde_provider_info_lock()); } static void From 53f71222c9b9b8f03f60d73a0d3be20a66bc4b56 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 18 Apr 2025 01:10:17 +0200 Subject: [PATCH 109/796] PG-1441 Refactor code for generating keys in SMGR code The old code was harder to read than necessary since it had exactly two callers of which one had each value of the boolean flag. Breaking it up into two functions makes the intent clearer. While at it we also clean up the flow a bit more. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 80 +++++++++++++-------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 9b11f57acedfa..a885b9b4265a9 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -29,23 +29,30 @@ typedef TDESMgrRelationData *TDESMgrRelation; static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); static InternalKey * -tde_smgr_get_key(SMgrRelation reln, RelFileLocator *old_locator, bool can_create) +tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) +{ + /* Do not try to encrypt/decrypt catalog tables */ + if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) + return NULL; + + return GetSMGRRelationKey(*smgr_rlocator); +} + +static bool +tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { TdeCreateEvent *event; - InternalKey *key; - if (IsCatalogRelationOid(reln->smgr_rlocator.locator.relNumber)) - { - /* do not try to encrypt/decrypt catalog tables */ - return NULL; - } + /* Do not try to encrypt/decrypt catalog tables */ + if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) + return false; - /* see if we have a key for the relation, and return if yes */ - key = GetSMGRRelationKey(reln->smgr_rlocator); - if (key != NULL) - { - return key; - } + /* + * Make sure that even if a statement failed, and an event trigger end + * trigger didn't fire, we don't accidentaly create encrypted files when + * we don't have to. + */ + validateCurrentEventTriggerState(false); event = GetCurrentTdeCreateEvent(); @@ -55,25 +62,22 @@ tde_smgr_get_key(SMgrRelation reln, RelFileLocator *old_locator, bool can_create * * Every file has its own key, that makes logistics easier. */ - if (event->encryptMode == true && can_create) - { - return pg_tde_create_smgr_key(&reln->smgr_rlocator); - } + if (event->encryptMode) + return true; /* check if we had a key for the old locator, if there's one */ - if (old_locator != NULL && can_create) + if (!event->alterAccessMethodMode && old_locator) { - RelFileLocatorBackend rlocator = {.locator = *old_locator,.backend = reln->smgr_rlocator.backend}; - InternalKey *oldkey = GetSMGRRelationKey(rlocator); + RelFileLocatorBackend old_smgr_locator = { + .locator = *old_locator, + .backend = smgr_rlocator->backend, + }; - if (oldkey != NULL) - { - /* create a new key for the new file */ - return pg_tde_create_smgr_key(&reln->smgr_rlocator); - } + if (GetSMGRRelationKey(old_smgr_locator)) + return true; } - return NULL; + return false; } static void @@ -216,20 +220,11 @@ static void tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool isRedo) { TDESMgrRelation tdereln = (TDESMgrRelation) reln; - InternalKey *key; /* Copied from mdcreate() in md.c */ if (isRedo && tdereln->md_num_open_segs[forknum] > 0) return; - /* - * Make sure that even if a statement failed, and an event trigger end - * trigger didn't fire, we don't accidentaly create encrypted files when - * we don't have to. event above is a pointer, so it will reflect the - * correct state even if this changes it. - */ - validateCurrentEventTriggerState(false); - /* * This is the only function that gets called during actual CREATE * TABLE/INDEX (EVENT TRIGGER) @@ -240,20 +235,19 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool if (forknum == MAIN_FORKNUM || forknum == INIT_FORKNUM) { - TdeCreateEvent *event = GetCurrentTdeCreateEvent(); - /* * Only create keys when creating the main/init fork. Other forks can * be created later, even during tde creation events. We definitely do * not want to create keys then, even later, when we encrypt all * forks! - */ - - /* + * * Later calls then decide to encrypt or not based on the existence of - * the key + * the key. */ - key = tde_smgr_get_key(reln, event->alterAccessMethodMode ? NULL : &relold, true); + InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); + + if (!key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) + key = pg_tde_create_smgr_key(&reln->smgr_rlocator); if (key) { @@ -274,7 +268,7 @@ static void tde_mdopen(SMgrRelation reln) { TDESMgrRelation tdereln = (TDESMgrRelation) reln; - InternalKey *key = tde_smgr_get_key(reln, NULL, false); + InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); if (key) { From 4d9756dd172d5bab8b6e1104ce4b15dee3b929f1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 18 Apr 2025 00:44:47 +0200 Subject: [PATCH 110/796] PG-1441 Write simple TAP test for replication of pg_tde This way we can avoid obvious regression when refactoring the code for replicating keys in future commits. This test can in the future be expanded to test more interesting cases. --- contrib/pg_tde/meson.build | 4 +- contrib/pg_tde/t/012_replication.pl | 56 ++++++++++++++++++ contrib/pg_tde/t/expected/012_replication.out | 57 +++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 contrib/pg_tde/t/012_replication.pl create mode 100644 contrib/pg_tde/t/expected/012_replication.out diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index c1cfcb20548ce..6cf1a43994629 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -113,6 +113,7 @@ tap_tests = [ 't/009_wal_encrypt.pl', 't/010_change_key_provider.pl', 't/011_unlogged_tables.pl', + 't/012_replication.pl', ] tests += { @@ -125,7 +126,8 @@ tests += { 'runningcheck': false, }, 'tap': { - 'tests': tap_tests }, + 'tests': tap_tests + }, } pg_tde_inc = incdir diff --git a/contrib/pg_tde/t/012_replication.pl b/contrib/pg_tde/t/012_replication.pl new file mode 100644 index 0000000000000..884090467aafc --- /dev/null +++ b/contrib/pg_tde/t/012_replication.pl @@ -0,0 +1,56 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use Test::More; +use lib 't'; +use pgtde; + +PGTDE::setup_files_dir(basename($0)); + +my $primary = PostgreSQL::Test::Cluster->new('primary'); +$primary->init(allows_streaming => 1); +$primary->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$primary->start; + +$primary->backup('backup'); +my $replica = PostgreSQL::Test::Cluster->new('replica'); +$replica->init_from_backup($primary, 'backup', has_streaming => 1); +$replica->set_standby_mode(); +$replica->start; + +PGTDE::append_to_result_file("-- At primary"); + +PGTDE::psql($primary, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($primary, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');"); +PGTDE::psql($primary, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); + +PGTDE::psql($primary, 'postgres', "CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::psql($primary, 'postgres', "INSERT INTO test_enc (x) VALUES (1), (2);"); + +PGTDE::psql($primary, 'postgres', "CREATE TABLE test_plain (x int PRIMARY KEY) USING heap;"); +PGTDE::psql($primary, 'postgres', "INSERT INTO test_plain (x) VALUES (3), (4);"); + +$primary->wait_for_catchup('replica'); + +PGTDE::append_to_result_file("-- At replica"); + +PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_enc_pkey');"); +PGTDE::psql($replica, 'postgres', "SELECT * FROM test_enc ORDER BY x;"); + +PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_plain');"); +PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_plain_pkey');"); +PGTDE::psql($replica, 'postgres', "SELECT * FROM test_plain ORDER BY x;"); + +$replica->stop; + +$primary->stop; + +# Compare the expected and out file +my $compare = PGTDE->compare_results(); + +is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); + +done_testing(); diff --git a/contrib/pg_tde/t/expected/012_replication.out b/contrib/pg_tde/t/expected/012_replication.out new file mode 100644 index 0000000000000..0ea5f8470599b --- /dev/null +++ b/contrib/pg_tde/t/expected/012_replication.out @@ -0,0 +1,57 @@ +-- At primary +CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap; +INSERT INTO test_enc (x) VALUES (1), (2); +CREATE TABLE test_plain (x int PRIMARY KEY) USING heap; +INSERT INTO test_plain (x) VALUES (3), (4); +-- At replica +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('test_enc_pkey'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT * FROM test_enc ORDER BY x; + x +--- + 1 + 2 +(2 rows) + +SELECT pg_tde_is_encrypted('test_plain'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('test_plain_pkey'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT * FROM test_plain ORDER BY x; + x +--- + 3 + 4 +(2 rows) + From 8fe368b6f9eb6524852b06d4cc61e67b56066237 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 00:59:38 +0200 Subject: [PATCH 111/796] PG-1441 Do not generate relation keys in the SMGR on redo Make sure we can never generate relation keys on a streaming replica or in recovery in the SMGR code. Instead the key should always have been already generated when replaying the XLOG_TDE_ADD_RELATION_KEY record. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index a885b9b4265a9..a14a868a21577 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -243,10 +243,13 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool * * Later calls then decide to encrypt or not based on the existence of * the key. + * + * Since event triggers do not fire on the standby or in recovery we + * do not try to generate any new keys and instead trust the xlog. */ InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); - if (!key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) + if (!isRedo && !key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) key = pg_tde_create_smgr_key(&reln->smgr_rlocator); if (key) From e450170e0326b12403f916182cb359c9985bcfd6 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 17 Apr 2025 00:46:53 +0200 Subject: [PATCH 112/796] PG-1441 Do not replicate relation keys Instead of replicating relation keys we generate new ones on replay of the XLOG_TDE_ADD_RELATION_KEY record at the replica server. This means a replica and its master server will end up with different sets of relation keys making a simple binary diff impossible but that is a dubious advantage since the WAL keys will differ anyway and on on the flip-side the new code is simpler and easier to reason about. Especially since now WAL keys and relation keys are treated in a bit more similar ways. To prevent duplicate keys in the key file we skip generating and adding a key if there already is an entry in the file for the same relation. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 138 ++++++++---------- contrib/pg_tde/src/access/pg_tde_xlog.c | 4 +- .../pg_tde/src/include/access/pg_tde_tdemap.h | 5 +- contrib/pg_tde/t/012_replication.pl | 41 ++++-- 4 files changed, 88 insertions(+), 100 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 5b8189bd48615..715c9a8d1eaf8 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -121,13 +121,13 @@ static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLog static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, InternalKey *key); #ifndef FRONTEND -static InternalKey *pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type); -static InternalKey *pg_tde_create_local_key(const RelFileLocator *newrlocator, uint32 entry_type); +static InternalKey *pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator); +static InternalKey *pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator); static void pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type); static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog); +static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); @@ -137,22 +137,32 @@ InternalKey * pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator) { if (RelFileLocatorBackendIsTemp(*newrlocator)) - return pg_tde_create_local_key(&newrlocator->locator, TDE_KEY_TYPE_SMGR); + return pg_tde_create_smgr_key_temp(&newrlocator->locator); else - return pg_tde_create_key_map_entry(&newrlocator->locator, TDE_KEY_TYPE_SMGR); + return pg_tde_create_smgr_key_perm(&newrlocator->locator); +} + +static InternalKey * +pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator) +{ + InternalKey int_key; + + pg_tde_generate_internal_key(&int_key, TDE_KEY_TYPE_SMGR); + + return pg_tde_put_key_into_cache(newrlocator, &int_key); } -/* - * Generate an encrypted key for the relation and store it in the keymap file. - */ static InternalKey * -pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type) +pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) { InternalKey rel_key_data; TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); + XLogRelKey xlrec = { + .rlocator = *newrlocator, + }; - pg_tde_generate_internal_key(&rel_key_data, entry_type); + pg_tde_generate_internal_key(&rel_key_data, TDE_KEY_TYPE_SMGR); LWLockAcquire(lock_pk, LW_EXCLUSIVE); principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); @@ -164,20 +174,49 @@ pg_tde_create_key_map_entry(const RelFileLocator *newrlocator, uint32 entry_type } /* Add the encrypted key to the key map data file structure. */ - pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key, true); + pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key); LWLockRelease(lock_pk); + /* + * It is fine to write the to WAL after writing to the file since we have + * not WAL logged the SMGR CREATE event either. + */ + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); + return pg_tde_put_key_into_cache(newrlocator, &rel_key_data); } -static InternalKey * -pg_tde_create_local_key(const RelFileLocator *newrlocator, uint32 entry_type) +void +pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator) { - InternalKey int_key; + InternalKey rel_key_data; + InternalKey *old_key; + TDEPrincipalKey *principal_key; + LWLock *lock_pk = tde_lwlock_enc_keys(); - pg_tde_generate_internal_key(&int_key, entry_type); + if ((old_key = pg_tde_get_key_from_file(newrlocator, TDE_KEY_TYPE_SMGR))) + { + pfree(old_key); + LWLockRelease(lock_pk); + return; + } - return pg_tde_put_key_into_cache(newrlocator, &int_key); + pg_tde_generate_internal_key(&rel_key_data, TDE_KEY_TYPE_SMGR); + + LWLockAcquire(lock_pk, LW_EXCLUSIVE); + principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); + if (principal_key == NULL) + { + ereport(ERROR, + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); + } + + /* Add the encrypted key to the key map data file structure. */ + pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key); + LWLockRelease(lock_pk); } static void @@ -240,7 +279,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat /* * Add the encrypted key to the key map data file structure. */ - pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key, false); + pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); LWLockRelease(tde_lwlock_enc_keys()); } @@ -415,7 +454,7 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, * concurrent in place updates leading to data conflicts. */ void -pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key, bool write_xlog) +pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key) { char db_map_path[MAXPGPATH]; int map_fd; @@ -458,75 +497,12 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ /* Initialize map entry and encrypt key */ pg_tde_initialize_map_entry(&write_map_entry, principal_key, rlocator, rel_key_data); - if (write_xlog) - { - XLogRelKey xlrec; - - xlrec.mapEntry = write_map_entry; - xlrec.pkInfo = signed_key_Info; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); - } - /* Write the given entry at curr_pos; i.e. the free entry. */ pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); close(map_fd); } -/* - * Write and already encrypted entry to the key map. - * - * The caller must hold an exclusive lock on the map file to avoid - * concurrent in place updates leading to data conflicts. - */ -void -pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info) -{ - char db_map_path[MAXPGPATH]; - int map_fd; - off_t curr_pos = 0; - - pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); - - LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - - /* Open and validate file for basic correctness. */ - map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos); - - /* - * Read until we find an empty slot. Otherwise, read until end. This seems - * to be less frequent than vacuum. So let's keep this function here - * rather than overloading the vacuum process. - */ - while (1) - { - TDEMapEntry read_map_entry; - off_t prev_pos = curr_pos; - - if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) - { - curr_pos = prev_pos; - break; - } - - if (read_map_entry.flags == MAP_ENTRY_EMPTY) - { - curr_pos = prev_pos; - break; - } - } - - /* Write the given entry at curr_pos; i.e. the free entry. */ - pg_tde_write_one_map_entry(map_fd, write_map_entry, &curr_pos, db_map_path); - - close(map_fd); - - LWLockRelease(tde_lwlock_enc_keys()); -} - /* * Mark relation map entry as free and overwrite the key * diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 7e5a70fe30007..9d817036bd8ef 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -52,7 +52,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - pg_tde_write_key_map_entry_redo(&xlrec->mapEntry, &xlrec->pkInfo); + pg_tde_create_smgr_key_perm_redo(&xlrec->rlocator); } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { @@ -93,7 +93,7 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - appendStringInfo(buf, "rel: %u/%u/%u", xlrec->mapEntry.spcOid, xlrec->pkInfo.data.databaseId, xlrec->mapEntry.relNumber); + appendStringInfo(buf, "rel: %u/%u/%u", xlrec->rlocator.spcOid, xlrec->rlocator.dbOid, xlrec->rlocator.relNumber); } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index e0c40827f1173..083e0f182fd53 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -66,8 +66,7 @@ typedef struct TDEMapEntry typedef struct XLogRelKey { - TDEMapEntry mapEntry; - TDESignedPrincipalKeyInfo pkInfo; + RelFileLocator rlocator; } XLogRelKey; /* @@ -99,9 +98,9 @@ extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); +extern void pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); -extern void pg_tde_write_key_map_entry_redo(const TDEMapEntry *write_map_entry, TDESignedPrincipalKeyInfo *signed_key_info); #define PG_TDE_MAP_FILENAME "pg_tde_%d_map" diff --git a/contrib/pg_tde/t/012_replication.pl b/contrib/pg_tde/t/012_replication.pl index 884090467aafc..2de29970471f2 100644 --- a/contrib/pg_tde/t/012_replication.pl +++ b/contrib/pg_tde/t/012_replication.pl @@ -11,7 +11,8 @@ my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init(allows_streaming => 1); -$primary->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$primary->append_conf('postgresql.conf', + "shared_preload_libraries = 'pg_tde'"); $primary->start; $primary->backup('backup'); @@ -23,34 +24,46 @@ PGTDE::append_to_result_file("-- At primary"); PGTDE::psql($primary, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($primary, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');"); -PGTDE::psql($primary, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"); - -PGTDE::psql($primary, 'postgres', "CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap;"); -PGTDE::psql($primary, 'postgres', "INSERT INTO test_enc (x) VALUES (1), (2);"); - -PGTDE::psql($primary, 'postgres', "CREATE TABLE test_plain (x int PRIMARY KEY) USING heap;"); -PGTDE::psql($primary, 'postgres', "INSERT INTO test_plain (x) VALUES (3), (4);"); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" +); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" +); + +PGTDE::psql($primary, 'postgres', + "CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::psql($primary, 'postgres', + "INSERT INTO test_enc (x) VALUES (1), (2);"); + +PGTDE::psql($primary, 'postgres', + "CREATE TABLE test_plain (x int PRIMARY KEY) USING heap;"); +PGTDE::psql($primary, 'postgres', + "INSERT INTO test_plain (x) VALUES (3), (4);"); $primary->wait_for_catchup('replica'); PGTDE::append_to_result_file("-- At replica"); PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_enc_pkey');"); +PGTDE::psql($replica, 'postgres', + "SELECT pg_tde_is_encrypted('test_enc_pkey');"); PGTDE::psql($replica, 'postgres', "SELECT * FROM test_enc ORDER BY x;"); -PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_plain');"); -PGTDE::psql($replica, 'postgres', "SELECT pg_tde_is_encrypted('test_plain_pkey');"); +PGTDE::psql($replica, 'postgres', + "SELECT pg_tde_is_encrypted('test_plain');"); +PGTDE::psql($replica, 'postgres', + "SELECT pg_tde_is_encrypted('test_plain_pkey');"); PGTDE::psql($replica, 'postgres', "SELECT * FROM test_plain ORDER BY x;"); $replica->stop; - $primary->stop; # Compare the expected and out file my $compare = PGTDE->compare_results(); -is($compare, 0, "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); done_testing(); From b7d2f1599f62a0e5f79188d3862c01575b2ff40d Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 23 Apr 2025 17:34:25 +0200 Subject: [PATCH 113/796] Fix typo --- src/bin/pg_checksums/pg_checksums.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index e1b85132eef49..833b07a970971 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -138,7 +138,7 @@ pg_tde_init(const char *datadir) static bool is_pg_tde_encypted(Oid spcOid, Oid dbOid, RelFileNumber relNumber) { - RelFileLocator locator = {.spcOid = spcOid, dbOid = dbOid,.relNumber = relNumber}; + RelFileLocator locator = {.spcOid = spcOid, .dbOid = dbOid,.relNumber = relNumber}; RelFileLocatorBackend rlocator = {.locator = locator,.backend = INVALID_PROC_NUMBER}; return GetSMGRRelationKey(rlocator) != NULL; From e735727c8e6adf1dae4035c99e9ffaf1ece2be05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 23 Apr 2025 22:03:44 +0200 Subject: [PATCH 114/796] Do not encourage root token use for vault We should probably not encourage anyone to use a root token for vault keyring providers. We use "secret_token" elsewhere, so use that consistently. --- contrib/pg_tde/documentation/docs/multi-tenant-setup.md | 4 ++-- contrib/pg_tde/documentation/docs/setup.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md index 2cb9d7698000f..5a6558208da43 100644 --- a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/multi-tenant-setup.md @@ -83,14 +83,14 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','root_token','url','mount','ca_path'); + SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys - * `root_token` is an access token with read and write access to the above mount point + * `secret_token` is an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification :material-information: Warning: This example is for testing purposes only: diff --git a/contrib/pg_tde/documentation/docs/setup.md b/contrib/pg_tde/documentation/docs/setup.md index 814b8791a7c79..9e8dbcb51b019 100644 --- a/contrib/pg_tde/documentation/docs/setup.md +++ b/contrib/pg_tde/documentation/docs/setup.md @@ -77,14 +77,14 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo The Vault server setup is out of scope of this document. ``` - SELECT pg_tde_add_global_key_provider_vault_v2('provider-name','root_token','url','mount','ca_path'); + SELECT pg_tde_add_global_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys - * `root_token` is an access token with read and write access to the above mount point + * `secret_token` is an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification :material-information: Warning: This example is for testing purposes only: From 1fa786fa2924468a15567d1bfd127afdb269f7a9 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Tue, 15 Apr 2025 10:53:59 +0300 Subject: [PATCH 115/796] Don't rewrite _map files on the save_principal_key redo We create a new WAL key during the extension init, which happens before the redo. This means that in case of a crash, pg_tde_save_principal_key_redo was rewriting a WAL _map file and destroying a newly created key. Since we emit an XLog record after the key was successfully written to the file (the file was created), we can safely assume that we should not change the file if it exists. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 715c9a8d1eaf8..7994b5cfaf105 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -309,7 +309,7 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, true, &curr_pos); + map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos); close(map_fd); LWLockRelease(tde_lwlock_enc_keys()); From c8419b81acc80be89585b181b6128dfc87457bcb Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 23 Apr 2025 19:49:12 +0300 Subject: [PATCH 116/796] Add crash recovery tests Tests to trigger redo routines after the server crash. It mostly checks invariants when different redo functions might rewrite WAL keys created on the init stage. For PG-1539, PG-1541, PG-1468, PG-1413 --- contrib/pg_tde/meson.build | 1 + contrib/pg_tde/t/013_crash_recovery.pl | 94 +++++++++++++++++++ .../pg_tde/t/expected/013_crash_recovery.out | 77 +++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 contrib/pg_tde/t/013_crash_recovery.pl create mode 100644 contrib/pg_tde/t/expected/013_crash_recovery.out diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 6cf1a43994629..200894ece1c0f 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -114,6 +114,7 @@ tap_tests = [ 't/010_change_key_provider.pl', 't/011_unlogged_tables.pl', 't/012_replication.pl', + 't/013_crash_recovery.pl', ] tests += { diff --git a/contrib/pg_tde/t/013_crash_recovery.pl b/contrib/pg_tde/t/013_crash_recovery.pl new file mode 100644 index 0000000000000..7163cfc9ead35 --- /dev/null +++ b/contrib/pg_tde/t/013_crash_recovery.pl @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use Test::More; +use lib 't'; +use pgtde; + +PGTDE::setup_files_dir(basename($0)); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', q{ +checkpoint_timeout = 1h +shared_preload_libraries = 'pg_tde' +}); +$node->start; + +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'global_keyring');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('db_key', 'db_keyring');" +); + +PGTDE::psql($node, 'postgres', + "CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::psql($node, 'postgres', "INSERT INTO test_enc (x) VALUES (1), (2);"); + +PGTDE::psql($node, 'postgres', + "CREATE TABLE test_plain (x int PRIMARY KEY) USING heap;"); +PGTDE::psql($node, 'postgres', "INSERT INTO test_plain (x) VALUES (3), (4);"); + +PGTDE::psql($node, 'postgres', "ALTER SYSTEM SET pg_tde.wal_encrypt = 'on';"); + +PGTDE::append_to_result_file("-- kill -9"); +$node->kill9; + +PGTDE::append_to_result_file("-- server start"); +$node->start; + +PGTDE::append_to_result_file("-- rotate wal key"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring', true);" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring');" +); +PGTDE::psql($node, 'postgres', "INSERT INTO test_enc (x) VALUES (3), (4);"); +PGTDE::append_to_result_file("-- kill -9"); +$node->kill9; +PGTDE::append_to_result_file("-- server start"); +PGTDE::append_to_result_file( + "-- check that pg_tde_save_principal_key_redo hasn't destroyed a WAL key created during the server start" +); +$node->start; + +PGTDE::append_to_result_file("-- rotate wal key"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring', true);" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring');" +); +PGTDE::psql($node, 'postgres', "INSERT INTO test_enc (x) VALUES (5), (6);"); +PGTDE::append_to_result_file("-- kill -9"); +$node->kill9; +PGTDE::append_to_result_file("-- server start"); +PGTDE::append_to_result_file( + "-- check that the key rotation hasn't destroyed a WAL key created during the server start" +); +$node->start; + +PGTDE::psql($node, 'postgres', "TABLE test_enc;"); + +$node->stop; + +# Compare the expected and out file +my $compare = PGTDE->compare_results(); + +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); + +done_testing(); diff --git a/contrib/pg_tde/t/expected/013_crash_recovery.out b/contrib/pg_tde/t/expected/013_crash_recovery.out new file mode 100644 index 0000000000000..3ec79d59e35bf --- /dev/null +++ b/contrib/pg_tde/t/expected/013_crash_recovery.out @@ -0,0 +1,77 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per'); + pg_tde_add_global_key_provider_file +------------------------------------- + -1 +(1 row) + +SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'global_keyring'); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('db_key', 'db_keyring'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +CREATE TABLE test_enc (x int PRIMARY KEY) USING tde_heap; +INSERT INTO test_enc (x) VALUES (1), (2); +CREATE TABLE test_plain (x int PRIMARY KEY) USING heap; +INSERT INTO test_plain (x) VALUES (3), (4); +ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; +-- kill -9 +-- server start +-- rotate wal key +SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring', true); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +INSERT INTO test_enc (x) VALUES (3), (4); +-- kill -9 +-- server start +-- check that pg_tde_save_principal_key_redo hasn't destroyed a WAL key created during the server start +-- rotate wal key +SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring', true); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +INSERT INTO test_enc (x) VALUES (5), (6); +-- kill -9 +-- server start +-- check that the key rotation hasn't destroyed a WAL key created during the server start +TABLE test_enc; + x +--- + 1 + 2 + 3 + 4 + 5 + 6 +(6 rows) + From 57ac8c89183b5edcd2b0680cc122723f403dd399 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 24 Apr 2025 18:18:48 +0200 Subject: [PATCH 117/796] Fix typo in out param of pg_tde_*_key_info() --- contrib/pg_tde/pg_tde--1.0-rc.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index c717c30dda579..fc903e27cbd76 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -464,7 +464,7 @@ CREATE FUNCTION pg_tde_key_info() RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, - key_createion_time timestamp with time zone) + key_creation_time timestamp with time zone) LANGUAGE C AS 'MODULE_PATHNAME'; @@ -472,7 +472,7 @@ CREATE FUNCTION pg_tde_server_key_info() RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, - key_createion_time timestamp with time zone) + key_creation_time timestamp with time zone) LANGUAGE C AS 'MODULE_PATHNAME'; @@ -480,7 +480,7 @@ CREATE FUNCTION pg_tde_default_key_info() RETURNS TABLE ( key_name text, key_provider_name text, key_provider_id integer, - key_createion_time timestamp with time zone) + key_creation_time timestamp with time zone) LANGUAGE C AS 'MODULE_PATHNAME'; From 6b508a0f74ea756a1b3c1eefc174467d501a05da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 25 Apr 2025 13:21:34 +0200 Subject: [PATCH 118/796] Make 013_crash_recovery.pl work on repeated runs If the keyring from a previous run is still present, the test fails. --- contrib/pg_tde/t/013_crash_recovery.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/pg_tde/t/013_crash_recovery.pl b/contrib/pg_tde/t/013_crash_recovery.pl index 7163cfc9ead35..1b91827cc4c4b 100644 --- a/contrib/pg_tde/t/013_crash_recovery.pl +++ b/contrib/pg_tde/t/013_crash_recovery.pl @@ -7,6 +7,9 @@ use lib 't'; use pgtde; +# ensure we start with a clean key provider file +unlink('/tmp/crash_recovery.per'); + PGTDE::setup_files_dir(basename($0)); my $node = PostgreSQL::Test::Cluster->new('main'); From 16ba8eeeeb0b8603afcca2938d5ce0eac36130f6 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 24 Apr 2025 19:41:43 +0300 Subject: [PATCH 119/796] Fix release of the not-held lock Fixes PG-1573 --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 1 - contrib/pg_tde/t/013_crash_recovery.pl | 14 ++++++++++++-- contrib/pg_tde/t/expected/013_crash_recovery.out | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 7994b5cfaf105..91fa23a53b82a 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -199,7 +199,6 @@ pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator) if ((old_key = pg_tde_get_key_from_file(newrlocator, TDE_KEY_TYPE_SMGR))) { pfree(old_key); - LWLockRelease(lock_pk); return; } diff --git a/contrib/pg_tde/t/013_crash_recovery.pl b/contrib/pg_tde/t/013_crash_recovery.pl index 1b91827cc4c4b..ffdbf2ab062e6 100644 --- a/contrib/pg_tde/t/013_crash_recovery.pl +++ b/contrib/pg_tde/t/013_crash_recovery.pl @@ -53,7 +53,7 @@ PGTDE::append_to_result_file("-- rotate wal key"); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring', true);" + "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring');" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring');" @@ -69,7 +69,7 @@ PGTDE::append_to_result_file("-- rotate wal key"); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring', true);" + "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring');" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring');" @@ -85,6 +85,16 @@ PGTDE::psql($node, 'postgres', "TABLE test_enc;"); +PGTDE::psql($node, 'postgres', + "CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::append_to_result_file("-- kill -9"); +$node->kill9; +PGTDE::append_to_result_file("-- server start"); +PGTDE::append_to_result_file( + "-- check redo of the smgr internal key creation when the key is on disk" +); +$node->start; + $node->stop; # Compare the expected and out file diff --git a/contrib/pg_tde/t/expected/013_crash_recovery.out b/contrib/pg_tde/t/expected/013_crash_recovery.out index 3ec79d59e35bf..75cb4fa3bed81 100644 --- a/contrib/pg_tde/t/expected/013_crash_recovery.out +++ b/contrib/pg_tde/t/expected/013_crash_recovery.out @@ -31,7 +31,7 @@ ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; -- kill -9 -- server start -- rotate wal key -SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring', true); +SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -48,7 +48,7 @@ INSERT INTO test_enc (x) VALUES (3), (4); -- server start -- check that pg_tde_save_principal_key_redo hasn't destroyed a WAL key created during the server start -- rotate wal key -SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring', true); +SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -75,3 +75,7 @@ TABLE test_enc; 6 (6 rows) +CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap; +-- kill -9 +-- server start +-- check redo of the smgr internal key creation when the key is on disk From 3251aef9e759a4fc49a2ab499c7e3617bcac8afd Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 13:45:15 +0200 Subject: [PATCH 120/796] Rename the files under $PGDATA/pg_tde The name pg_tde__keyring was confusing to users due to making it sound like it would contain keys. And the name pg_tde__map did not tell a user anything. The new names are _providers for the key providers and _keys for the relation/WAL keys. While changing the suffixes to be more descriptive I also dropped the pg_tde_ prefix since it is just noise when they all are in the pg_tde directory. --- contrib/pg_tde/src/catalog/tde_keyring.c | 2 +- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b063bc56ad91c..c391d7c6d1576 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -47,7 +47,7 @@ typedef enum ProviderScanType PROVIDER_SCAN_ALL } ProviderScanType; -#define PG_TDE_KEYRING_FILENAME "pg_tde_%d_keyring" +#define PG_TDE_KEYRING_FILENAME "%d_providers" #define FILE_KEYRING_TYPE "file" #define VAULTV2_KEYRING_TYPE "vault-v2" diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 083e0f182fd53..81b11fc399a4a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -102,7 +102,7 @@ extern void pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); -#define PG_TDE_MAP_FILENAME "pg_tde_%d_map" +#define PG_TDE_MAP_FILENAME "%d_keys" static inline void pg_tde_set_db_file_path(Oid dbOid, char *path) From f758cc70bb5ec2ac14621184ee8ec2c5292e45da Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 12:16:17 +0200 Subject: [PATCH 121/796] Make local variable no longer static Presumably this variable used to actually be used as a static variable at some point in time but that is no longer the case. --- contrib/pg_tde/src/catalog/tde_keyring.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index c391d7c6d1576..42f66ce220f49 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -979,11 +979,10 @@ GenericKeyring * GetKeyProviderByName(const char *provider_name, Oid dbOid) { GenericKeyring *keyring = NULL; - #ifndef FRONTEND - static List *providers; + List *providers; #else - static SimplePtrList *providers; + SimplePtrList *providers; #endif providers = scan_key_provider_file(PROVIDER_SCAN_BY_NAME, (void *) provider_name, dbOid); From e7fa26f8d3a94eb461501ebc60436d1f1cda7388 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 12:24:00 +0200 Subject: [PATCH 122/796] Consistently use HeapTupleIsValid() HeapTupleIsValid() is actually just a null check but PostgreSQL's codebase almost always uses this macro and we had a confusion where we both had a null check and called this macro so we at least should pick just one of the two ways to write it. And here I picked the most commonly used way in the PostgreSQL codebase. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 4a3731ff6e381..8fdc3b5700ad1 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -979,18 +979,12 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) /* We have to verify that it isn't currently used by any database */ - rel = table_open(DatabaseRelationId, AccessShareLock); scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); - while ((tuple = systable_getnext(scan)) != NULL) + while (HeapTupleIsValid(tuple = systable_getnext(scan))) { - if (!HeapTupleIsValid(tuple)) - { - break; - } - dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); @@ -1072,7 +1066,6 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci pg_tde_rotate_default_key_for_database(principal_key, newKey); } - /* * Take row exclusive lock, as we do not want anybody to create/drop a * database in parallel. If it happens, its not the end of the world, but @@ -1082,17 +1075,15 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); - while ((tuple = systable_getnext(scan)) != NULL) + while (HeapTupleIsValid(tuple = systable_getnext(scan))) { dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(oldKey, principal_key)) { pg_tde_rotate_default_key_for_database(principal_key, newKey); } - } systable_endscan(scan); From b639210ef40b2463ca395625f33108ce0ea6ad0f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 14:09:21 +0200 Subject: [PATCH 123/796] Remove duplicate pg_regress alternate expected file We apparently had the same alternate file twice in our repo. --- src/test/regress/expected/create_index_2.out | 3000 ------------------ 1 file changed, 3000 deletions(-) delete mode 100644 src/test/regress/expected/create_index_2.out diff --git a/src/test/regress/expected/create_index_2.out b/src/test/regress/expected/create_index_2.out deleted file mode 100644 index f29407adc69c9..0000000000000 --- a/src/test/regress/expected/create_index_2.out +++ /dev/null @@ -1,3000 +0,0 @@ --- --- CREATE_INDEX --- Create ancillary data structures (i.e. indices) --- --- directory paths are passed to us in environment variables -\getenv abs_srcdir PG_ABS_SRCDIR --- --- BTREE --- -CREATE INDEX onek_unique1 ON onek USING btree(unique1 int4_ops); -CREATE INDEX IF NOT EXISTS onek_unique1 ON onek USING btree(unique1 int4_ops); -NOTICE: relation "onek_unique1" already exists, skipping -CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_ops); -ERROR: syntax error at or near "ON" -LINE 1: CREATE INDEX IF NOT EXISTS ON onek USING btree(unique1 int4_... - ^ -CREATE INDEX onek_unique2 ON onek USING btree(unique2 int4_ops); -CREATE INDEX onek_hundred ON onek USING btree(hundred int4_ops); -CREATE INDEX onek_stringu1 ON onek USING btree(stringu1 name_ops); -CREATE INDEX tenk1_unique1 ON tenk1 USING btree(unique1 int4_ops); -CREATE INDEX tenk1_unique2 ON tenk1 USING btree(unique2 int4_ops); -CREATE INDEX tenk1_hundred ON tenk1 USING btree(hundred int4_ops); -CREATE INDEX tenk1_thous_tenthous ON tenk1 (thousand, tenthous); -CREATE INDEX tenk2_unique1 ON tenk2 USING btree(unique1 int4_ops); -CREATE INDEX tenk2_unique2 ON tenk2 USING btree(unique2 int4_ops); -CREATE INDEX tenk2_hundred ON tenk2 USING btree(hundred int4_ops); -CREATE INDEX rix ON road USING btree (name text_ops); -CREATE INDEX iix ON ihighway USING btree (name text_ops); -CREATE INDEX six ON shighway USING btree (name text_ops); --- test comments -COMMENT ON INDEX six_wrong IS 'bad index'; -ERROR: relation "six_wrong" does not exist -COMMENT ON INDEX six IS 'good index'; -COMMENT ON INDEX six IS NULL; --- --- BTREE partial indices --- -CREATE INDEX onek2_u1_prtl ON onek2 USING btree(unique1 int4_ops) - where unique1 < 20 or unique1 > 980; -CREATE INDEX onek2_u2_prtl ON onek2 USING btree(unique2 int4_ops) - where stringu1 < 'B'; -CREATE INDEX onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops) - where onek2.stringu1 >= 'J' and onek2.stringu1 < 'K'; --- --- GiST (rtree-equivalent opclasses only) --- -CREATE TABLE slow_emp4000 ( - home_base box -); -CREATE TABLE fast_emp4000 ( - home_base box -); -\set filename :abs_srcdir '/data/rect.data' -COPY slow_emp4000 FROM :'filename'; -INSERT INTO fast_emp4000 SELECT * FROM slow_emp4000; -ANALYZE slow_emp4000; -ANALYZE fast_emp4000; -CREATE INDEX grect2ind ON fast_emp4000 USING gist (home_base); --- we want to work with a point_tbl that includes a null -CREATE TEMP TABLE point_tbl AS SELECT * FROM public.point_tbl; -INSERT INTO POINT_TBL(f1) VALUES (NULL); -CREATE INDEX gpointind ON point_tbl USING gist (f1); -CREATE TEMP TABLE gpolygon_tbl AS - SELECT polygon(home_base) AS f1 FROM slow_emp4000; -INSERT INTO gpolygon_tbl VALUES ( '(1000,0,0,1000)' ); -INSERT INTO gpolygon_tbl VALUES ( '(0,1000,1000,1000)' ); -CREATE TEMP TABLE gcircle_tbl AS - SELECT circle(home_base) AS f1 FROM slow_emp4000; -CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1); -CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1); --- --- Test GiST indexes --- --- get non-indexed results for comparison purposes -SET enable_seqscan = ON; -SET enable_indexscan = OFF; -SET enable_bitmapscan = OFF; -SELECT * FROM fast_emp4000 - WHERE home_base <@ '(200,200),(2000,1000)'::box - ORDER BY (home_base[0])[0]; - home_base ------------------------ - (337,455),(240,359) - (1444,403),(1346,344) -(2 rows) - -SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; - count -------- - 2 -(1 row) - -SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; - count -------- - 278 -(1 row) - -SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon; - count -------- - 2 -(1 row) - -SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle; - count -------- - 2 -(1 row) - -SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)'; - count -------- - 3 -(1 row) - -SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1; - count -------- - 3 -(1 row) - -SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; - count -------- - 5 -(1 row) - -SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>'; - count -------- - 1 -(1 row) - -SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)'; - count -------- - 3 -(1 row) - -SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)'; - count -------- - 4 -(1 row) - -SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)'; - count -------- - 1 -(1 row) - -SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)'; - count -------- - 5 -(1 row) - -SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)'; - count -------- - 1 -(1 row) - -SELECT * FROM point_tbl ORDER BY f1 <-> '0,1'; - f1 -------------------- - (0,0) - (1e-300,-1e-300) - (-3,4) - (-10,0) - (10,10) - (-5,-12) - (5.1,34.5) - (Infinity,1e+300) - (1e+300,Infinity) - (NaN,NaN) - -(11 rows) - -SELECT * FROM point_tbl WHERE f1 IS NULL; - f1 ----- - -(1 row) - -SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; - f1 -------------------- - (0,0) - (1e-300,-1e-300) - (-3,4) - (-10,0) - (10,10) - (-5,-12) - (5.1,34.5) - (1e+300,Infinity) - (Infinity,1e+300) - (NaN,NaN) -(10 rows) - -SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - f1 ------------------- - (0,0) - (1e-300,-1e-300) - (-3,4) - (-10,0) - (10,10) -(5 rows) - -SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10; - f1 -------------------------------------------------- - ((240,359),(240,455),(337,455),(337,359)) - ((662,163),(662,187),(759,187),(759,163)) - ((1000,0),(0,1000)) - ((0,1000),(1000,1000)) - ((1346,344),(1346,403),(1444,403),(1444,344)) - ((278,1409),(278,1457),(369,1457),(369,1409)) - ((907,1156),(907,1201),(948,1201),(948,1156)) - ((1517,971),(1517,1043),(1594,1043),(1594,971)) - ((175,1820),(175,1850),(259,1850),(259,1820)) - ((2424,81),(2424,160),(2424,160),(2424,81)) -(10 rows) - -SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10; - circle_center | radius -----------------+-------- - (288.5,407) | 68 - (710.5,175) | 50 - (323.5,1433) | 51 - (927.5,1178.5) | 30 - (1395,373.5) | 57 - (1555.5,1007) | 53 - (217,1835) | 45 - (489,2421.5) | 22 - (2424,120.5) | 40 - (751.5,2655) | 20 -(10 rows) - --- Now check the results from plain indexscan -SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; -EXPLAIN (COSTS OFF) -SELECT * FROM fast_emp4000 - WHERE home_base <@ '(200,200),(2000,1000)'::box - ORDER BY (home_base[0])[0]; - QUERY PLAN ------------------------------------------------------------------ - Sort - Sort Key: ((home_base[0])[0]) - -> Index Only Scan using grect2ind on fast_emp4000 - Index Cond: (home_base <@ '(2000,1000),(200,200)'::box) -(4 rows) - -SELECT * FROM fast_emp4000 - WHERE home_base <@ '(200,200),(2000,1000)'::box - ORDER BY (home_base[0])[0]; - home_base ------------------------ - (337,455),(240,359) - (1444,403),(1346,344) -(2 rows) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; - QUERY PLAN -------------------------------------------------------------- - Aggregate - -> Index Only Scan using grect2ind on fast_emp4000 - Index Cond: (home_base && '(1000,1000),(0,0)'::box) -(3 rows) - -SELECT count(*) FROM fast_emp4000 WHERE home_base && '(1000,1000,0,0)'::box; - count -------- - 2 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; - QUERY PLAN -------------------------------------------------------- - Aggregate - -> Index Only Scan using grect2ind on fast_emp4000 - Index Cond: (home_base IS NULL) -(3 rows) - -SELECT count(*) FROM fast_emp4000 WHERE home_base IS NULL; - count -------- - 278 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon; - QUERY PLAN ------------------------------------------------------------- - Aggregate - -> Index Scan using ggpolygonind on gpolygon_tbl - Index Cond: (f1 && '((1000,1000),(0,0))'::polygon) -(3 rows) - -SELECT count(*) FROM gpolygon_tbl WHERE f1 && '(1000,1000,0,0)'::polygon; - count -------- - 2 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle; - QUERY PLAN -------------------------------------------------------- - Aggregate - -> Index Scan using ggcircleind on gcircle_tbl - Index Cond: (f1 && '<(500,500),500>'::circle) -(3 rows) - -SELECT count(*) FROM gcircle_tbl WHERE f1 && '<(500,500),500>'::circle; - count -------- - 2 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)'; - QUERY PLAN ----------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl - Index Cond: (f1 <@ '(100,100),(0,0)'::box) -(3 rows) - -SELECT count(*) FROM point_tbl WHERE f1 <@ box '(0,0,100,100)'; - count -------- - 3 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1; - QUERY PLAN ----------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl - Index Cond: (f1 <@ '(100,100),(0,0)'::box) -(3 rows) - -SELECT count(*) FROM point_tbl WHERE box '(0,0,100,100)' @> f1; - count -------- - 3 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; - QUERY PLAN ----------------------------------------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl - Index Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon) -(3 rows) - -SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; - count -------- - 4 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>'; - QUERY PLAN ----------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl - Index Cond: (f1 <@ '<(50,50),50>'::circle) -(3 rows) - -SELECT count(*) FROM point_tbl WHERE f1 <@ circle '<(50,50),50>'; - count -------- - 1 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)'; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl p - Index Cond: (f1 << '(0,0)'::point) -(3 rows) - -SELECT count(*) FROM point_tbl p WHERE p.f1 << '(0.0, 0.0)'; - count -------- - 3 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)'; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl p - Index Cond: (f1 >> '(0,0)'::point) -(3 rows) - -SELECT count(*) FROM point_tbl p WHERE p.f1 >> '(0.0, 0.0)'; - count -------- - 4 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)'; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl p - Index Cond: (f1 <<| '(0,0)'::point) -(3 rows) - -SELECT count(*) FROM point_tbl p WHERE p.f1 <<| '(0.0, 0.0)'; - count -------- - 1 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)'; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl p - Index Cond: (f1 |>> '(0,0)'::point) -(3 rows) - -SELECT count(*) FROM point_tbl p WHERE p.f1 |>> '(0.0, 0.0)'; - count -------- - 5 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)'; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Index Only Scan using gpointind on point_tbl p - Index Cond: (f1 ~= '(-5,-12)'::point) -(3 rows) - -SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)'; - count -------- - 1 -(1 row) - -EXPLAIN (COSTS OFF) -SELECT * FROM point_tbl ORDER BY f1 <-> '0,1'; - QUERY PLAN ----------------------------------------------- - Index Only Scan using gpointind on point_tbl - Order By: (f1 <-> '(0,1)'::point) -(2 rows) - -SELECT * FROM point_tbl ORDER BY f1 <-> '0,1'; - f1 -------------------- - (1e-300,-1e-300) - (0,0) - (-3,4) - (-10,0) - (10,10) - (-5,-12) - (5.1,34.5) - (Infinity,1e+300) - (1e+300,Infinity) - (NaN,NaN) - -(11 rows) - -EXPLAIN (COSTS OFF) -SELECT * FROM point_tbl WHERE f1 IS NULL; - QUERY PLAN ----------------------------------------------- - Index Only Scan using gpointind on point_tbl - Index Cond: (f1 IS NULL) -(2 rows) - -SELECT * FROM point_tbl WHERE f1 IS NULL; - f1 ----- - -(1 row) - -EXPLAIN (COSTS OFF) -SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; - QUERY PLAN ----------------------------------------------- - Index Only Scan using gpointind on point_tbl - Index Cond: (f1 IS NOT NULL) - Order By: (f1 <-> '(0,1)'::point) -(3 rows) - -SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1'; - f1 -------------------- - (1e-300,-1e-300) - (0,0) - (-3,4) - (-10,0) - (10,10) - (-5,-12) - (5.1,34.5) - (Infinity,1e+300) - (1e+300,Infinity) - (NaN,NaN) -(10 rows) - -EXPLAIN (COSTS OFF) -SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - QUERY PLAN ------------------------------------------------- - Index Only Scan using gpointind on point_tbl - Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) - Order By: (f1 <-> '(0,1)'::point) -(3 rows) - -SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - f1 ------------------- - (1e-300,-1e-300) - (0,0) - (-3,4) - (-10,0) - (10,10) -(5 rows) - -EXPLAIN (COSTS OFF) -SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10; - QUERY PLAN ------------------------------------------------------ - Limit - -> Index Scan using ggpolygonind on gpolygon_tbl - Order By: (f1 <-> '(0,0)'::point) -(3 rows) - -SELECT * FROM gpolygon_tbl ORDER BY f1 <-> '(0,0)'::point LIMIT 10; - f1 -------------------------------------------------- - ((240,359),(240,455),(337,455),(337,359)) - ((662,163),(662,187),(759,187),(759,163)) - ((1000,0),(0,1000)) - ((0,1000),(1000,1000)) - ((1346,344),(1346,403),(1444,403),(1444,344)) - ((278,1409),(278,1457),(369,1457),(369,1409)) - ((907,1156),(907,1201),(948,1201),(948,1156)) - ((1517,971),(1517,1043),(1594,1043),(1594,971)) - ((175,1820),(175,1850),(259,1850),(259,1820)) - ((2424,81),(2424,160),(2424,160),(2424,81)) -(10 rows) - -EXPLAIN (COSTS OFF) -SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10; - QUERY PLAN ---------------------------------------------------- - Limit - -> Index Scan using ggcircleind on gcircle_tbl - Order By: (f1 <-> '(200,300)'::point) -(3 rows) - -SELECT circle_center(f1), round(radius(f1)) as radius FROM gcircle_tbl ORDER BY f1 <-> '(200,300)'::point LIMIT 10; - circle_center | radius -----------------+-------- - (288.5,407) | 68 - (710.5,175) | 50 - (323.5,1433) | 51 - (927.5,1178.5) | 30 - (1395,373.5) | 57 - (1555.5,1007) | 53 - (217,1835) | 45 - (489,2421.5) | 22 - (2424,120.5) | 40 - (751.5,2655) | 20 -(10 rows) - -EXPLAIN (COSTS OFF) -SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x; - QUERY PLAN --------------------------------------------------------------------------------------------- - Function Scan on generate_series x - SubPlan 1 - -> Limit - -> Index Scan using ggpolygonind on gpolygon_tbl - Order By: (f1 <-> point((x.x)::double precision, (x.x)::double precision)) -(5 rows) - -SELECT point(x,x), (SELECT f1 FROM gpolygon_tbl ORDER BY f1 <-> point(x,x) LIMIT 1) as c FROM generate_series(0,10,1) x; - point | c ----------+------------------------------------------- - (0,0) | ((240,359),(240,455),(337,455),(337,359)) - (1,1) | ((240,359),(240,455),(337,455),(337,359)) - (2,2) | ((240,359),(240,455),(337,455),(337,359)) - (3,3) | ((240,359),(240,455),(337,455),(337,359)) - (4,4) | ((240,359),(240,455),(337,455),(337,359)) - (5,5) | ((240,359),(240,455),(337,455),(337,359)) - (6,6) | ((240,359),(240,455),(337,455),(337,359)) - (7,7) | ((240,359),(240,455),(337,455),(337,359)) - (8,8) | ((240,359),(240,455),(337,455),(337,359)) - (9,9) | ((240,359),(240,455),(337,455),(337,359)) - (10,10) | ((240,359),(240,455),(337,455),(337,359)) -(11 rows) - --- Now check the results from bitmap indexscan -SET enable_seqscan = OFF; -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; -EXPLAIN (COSTS OFF) -SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - QUERY PLAN ------------------------------------------------------------- - Sort - Sort Key: ((f1 <-> '(0,1)'::point)) - -> Bitmap Heap Scan on point_tbl - Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box) - -> Bitmap Index Scan on gpointind - Index Cond: (f1 <@ '(10,10),(-10,-10)'::box) -(6 rows) - -SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1'; - f1 ------------------- - (0,0) - (1e-300,-1e-300) - (-3,4) - (-10,0) - (10,10) -(5 rows) - -RESET enable_seqscan; -RESET enable_indexscan; -RESET enable_bitmapscan; --- --- GIN over int[] and text[] --- --- Note: GIN currently supports only bitmap scans, not plain indexscans --- -CREATE TABLE array_index_op_test ( - seqno int4, - i int4[], - t text[] -); -\set filename :abs_srcdir '/data/array.data' -COPY array_index_op_test FROM :'filename'; -ANALYZE array_index_op_test; -SELECT * FROM array_index_op_test WHERE i = '{NULL}' ORDER BY seqno; - seqno | i | t --------+--------+-------- - 102 | {NULL} | {NULL} -(1 row) - -SELECT * FROM array_index_op_test WHERE i @> '{NULL}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_index_op_test WHERE i && '{NULL}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_index_op_test WHERE i <@ '{NULL}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SET enable_seqscan = OFF; -SET enable_indexscan = OFF; -SET enable_bitmapscan = ON; -CREATE INDEX intarrayidx ON array_index_op_test USING gin (i); -explain (costs off) -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; - QUERY PLAN ----------------------------------------------------- - Sort - Sort Key: seqno - -> Bitmap Heap Scan on array_index_op_test - Recheck Cond: (i @> '{32}'::integer[]) - -> Bitmap Index Scan on intarrayidx - Index Cond: (i @> '{32}'::integer[]) -(6 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_index_op_test WHERE i && '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(3 rows) - -SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(11 rows) - -SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; - seqno | i | t --------+---------------+---------------------------------------------------------------------------------------------------------------------------- - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 101 | {} | {} -(4 rows) - -SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; - seqno | i | t --------+---------+----------------------------------------------------------------------------------------------------------------- - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} -(1 row) - -SELECT * FROM array_index_op_test WHERE i = '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_index_op_test WHERE i @> '{}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} - 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} - 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} - 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} - 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} - 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} - 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} - 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} - 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} - 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 16 | {14,63,85,11} | {AAAAAA66777} - 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} - 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} - 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} - 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} - 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} - 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} - 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} - 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} - 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} - 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} - 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} - 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} - 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} - 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} - 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} - 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} - 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} - 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} - 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} - 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} - 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} - 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} - 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} - 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} - 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} - 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} - 53 | {38,17} | {AAAAAAAAAAA21658} - 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} - 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} - 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} - 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} - 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} - 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} - 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} - 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} - 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} - 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} - 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} - 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} - 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} - 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} - 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} - 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} - 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} - 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} - 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} - 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} - 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} - 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} - 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} - 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} - 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} - 101 | {} | {} - 102 | {NULL} | {NULL} -(102 rows) - -SELECT * FROM array_index_op_test WHERE i && '{}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_index_op_test WHERE i <@ '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -CREATE INDEX textarrayidx ON array_index_op_test USING gin (t); -explain (costs off) -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; - QUERY PLAN ------------------------------------------------------------- - Sort - Sort Key: seqno - -> Bitmap Heap Scan on array_index_op_test - Recheck Cond: (t @> '{AAAAAAAA72908}'::text[]) - -> Bitmap Index Scan on textarrayidx - Index Cond: (t @> '{AAAAAAAA72908}'::text[]) -(6 rows) - -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(4 rows) - -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(4 rows) - -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------------------+-------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(3 rows) - -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------------------+-------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(3 rows) - -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------+-------------------------------------------------------------------- - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(1 row) - -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(6 rows) - -SELECT * FROM array_index_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno; - seqno | i | t --------+--------------------+----------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 101 | {} | {} -(3 rows) - -SELECT * FROM array_index_op_test WHERE t = '{AAAAAAAAAA646,A87088}' ORDER BY seqno; - seqno | i | t --------+------------+------------------------ - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(1 row) - -SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_index_op_test WHERE t @> '{}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} - 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} - 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} - 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} - 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} - 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} - 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} - 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} - 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} - 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 16 | {14,63,85,11} | {AAAAAA66777} - 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} - 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} - 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} - 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} - 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} - 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} - 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} - 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} - 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} - 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} - 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} - 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} - 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} - 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} - 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} - 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} - 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} - 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} - 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} - 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} - 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} - 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} - 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} - 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} - 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} - 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} - 53 | {38,17} | {AAAAAAAAAAA21658} - 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} - 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} - 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} - 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} - 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} - 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} - 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} - 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} - 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} - 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} - 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} - 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} - 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} - 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} - 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} - 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} - 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} - 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} - 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} - 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} - 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} - 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} - 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} - 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} - 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} - 101 | {} | {} - 102 | {NULL} | {NULL} -(102 rows) - -SELECT * FROM array_index_op_test WHERE t && '{}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_index_op_test WHERE t <@ '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - --- And try it with a multicolumn GIN index -DROP INDEX intarrayidx, textarrayidx; -CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t); -SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+--------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------- - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(7 rows) - -SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+--------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------- - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(7 rows) - -SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+-----------------------------+------------------------------------------------------------------------------ - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(1 row) - -SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno; - seqno | i | t --------+-----------------------------+------------------------------------------------------------------------------ - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(1 row) - -SELECT * FROM array_index_op_test WHERE t = '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -RESET enable_seqscan; -RESET enable_indexscan; -RESET enable_bitmapscan; --- --- Try a GIN index with a lot of items with same key. (GIN creates a posting --- tree when there are enough duplicates) --- -CREATE TABLE array_gin_test (a int[]); -INSERT INTO array_gin_test SELECT ARRAY[1, g%5, g] FROM generate_series(1, 10000) g; -CREATE INDEX array_gin_test_idx ON array_gin_test USING gin (a); -SELECT COUNT(*) FROM array_gin_test WHERE a @> '{2}'; - count -------- - 2000 -(1 row) - -DROP TABLE array_gin_test; --- --- Test GIN index's reloptions --- -CREATE INDEX gin_relopts_test ON array_index_op_test USING gin (i) - WITH (FASTUPDATE=on, GIN_PENDING_LIST_LIMIT=128); -\d+ gin_relopts_test - Index "public.gin_relopts_test" - Column | Type | Key? | Definition | Storage | Stats target ---------+---------+------+------------+---------+-------------- - i | integer | yes | i | plain | -gin, for table "public.array_index_op_test" -Options: fastupdate=on, gin_pending_list_limit=128 - --- --- HASH --- -CREATE UNLOGGED TABLE unlogged_hash_table (id int4); -CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int4_ops); -DROP TABLE unlogged_hash_table; --- CREATE INDEX hash_ovfl_index ON hash_ovfl_heap USING hash (x int4_ops); --- Test hash index build tuplesorting. Force hash tuplesort using low --- maintenance_work_mem setting and fillfactor: -SET maintenance_work_mem = '1MB'; -CREATE INDEX hash_tuplesort_idx ON tenk1 USING hash (stringu1 name_ops) WITH (fillfactor = 10); -EXPLAIN (COSTS OFF) -SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA'; - QUERY PLAN -------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on tenk1 - Recheck Cond: (stringu1 = 'TVAAAA'::name) - -> Bitmap Index Scan on hash_tuplesort_idx - Index Cond: (stringu1 = 'TVAAAA'::name) -(5 rows) - -SELECT count(*) FROM tenk1 WHERE stringu1 = 'TVAAAA'; - count -------- - 14 -(1 row) - -DROP INDEX hash_tuplesort_idx; -RESET maintenance_work_mem; --- --- Test unique null behavior --- -CREATE TABLE unique_tbl (i int, t text); -CREATE UNIQUE INDEX unique_idx1 ON unique_tbl (i) NULLS DISTINCT; -CREATE UNIQUE INDEX unique_idx2 ON unique_tbl (i) NULLS NOT DISTINCT; -INSERT INTO unique_tbl VALUES (1, 'one'); -INSERT INTO unique_tbl VALUES (2, 'two'); -INSERT INTO unique_tbl VALUES (3, 'three'); -INSERT INTO unique_tbl VALUES (4, 'four'); -INSERT INTO unique_tbl VALUES (5, 'one'); -INSERT INTO unique_tbl (t) VALUES ('six'); -INSERT INTO unique_tbl (t) VALUES ('seven'); -- error from unique_idx2 -ERROR: duplicate key value violates unique constraint "unique_idx2" -DETAIL: Key (i)=(null) already exists. -DROP INDEX unique_idx1, unique_idx2; -INSERT INTO unique_tbl (t) VALUES ('seven'); --- build indexes on filled table -CREATE UNIQUE INDEX unique_idx3 ON unique_tbl (i) NULLS DISTINCT; -- ok -CREATE UNIQUE INDEX unique_idx4 ON unique_tbl (i) NULLS NOT DISTINCT; -- error -ERROR: could not create unique index "unique_idx4" -DETAIL: Key (i)=(null) is duplicated. -DELETE FROM unique_tbl WHERE t = 'seven'; -CREATE UNIQUE INDEX unique_idx4 ON unique_tbl (i) NULLS NOT DISTINCT; -- ok now -\d unique_tbl - Table "public.unique_tbl" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - i | integer | | | - t | text | | | -Indexes: - "unique_idx3" UNIQUE, btree (i) - "unique_idx4" UNIQUE, btree (i) NULLS NOT DISTINCT - -\d unique_idx3 - Index "public.unique_idx3" - Column | Type | Key? | Definition ---------+---------+------+------------ - i | integer | yes | i -unique, btree, for table "public.unique_tbl" - -\d unique_idx4 - Index "public.unique_idx4" - Column | Type | Key? | Definition ---------+---------+------+------------ - i | integer | yes | i -unique nulls not distinct, btree, for table "public.unique_tbl" - -SELECT pg_get_indexdef('unique_idx3'::regclass); - pg_get_indexdef ----------------------------------------------------------------------- - CREATE UNIQUE INDEX unique_idx3 ON public.unique_tbl USING btree (i) -(1 row) - -SELECT pg_get_indexdef('unique_idx4'::regclass); - pg_get_indexdef ------------------------------------------------------------------------------------------ - CREATE UNIQUE INDEX unique_idx4 ON public.unique_tbl USING btree (i) NULLS NOT DISTINCT -(1 row) - -DROP TABLE unique_tbl; --- --- Test functional index --- -CREATE TABLE func_index_heap (f1 text, f2 text); -CREATE UNIQUE INDEX func_index_index on func_index_heap (textcat(f1,f2)); -INSERT INTO func_index_heap VALUES('ABC','DEF'); -INSERT INTO func_index_heap VALUES('AB','CDEFG'); -INSERT INTO func_index_heap VALUES('QWE','RTY'); --- this should fail because of unique index: -INSERT INTO func_index_heap VALUES('ABCD', 'EF'); -ERROR: duplicate key value violates unique constraint "func_index_index" -DETAIL: Key (textcat(f1, f2))=(ABCDEF) already exists. --- but this shouldn't: -INSERT INTO func_index_heap VALUES('QWERTY'); --- while we're here, see that the metadata looks sane -\d func_index_heap - Table "public.func_index_heap" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | - f2 | text | | | -Indexes: - "func_index_index" UNIQUE, btree (textcat(f1, f2)) - -\d func_index_index - Index "public.func_index_index" - Column | Type | Key? | Definition ----------+------+------+----------------- - textcat | text | yes | textcat(f1, f2) -unique, btree, for table "public.func_index_heap" - --- --- Same test, expressional index --- -DROP TABLE func_index_heap; -CREATE TABLE func_index_heap (f1 text, f2 text); -CREATE UNIQUE INDEX func_index_index on func_index_heap ((f1 || f2) text_ops); -INSERT INTO func_index_heap VALUES('ABC','DEF'); -INSERT INTO func_index_heap VALUES('AB','CDEFG'); -INSERT INTO func_index_heap VALUES('QWE','RTY'); --- this should fail because of unique index: -INSERT INTO func_index_heap VALUES('ABCD', 'EF'); -ERROR: duplicate key value violates unique constraint "func_index_index" -DETAIL: Key ((f1 || f2))=(ABCDEF) already exists. --- but this shouldn't: -INSERT INTO func_index_heap VALUES('QWERTY'); --- while we're here, see that the metadata looks sane -\d func_index_heap - Table "public.func_index_heap" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | - f2 | text | | | -Indexes: - "func_index_index" UNIQUE, btree ((f1 || f2)) - -\d func_index_index - Index "public.func_index_index" - Column | Type | Key? | Definition ---------+------+------+------------ - expr | text | yes | (f1 || f2) -unique, btree, for table "public.func_index_heap" - --- this should fail because of unsafe column type (anonymous record) -create index on func_index_heap ((f1 || f2), (row(f1, f2))); -ERROR: column "row" has pseudo-type record --- --- Test unique index with included columns --- -CREATE TABLE covering_index_heap (f1 int, f2 int, f3 text); -CREATE UNIQUE INDEX covering_index_index on covering_index_heap (f1,f2) INCLUDE(f3); -INSERT INTO covering_index_heap VALUES(1,1,'AAA'); -INSERT INTO covering_index_heap VALUES(1,2,'AAA'); --- this should fail because of unique index on f1,f2: -INSERT INTO covering_index_heap VALUES(1,2,'BBB'); -ERROR: duplicate key value violates unique constraint "covering_index_index" -DETAIL: Key (f1, f2)=(1, 2) already exists. --- and this shouldn't: -INSERT INTO covering_index_heap VALUES(1,4,'AAA'); --- Try to build index on table that already contains data -CREATE UNIQUE INDEX covering_pkey on covering_index_heap (f1,f2) INCLUDE(f3); --- Try to use existing covering index as primary key -ALTER TABLE covering_index_heap ADD CONSTRAINT covering_pkey PRIMARY KEY USING INDEX -covering_pkey; -DROP TABLE covering_index_heap; --- --- Try some concurrent index builds --- --- Unfortunately this only tests about half the code paths because there are --- no concurrent updates happening to the table at the same time. -CREATE TABLE concur_heap (f1 text, f2 text); --- empty table -CREATE INDEX CONCURRENTLY concur_index1 ON concur_heap(f2,f1); -CREATE INDEX CONCURRENTLY IF NOT EXISTS concur_index1 ON concur_heap(f2,f1); -NOTICE: relation "concur_index1" already exists, skipping -INSERT INTO concur_heap VALUES ('a','b'); -INSERT INTO concur_heap VALUES ('b','b'); --- unique index -CREATE UNIQUE INDEX CONCURRENTLY concur_index2 ON concur_heap(f1); -CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS concur_index2 ON concur_heap(f1); -NOTICE: relation "concur_index2" already exists, skipping --- check if constraint is set up properly to be enforced -INSERT INTO concur_heap VALUES ('b','x'); -ERROR: duplicate key value violates unique constraint "concur_index2" -DETAIL: Key (f1)=(b) already exists. --- check if constraint is enforced properly at build time -CREATE UNIQUE INDEX CONCURRENTLY concur_index3 ON concur_heap(f2); -ERROR: could not create unique index "concur_index3" -DETAIL: Key (f2)=(b) is duplicated. --- test that expression indexes and partial indexes work concurrently -CREATE INDEX CONCURRENTLY concur_index4 on concur_heap(f2) WHERE f1='a'; -CREATE INDEX CONCURRENTLY concur_index5 on concur_heap(f2) WHERE f1='x'; --- here we also check that you can default the index name -CREATE INDEX CONCURRENTLY on concur_heap((f2||f1)); --- You can't do a concurrent index build in a transaction -BEGIN; -CREATE INDEX CONCURRENTLY concur_index7 ON concur_heap(f1); -ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block -COMMIT; --- test where predicate is able to do a transactional update during --- a concurrent build before switching pg_index state flags. -CREATE FUNCTION predicate_stable() RETURNS bool IMMUTABLE -LANGUAGE plpgsql AS $$ -BEGIN - EXECUTE 'SELECT txid_current()'; - RETURN true; -END; $$; -CREATE INDEX CONCURRENTLY concur_index8 ON concur_heap (f1) - WHERE predicate_stable(); -DROP INDEX concur_index8; -DROP FUNCTION predicate_stable(); --- But you can do a regular index build in a transaction -BEGIN; -CREATE INDEX std_index on concur_heap(f2); -COMMIT; --- Failed builds are left invalid by VACUUM FULL, fixed by REINDEX -VACUUM FULL concur_heap; -REINDEX TABLE concur_heap; -ERROR: could not create unique index "concur_index3" -DETAIL: Key (f2)=(b) is duplicated. -DELETE FROM concur_heap WHERE f1 = 'b'; -VACUUM FULL concur_heap; -\d concur_heap - Table "public.concur_heap" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | - f2 | text | | | -Indexes: - "concur_heap_expr_idx" btree ((f2 || f1)) - "concur_index1" btree (f2, f1) - "concur_index2" UNIQUE, btree (f1) - "concur_index3" UNIQUE, btree (f2) INVALID - "concur_index4" btree (f2) WHERE f1 = 'a'::text - "concur_index5" btree (f2) WHERE f1 = 'x'::text - "std_index" btree (f2) - -REINDEX TABLE concur_heap; -\d concur_heap - Table "public.concur_heap" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | - f2 | text | | | -Indexes: - "concur_heap_expr_idx" btree ((f2 || f1)) - "concur_index1" btree (f2, f1) - "concur_index2" UNIQUE, btree (f1) - "concur_index3" UNIQUE, btree (f2) - "concur_index4" btree (f2) WHERE f1 = 'a'::text - "concur_index5" btree (f2) WHERE f1 = 'x'::text - "std_index" btree (f2) - --- Temporary tables with concurrent builds and on-commit actions --- CONCURRENTLY used with CREATE INDEX and DROP INDEX is ignored. --- PRESERVE ROWS, the default. -CREATE TEMP TABLE concur_temp (f1 int, f2 text) - ON COMMIT PRESERVE ROWS; -INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar'); -CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1); -DROP INDEX CONCURRENTLY concur_temp_ind; -DROP TABLE concur_temp; --- ON COMMIT DROP -BEGIN; -CREATE TEMP TABLE concur_temp (f1 int, f2 text) - ON COMMIT DROP; -INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar'); --- Fails when running in a transaction. -CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1); -ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block -COMMIT; --- ON COMMIT DELETE ROWS -CREATE TEMP TABLE concur_temp (f1 int, f2 text) - ON COMMIT DELETE ROWS; -INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar'); -CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1); -DROP INDEX CONCURRENTLY concur_temp_ind; -DROP TABLE concur_temp; --- --- Try some concurrent index drops --- -DROP INDEX CONCURRENTLY "concur_index2"; -- works -DROP INDEX CONCURRENTLY IF EXISTS "concur_index2"; -- notice -NOTICE: index "concur_index2" does not exist, skipping --- failures -DROP INDEX CONCURRENTLY "concur_index2", "concur_index3"; -ERROR: DROP INDEX CONCURRENTLY does not support dropping multiple objects -BEGIN; -DROP INDEX CONCURRENTLY "concur_index5"; -ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block -ROLLBACK; --- successes -DROP INDEX CONCURRENTLY IF EXISTS "concur_index3"; -DROP INDEX CONCURRENTLY "concur_index4"; -DROP INDEX CONCURRENTLY "concur_index5"; -DROP INDEX CONCURRENTLY "concur_index1"; -DROP INDEX CONCURRENTLY "concur_heap_expr_idx"; -\d concur_heap - Table "public.concur_heap" - Column | Type | Collation | Nullable | Default ---------+------+-----------+----------+--------- - f1 | text | | | - f2 | text | | | -Indexes: - "std_index" btree (f2) - -DROP TABLE concur_heap; --- --- Test ADD CONSTRAINT USING INDEX --- -CREATE TABLE cwi_test( a int , b varchar(10), c char); --- add some data so that all tests have something to work with. -INSERT INTO cwi_test VALUES(1, 2), (3, 4), (5, 6); -CREATE UNIQUE INDEX cwi_uniq_idx ON cwi_test(a , b); -ALTER TABLE cwi_test ADD primary key USING INDEX cwi_uniq_idx; -\d cwi_test - Table "public.cwi_test" - Column | Type | Collation | Nullable | Default ---------+-----------------------+-----------+----------+--------- - a | integer | | not null | - b | character varying(10) | | not null | - c | character(1) | | | -Indexes: - "cwi_uniq_idx" PRIMARY KEY, btree (a, b) - -\d cwi_uniq_idx - Index "public.cwi_uniq_idx" - Column | Type | Key? | Definition ---------+-----------------------+------+------------ - a | integer | yes | a - b | character varying(10) | yes | b -primary key, btree, for table "public.cwi_test" - -CREATE UNIQUE INDEX cwi_uniq2_idx ON cwi_test(b , a); -ALTER TABLE cwi_test DROP CONSTRAINT cwi_uniq_idx, - ADD CONSTRAINT cwi_replaced_pkey PRIMARY KEY - USING INDEX cwi_uniq2_idx; -NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "cwi_uniq2_idx" to "cwi_replaced_pkey" -\d cwi_test - Table "public.cwi_test" - Column | Type | Collation | Nullable | Default ---------+-----------------------+-----------+----------+--------- - a | integer | | not null | - b | character varying(10) | | not null | - c | character(1) | | | -Indexes: - "cwi_replaced_pkey" PRIMARY KEY, btree (b, a) - -\d cwi_replaced_pkey - Index "public.cwi_replaced_pkey" - Column | Type | Key? | Definition ---------+-----------------------+------+------------ - b | character varying(10) | yes | b - a | integer | yes | a -primary key, btree, for table "public.cwi_test" - -DROP INDEX cwi_replaced_pkey; -- Should fail; a constraint depends on it -ERROR: cannot drop index cwi_replaced_pkey because constraint cwi_replaced_pkey on table cwi_test requires it -HINT: You can drop constraint cwi_replaced_pkey on table cwi_test instead. --- Check that non-default index options are rejected -CREATE UNIQUE INDEX cwi_uniq3_idx ON cwi_test(a desc); -ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq3_idx; -- fail -ERROR: index "cwi_uniq3_idx" column number 1 does not have default sorting behavior -LINE 1: ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq3_idx; - ^ -DETAIL: Cannot create a primary key or unique constraint using such an index. -CREATE UNIQUE INDEX cwi_uniq4_idx ON cwi_test(b collate "POSIX"); -ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq4_idx; -- fail -ERROR: index "cwi_uniq4_idx" column number 1 does not have default sorting behavior -LINE 1: ALTER TABLE cwi_test ADD UNIQUE USING INDEX cwi_uniq4_idx; - ^ -DETAIL: Cannot create a primary key or unique constraint using such an index. -DROP TABLE cwi_test; --- ADD CONSTRAINT USING INDEX is forbidden on partitioned tables -CREATE TABLE cwi_test(a int) PARTITION BY hash (a); -create unique index on cwi_test (a); -alter table cwi_test add primary key using index cwi_test_a_idx ; -ERROR: ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables -DROP TABLE cwi_test; --- PRIMARY KEY constraint cannot be backed by a NULLS NOT DISTINCT index -CREATE TABLE cwi_test(a int, b int); -CREATE UNIQUE INDEX cwi_a_nnd ON cwi_test (a) NULLS NOT DISTINCT; -ALTER TABLE cwi_test ADD PRIMARY KEY USING INDEX cwi_a_nnd; -ERROR: primary keys cannot use NULLS NOT DISTINCT indexes -DROP TABLE cwi_test; --- --- Check handling of indexes on system columns --- -CREATE TABLE syscol_table (a INT); --- System columns cannot be indexed -CREATE INDEX ON syscolcol_table (ctid); -ERROR: relation "syscolcol_table" does not exist --- nor used in expressions -CREATE INDEX ON syscol_table ((ctid >= '(1000,0)')); -ERROR: index creation on system columns is not supported --- nor used in predicates -CREATE INDEX ON syscol_table (a) WHERE ctid >= '(1000,0)'; -ERROR: index creation on system columns is not supported -DROP TABLE syscol_table; --- --- Tests for IS NULL/IS NOT NULL with b-tree indexes --- -CREATE TABLE onek_with_null AS SELECT unique1, unique2 FROM onek; -INSERT INTO onek_with_null (unique1,unique2) VALUES (NULL, -1), (NULL, NULL); -CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2,unique1); -SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = ON; -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL; - count -------- - 2 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL; - count -------- - 1000 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500; - count -------- - 499 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500; - count -------- - 0 -(1 row) - -DROP INDEX onek_nulltest; -CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc,unique1); -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL; - count -------- - 2 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL; - count -------- - 1000 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500; - count -------- - 499 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500; - count -------- - 0 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IN (-1, 0, 1); - count -------- - 1 -(1 row) - -DROP INDEX onek_nulltest; -CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc nulls last,unique1); -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL; - count -------- - 2 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL; - count -------- - 1000 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500; - count -------- - 499 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500; - count -------- - 0 -(1 row) - -DROP INDEX onek_nulltest; -CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 nulls first,unique1); -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL; - count -------- - 2 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL; - count -------- - 1000 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL; - count -------- - 1 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500; - count -------- - 499 -(1 row) - -SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500; - count -------- - 0 -(1 row) - -DROP INDEX onek_nulltest; --- Check initial-positioning logic too -CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2); -SET enable_seqscan = OFF; -SET enable_indexscan = ON; -SET enable_bitmapscan = OFF; -SELECT unique1, unique2 FROM onek_with_null - ORDER BY unique2 LIMIT 2; - unique1 | unique2 ----------+--------- - | -1 - 147 | 0 -(2 rows) - -SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1 - ORDER BY unique2 LIMIT 2; - unique1 | unique2 ----------+--------- - | -1 - 147 | 0 -(2 rows) - -SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= 0 - ORDER BY unique2 LIMIT 2; - unique1 | unique2 ----------+--------- - 147 | 0 - 931 | 1 -(2 rows) - -SELECT unique1, unique2 FROM onek_with_null - ORDER BY unique2 DESC LIMIT 2; - unique1 | unique2 ----------+--------- - | - 278 | 999 -(2 rows) - -SELECT unique1, unique2 FROM onek_with_null WHERE unique2 >= -1 - ORDER BY unique2 DESC LIMIT 2; - unique1 | unique2 ----------+--------- - 278 | 999 - 0 | 998 -(2 rows) - -SELECT unique1, unique2 FROM onek_with_null WHERE unique2 < 999 - ORDER BY unique2 DESC LIMIT 2; - unique1 | unique2 ----------+--------- - 0 | 998 - 744 | 997 -(2 rows) - -RESET enable_seqscan; -RESET enable_indexscan; -RESET enable_bitmapscan; -DROP TABLE onek_with_null; --- --- Check bitmap index path planning --- -EXPLAIN (COSTS OFF) -SELECT * FROM tenk1 - WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ - Bitmap Heap Scan on tenk1 - Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42))) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 1)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 3)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: ((thousand = 42) AND (tenthous = 42)) -(9 rows) - -SELECT * FROM tenk1 - WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 42 | 5530 | 0 | 2 | 2 | 2 | 42 | 42 | 42 | 42 | 42 | 84 | 85 | QBAAAA | SEIAAA | OOOOxx -(1 row) - -EXPLAIN (COSTS OFF) -SELECT count(*) FROM tenk1 - WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); - QUERY PLAN ---------------------------------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on tenk1 - Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 99))) - -> BitmapAnd - -> Bitmap Index Scan on tenk1_hundred - Index Cond: (hundred = 42) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = 42) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = 99) -(11 rows) - -SELECT count(*) FROM tenk1 - WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); - count -------- - 10 -(1 row) - --- --- Check behavior with duplicate index column contents --- -CREATE TABLE dupindexcols AS - SELECT unique1 as id, stringu2::text as f1 FROM tenk1; -CREATE INDEX dupindexcols_i ON dupindexcols (f1, id, f1 text_pattern_ops); -ANALYZE dupindexcols; -EXPLAIN (COSTS OFF) - SELECT count(*) FROM dupindexcols - WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX'; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on dupindexcols - Recheck Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text)) - -> Bitmap Index Scan on dupindexcols_i - Index Cond: ((f1 >= 'WA'::text) AND (f1 <= 'ZZZ'::text) AND (id < 1000) AND (f1 ~<~ 'YX'::text)) -(5 rows) - -SELECT count(*) FROM dupindexcols - WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX'; - count -------- - 97 -(1 row) - --- --- Check that index scans with =ANY indexquals return rows in index order --- -explain (costs off) -SELECT unique1 FROM tenk1 -WHERE unique1 IN (1,42,7) -ORDER BY unique1; - QUERY PLAN -------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = ANY ('{1,42,7}'::integer[])) -(2 rows) - -SELECT unique1 FROM tenk1 -WHERE unique1 IN (1,42,7) -ORDER BY unique1; - unique1 ---------- - 1 - 7 - 42 -(3 rows) - --- Non-required array scan key on "tenthous": -explain (costs off) -SELECT thousand, tenthous FROM tenk1 -WHERE thousand < 2 AND tenthous IN (1001,3000) -ORDER BY thousand; - QUERY PLAN --------------------------------------------------------------------------------- - Index Only Scan using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[]))) -(2 rows) - -SELECT thousand, tenthous FROM tenk1 -WHERE thousand < 2 AND tenthous IN (1001,3000) -ORDER BY thousand; - thousand | tenthous -----------+---------- - 0 | 3000 - 1 | 1001 -(2 rows) - --- Non-required array scan key on "tenthous", backward scan: -explain (costs off) -SELECT thousand, tenthous FROM tenk1 -WHERE thousand < 2 AND tenthous IN (1001,3000) -ORDER BY thousand DESC, tenthous DESC; - QUERY PLAN --------------------------------------------------------------------------------- - Index Only Scan Backward using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[]))) -(2 rows) - -SELECT thousand, tenthous FROM tenk1 -WHERE thousand < 2 AND tenthous IN (1001,3000) -ORDER BY thousand DESC, tenthous DESC; - thousand | tenthous -----------+---------- - 1 | 1001 - 0 | 3000 -(2 rows) - --- --- Check elimination of redundant and contradictory index quals --- -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}'); - QUERY PLAN ----------------------------------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = ANY ('{7,8,9}'::integer[]))) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}'); - unique1 ---------- - 7 -(1 row) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]); - QUERY PLAN ----------------------------------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{7,14,22}'::integer[])) AND (unique1 = ANY ('{33,44}'::bigint[]))) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]); - unique1 ---------- -(0 rows) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1; - QUERY PLAN ---------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = 1)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1; - unique1 ---------- - 1 -(1 row) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345; - QUERY PLAN -------------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = 12345)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345; - unique1 ---------- -(0 rows) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42; - QUERY PLAN ------------------------------------------------------------------------------ - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 >= 42)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42; - unique1 ---------- - 42 -(1 row) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42; - QUERY PLAN ----------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 > 42)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42; - unique1 ---------- -(0 rows) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999; - QUERY PLAN --------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 > 9996) AND (unique1 >= 9999)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999; - unique1 ---------- - 9999 -(1 row) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3; - QUERY PLAN --------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 < 3) AND (unique1 <= 3)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3; - unique1 ---------- - 0 - 1 - 2 -(3 rows) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint; - QUERY PLAN ------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 < 3) AND (unique1 < '-1'::bigint)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint; - unique1 ---------- -(0 rows) - -explain (costs off) -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint; - QUERY PLAN --------------------------------------------------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 < '-1'::bigint)) -(2 rows) - -SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint; - unique1 ---------- -(0 rows) - --- --- Check elimination of constant-NULL subexpressions --- -explain (costs off) - select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null)); - QUERY PLAN ------------------------------------------------------- - Index Scan using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand = 1) AND (tenthous = 1001)) -(2 rows) - --- --- Check matching of boolean index columns to WHERE conditions and sort keys --- -create temp table boolindex (b bool, i int, unique(b, i), junk float); -explain (costs off) - select * from boolindex order by b, i limit 10; - QUERY PLAN -------------------------------------------------------- - Limit - -> Index Scan using boolindex_b_i_key on boolindex -(2 rows) - -explain (costs off) - select * from boolindex where b order by i limit 10; - QUERY PLAN -------------------------------------------------------- - Limit - -> Index Scan using boolindex_b_i_key on boolindex - Index Cond: (b = true) -(3 rows) - -explain (costs off) - select * from boolindex where b = true order by i desc limit 10; - QUERY PLAN ----------------------------------------------------------------- - Limit - -> Index Scan Backward using boolindex_b_i_key on boolindex - Index Cond: (b = true) -(3 rows) - -explain (costs off) - select * from boolindex where not b order by i limit 10; - QUERY PLAN -------------------------------------------------------- - Limit - -> Index Scan using boolindex_b_i_key on boolindex - Index Cond: (b = false) -(3 rows) - -explain (costs off) - select * from boolindex where b is true order by i desc limit 10; - QUERY PLAN ----------------------------------------------------------------- - Limit - -> Index Scan Backward using boolindex_b_i_key on boolindex - Index Cond: (b = true) -(3 rows) - -explain (costs off) - select * from boolindex where b is false order by i desc limit 10; - QUERY PLAN ----------------------------------------------------------------- - Limit - -> Index Scan Backward using boolindex_b_i_key on boolindex - Index Cond: (b = false) -(3 rows) - --- --- REINDEX (VERBOSE) --- -CREATE TABLE reindex_verbose(id integer primary key); -\set VERBOSITY terse \\ -- suppress machine-dependent details -REINDEX (VERBOSE) TABLE reindex_verbose; -INFO: index "reindex_verbose_pkey" was reindexed -\set VERBOSITY default -DROP TABLE reindex_verbose; --- --- REINDEX CONCURRENTLY --- -CREATE TABLE concur_reindex_tab (c1 int); --- REINDEX -REINDEX TABLE concur_reindex_tab; -- notice -NOTICE: table "concur_reindex_tab" has no indexes to reindex -REINDEX (CONCURRENTLY) TABLE concur_reindex_tab; -- notice -NOTICE: table "concur_reindex_tab" has no indexes that can be reindexed concurrently -ALTER TABLE concur_reindex_tab ADD COLUMN c2 text; -- add toast index --- Normal index with integer column -CREATE UNIQUE INDEX concur_reindex_ind1 ON concur_reindex_tab(c1); --- Normal index with text column -CREATE INDEX concur_reindex_ind2 ON concur_reindex_tab(c2); --- UNIQUE index with expression -CREATE UNIQUE INDEX concur_reindex_ind3 ON concur_reindex_tab(abs(c1)); --- Duplicate column names -CREATE INDEX concur_reindex_ind4 ON concur_reindex_tab(c1, c1, c2); --- Create table for check on foreign key dependence switch with indexes swapped -ALTER TABLE concur_reindex_tab ADD PRIMARY KEY USING INDEX concur_reindex_ind1; -CREATE TABLE concur_reindex_tab2 (c1 int REFERENCES concur_reindex_tab); -INSERT INTO concur_reindex_tab VALUES (1, 'a'); -INSERT INTO concur_reindex_tab VALUES (2, 'a'); --- Reindex concurrently of exclusion constraint currently not supported -CREATE TABLE concur_reindex_tab3 (c1 int, c2 int4range, EXCLUDE USING gist (c2 WITH &&)); -INSERT INTO concur_reindex_tab3 VALUES (3, '[1,2]'); -REINDEX INDEX CONCURRENTLY concur_reindex_tab3_c2_excl; -- error -ERROR: concurrent index creation for exclusion constraints is not supported -REINDEX TABLE CONCURRENTLY concur_reindex_tab3; -- succeeds with warning -WARNING: cannot reindex exclusion constraint index "public.concur_reindex_tab3_c2_excl" concurrently, skipping -INSERT INTO concur_reindex_tab3 VALUES (4, '[2,4]'); -ERROR: conflicting key value violates exclusion constraint "concur_reindex_tab3_c2_excl" -DETAIL: Key (c2)=([2,5)) conflicts with existing key (c2)=([1,3)). --- Check materialized views -CREATE MATERIALIZED VIEW concur_reindex_matview AS SELECT * FROM concur_reindex_tab; --- Dependency lookup before and after the follow-up REINDEX commands. --- These should remain consistent. -SELECT pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid,refobjid,refobjsubid) as objref, - deptype -FROM pg_depend -WHERE classid = 'pg_class'::regclass AND - objid in ('concur_reindex_tab'::regclass, - 'concur_reindex_ind1'::regclass, - 'concur_reindex_ind2'::regclass, - 'concur_reindex_ind3'::regclass, - 'concur_reindex_ind4'::regclass, - 'concur_reindex_matview'::regclass) - ORDER BY 1, 2; - obj | objref | deptype -------------------------------------------+------------------------------------------------------------+--------- - index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i - index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a - index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind3 | table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a - materialized view concur_reindex_matview | access method tde_heap | n - materialized view concur_reindex_matview | schema public | n - table concur_reindex_tab | access method tde_heap | n - table concur_reindex_tab | schema public | n -(10 rows) - -REINDEX INDEX CONCURRENTLY concur_reindex_ind1; -REINDEX TABLE CONCURRENTLY concur_reindex_tab; -REINDEX TABLE CONCURRENTLY concur_reindex_matview; -SELECT pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid,refobjid,refobjsubid) as objref, - deptype -FROM pg_depend -WHERE classid = 'pg_class'::regclass AND - objid in ('concur_reindex_tab'::regclass, - 'concur_reindex_ind1'::regclass, - 'concur_reindex_ind2'::regclass, - 'concur_reindex_ind3'::regclass, - 'concur_reindex_ind4'::regclass, - 'concur_reindex_matview'::regclass) - ORDER BY 1, 2; - obj | objref | deptype -------------------------------------------+------------------------------------------------------------+--------- - index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i - index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a - index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind3 | table concur_reindex_tab | a - index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a - index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a - materialized view concur_reindex_matview | access method tde_heap | n - materialized view concur_reindex_matview | schema public | n - table concur_reindex_tab | access method tde_heap | n - table concur_reindex_tab | schema public | n -(10 rows) - --- Check that comments are preserved -CREATE TABLE testcomment (i int); -CREATE INDEX testcomment_idx1 ON testcomment (i); -COMMENT ON INDEX testcomment_idx1 IS 'test comment'; -SELECT obj_description('testcomment_idx1'::regclass, 'pg_class'); - obj_description ------------------ - test comment -(1 row) - -REINDEX TABLE testcomment; -SELECT obj_description('testcomment_idx1'::regclass, 'pg_class'); - obj_description ------------------ - test comment -(1 row) - -REINDEX TABLE CONCURRENTLY testcomment ; -SELECT obj_description('testcomment_idx1'::regclass, 'pg_class'); - obj_description ------------------ - test comment -(1 row) - -DROP TABLE testcomment; --- Check that indisclustered updates are preserved -CREATE TABLE concur_clustered(i int); -CREATE INDEX concur_clustered_i_idx ON concur_clustered(i); -ALTER TABLE concur_clustered CLUSTER ON concur_clustered_i_idx; -REINDEX TABLE CONCURRENTLY concur_clustered; -SELECT indexrelid::regclass, indisclustered FROM pg_index - WHERE indrelid = 'concur_clustered'::regclass; - indexrelid | indisclustered -------------------------+---------------- - concur_clustered_i_idx | t -(1 row) - -DROP TABLE concur_clustered; --- Check that indisreplident updates are preserved. -CREATE TABLE concur_replident(i int NOT NULL); -CREATE UNIQUE INDEX concur_replident_i_idx ON concur_replident(i); -ALTER TABLE concur_replident REPLICA IDENTITY - USING INDEX concur_replident_i_idx; -SELECT indexrelid::regclass, indisreplident FROM pg_index - WHERE indrelid = 'concur_replident'::regclass; - indexrelid | indisreplident -------------------------+---------------- - concur_replident_i_idx | t -(1 row) - -REINDEX TABLE CONCURRENTLY concur_replident; -SELECT indexrelid::regclass, indisreplident FROM pg_index - WHERE indrelid = 'concur_replident'::regclass; - indexrelid | indisreplident -------------------------+---------------- - concur_replident_i_idx | t -(1 row) - -DROP TABLE concur_replident; --- Check that opclass parameters are preserved -CREATE TABLE concur_appclass_tab(i tsvector, j tsvector, k tsvector); -CREATE INDEX concur_appclass_ind on concur_appclass_tab - USING gist (i tsvector_ops (siglen='1000'), j tsvector_ops (siglen='500')); -CREATE INDEX concur_appclass_ind_2 on concur_appclass_tab - USING gist (k tsvector_ops (siglen='300'), j tsvector_ops); -REINDEX TABLE CONCURRENTLY concur_appclass_tab; -\d concur_appclass_tab - Table "public.concur_appclass_tab" - Column | Type | Collation | Nullable | Default ---------+----------+-----------+----------+--------- - i | tsvector | | | - j | tsvector | | | - k | tsvector | | | -Indexes: - "concur_appclass_ind" gist (i tsvector_ops (siglen='1000'), j tsvector_ops (siglen='500')) - "concur_appclass_ind_2" gist (k tsvector_ops (siglen='300'), j) - -DROP TABLE concur_appclass_tab; --- Partitions --- Create some partitioned tables -CREATE TABLE concur_reindex_part (c1 int, c2 int) PARTITION BY RANGE (c1); -CREATE TABLE concur_reindex_part_0 PARTITION OF concur_reindex_part - FOR VALUES FROM (0) TO (10) PARTITION BY list (c2); -CREATE TABLE concur_reindex_part_0_1 PARTITION OF concur_reindex_part_0 - FOR VALUES IN (1); -CREATE TABLE concur_reindex_part_0_2 PARTITION OF concur_reindex_part_0 - FOR VALUES IN (2); --- This partitioned table will have no partitions. -CREATE TABLE concur_reindex_part_10 PARTITION OF concur_reindex_part - FOR VALUES FROM (10) TO (20) PARTITION BY list (c2); --- Create some partitioned indexes -CREATE INDEX concur_reindex_part_index ON ONLY concur_reindex_part (c1); -CREATE INDEX concur_reindex_part_index_0 ON ONLY concur_reindex_part_0 (c1); -ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_0; --- This partitioned index will have no partitions. -CREATE INDEX concur_reindex_part_index_10 ON ONLY concur_reindex_part_10 (c1); -ALTER INDEX concur_reindex_part_index ATTACH PARTITION concur_reindex_part_index_10; -CREATE INDEX concur_reindex_part_index_0_1 ON ONLY concur_reindex_part_0_1 (c1); -ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_1; -CREATE INDEX concur_reindex_part_index_0_2 ON ONLY concur_reindex_part_0_2 (c1); -ALTER INDEX concur_reindex_part_index_0 ATTACH PARTITION concur_reindex_part_index_0_2; -SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index') - ORDER BY relid, level; - relid | parentrelid | level --------------------------------+-----------------------------+------- - concur_reindex_part_index | | 0 - concur_reindex_part_index_0 | concur_reindex_part_index | 1 - concur_reindex_part_index_10 | concur_reindex_part_index | 1 - concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2 - concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2 -(5 rows) - -SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index') - ORDER BY relid, level; - relid | parentrelid | level --------------------------------+-----------------------------+------- - concur_reindex_part_index | | 0 - concur_reindex_part_index_0 | concur_reindex_part_index | 1 - concur_reindex_part_index_10 | concur_reindex_part_index | 1 - concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2 - concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2 -(5 rows) - --- REINDEX should preserve dependencies of partition tree. -SELECT pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid,refobjid,refobjsubid) as objref, - deptype -FROM pg_depend -WHERE classid = 'pg_class'::regclass AND - objid in ('concur_reindex_part'::regclass, - 'concur_reindex_part_0'::regclass, - 'concur_reindex_part_0_1'::regclass, - 'concur_reindex_part_0_2'::regclass, - 'concur_reindex_part_index'::regclass, - 'concur_reindex_part_index_0'::regclass, - 'concur_reindex_part_index_0_1'::regclass, - 'concur_reindex_part_index_0_2'::regclass) - ORDER BY 1, 2; - obj | objref | deptype -------------------------------------------+--------------------------------------------+--------- - column c1 of table concur_reindex_part | table concur_reindex_part | i - column c2 of table concur_reindex_part_0 | table concur_reindex_part_0 | i - index concur_reindex_part_index | column c1 of table concur_reindex_part | a - index concur_reindex_part_index_0 | column c1 of table concur_reindex_part_0 | a - index concur_reindex_part_index_0 | index concur_reindex_part_index | P - index concur_reindex_part_index_0 | table concur_reindex_part_0 | S - index concur_reindex_part_index_0_1 | column c1 of table concur_reindex_part_0_1 | a - index concur_reindex_part_index_0_1 | index concur_reindex_part_index_0 | P - index concur_reindex_part_index_0_1 | table concur_reindex_part_0_1 | S - index concur_reindex_part_index_0_2 | column c1 of table concur_reindex_part_0_2 | a - index concur_reindex_part_index_0_2 | index concur_reindex_part_index_0 | P - index concur_reindex_part_index_0_2 | table concur_reindex_part_0_2 | S - table concur_reindex_part | schema public | n - table concur_reindex_part_0 | schema public | n - table concur_reindex_part_0 | table concur_reindex_part | a - table concur_reindex_part_0_1 | access method tde_heap | n - table concur_reindex_part_0_1 | schema public | n - table concur_reindex_part_0_1 | table concur_reindex_part_0 | a - table concur_reindex_part_0_2 | access method tde_heap | n - table concur_reindex_part_0_2 | schema public | n - table concur_reindex_part_0_2 | table concur_reindex_part_0 | a -(21 rows) - -REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_1; -REINDEX INDEX CONCURRENTLY concur_reindex_part_index_0_2; -SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index') - ORDER BY relid, level; - relid | parentrelid | level --------------------------------+-----------------------------+------- - concur_reindex_part_index | | 0 - concur_reindex_part_index_0 | concur_reindex_part_index | 1 - concur_reindex_part_index_10 | concur_reindex_part_index | 1 - concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2 - concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2 -(5 rows) - -REINDEX TABLE CONCURRENTLY concur_reindex_part_0_1; -REINDEX TABLE CONCURRENTLY concur_reindex_part_0_2; -SELECT pg_describe_object(classid, objid, objsubid) as obj, - pg_describe_object(refclassid,refobjid,refobjsubid) as objref, - deptype -FROM pg_depend -WHERE classid = 'pg_class'::regclass AND - objid in ('concur_reindex_part'::regclass, - 'concur_reindex_part_0'::regclass, - 'concur_reindex_part_0_1'::regclass, - 'concur_reindex_part_0_2'::regclass, - 'concur_reindex_part_index'::regclass, - 'concur_reindex_part_index_0'::regclass, - 'concur_reindex_part_index_0_1'::regclass, - 'concur_reindex_part_index_0_2'::regclass) - ORDER BY 1, 2; - obj | objref | deptype -------------------------------------------+--------------------------------------------+--------- - column c1 of table concur_reindex_part | table concur_reindex_part | i - column c2 of table concur_reindex_part_0 | table concur_reindex_part_0 | i - index concur_reindex_part_index | column c1 of table concur_reindex_part | a - index concur_reindex_part_index_0 | column c1 of table concur_reindex_part_0 | a - index concur_reindex_part_index_0 | index concur_reindex_part_index | P - index concur_reindex_part_index_0 | table concur_reindex_part_0 | S - index concur_reindex_part_index_0_1 | column c1 of table concur_reindex_part_0_1 | a - index concur_reindex_part_index_0_1 | index concur_reindex_part_index_0 | P - index concur_reindex_part_index_0_1 | table concur_reindex_part_0_1 | S - index concur_reindex_part_index_0_2 | column c1 of table concur_reindex_part_0_2 | a - index concur_reindex_part_index_0_2 | index concur_reindex_part_index_0 | P - index concur_reindex_part_index_0_2 | table concur_reindex_part_0_2 | S - table concur_reindex_part | schema public | n - table concur_reindex_part_0 | schema public | n - table concur_reindex_part_0 | table concur_reindex_part | a - table concur_reindex_part_0_1 | access method tde_heap | n - table concur_reindex_part_0_1 | schema public | n - table concur_reindex_part_0_1 | table concur_reindex_part_0 | a - table concur_reindex_part_0_2 | access method tde_heap | n - table concur_reindex_part_0_2 | schema public | n - table concur_reindex_part_0_2 | table concur_reindex_part_0 | a -(21 rows) - -SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index') - ORDER BY relid, level; - relid | parentrelid | level --------------------------------+-----------------------------+------- - concur_reindex_part_index | | 0 - concur_reindex_part_index_0 | concur_reindex_part_index | 1 - concur_reindex_part_index_10 | concur_reindex_part_index | 1 - concur_reindex_part_index_0_1 | concur_reindex_part_index_0 | 2 - concur_reindex_part_index_0_2 | concur_reindex_part_index_0 | 2 -(5 rows) - --- REINDEX for partitioned indexes --- REINDEX TABLE fails for partitioned indexes --- Top-most parent index -REINDEX TABLE concur_reindex_part_index; -- error -ERROR: "concur_reindex_part_index" is not a table or materialized view -REINDEX TABLE CONCURRENTLY concur_reindex_part_index; -- error -ERROR: "concur_reindex_part_index" is not a table or materialized view --- Partitioned index with no leaves -REINDEX TABLE concur_reindex_part_index_10; -- error -ERROR: "concur_reindex_part_index_10" is not a table or materialized view -REINDEX TABLE CONCURRENTLY concur_reindex_part_index_10; -- error -ERROR: "concur_reindex_part_index_10" is not a table or materialized view --- Cannot run in a transaction block -BEGIN; -REINDEX INDEX concur_reindex_part_index; -ERROR: REINDEX INDEX cannot run inside a transaction block -CONTEXT: while reindexing partitioned index "public.concur_reindex_part_index" -ROLLBACK; --- Helper functions to track changes of relfilenodes in a partition tree. --- Create a table tracking the relfilenode state. -CREATE OR REPLACE FUNCTION create_relfilenode_part(relname text, indname text) - RETURNS VOID AS - $func$ - BEGIN - EXECUTE format(' - CREATE TABLE %I AS - SELECT oid, relname, relfilenode, relkind, reltoastrelid - FROM pg_class - WHERE oid IN - (SELECT relid FROM pg_partition_tree(''%I''));', - relname, indname); - END - $func$ LANGUAGE plpgsql; -CREATE OR REPLACE FUNCTION compare_relfilenode_part(tabname text) - RETURNS TABLE (relname name, relkind "char", state text) AS - $func$ - BEGIN - RETURN QUERY EXECUTE - format( - 'SELECT b.relname, - b.relkind, - CASE WHEN a.relfilenode = b.relfilenode THEN ''relfilenode is unchanged'' - ELSE ''relfilenode has changed'' END - -- Do not join with OID here as CONCURRENTLY changes it. - FROM %I b JOIN pg_class a ON b.relname = a.relname - ORDER BY 1;', tabname); - END - $func$ LANGUAGE plpgsql; --- Check that expected relfilenodes are changed, non-concurrent case. -SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index'); - create_relfilenode_part -------------------------- - -(1 row) - -REINDEX INDEX concur_reindex_part_index; -SELECT * FROM compare_relfilenode_part('reindex_index_status'); - relname | relkind | state --------------------------------+---------+-------------------------- - concur_reindex_part_index | I | relfilenode is unchanged - concur_reindex_part_index_0 | I | relfilenode is unchanged - concur_reindex_part_index_0_1 | i | relfilenode has changed - concur_reindex_part_index_0_2 | i | relfilenode has changed - concur_reindex_part_index_10 | I | relfilenode is unchanged -(5 rows) - -DROP TABLE reindex_index_status; --- concurrent case. -SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index'); - create_relfilenode_part -------------------------- - -(1 row) - -REINDEX INDEX CONCURRENTLY concur_reindex_part_index; -SELECT * FROM compare_relfilenode_part('reindex_index_status'); - relname | relkind | state --------------------------------+---------+-------------------------- - concur_reindex_part_index | I | relfilenode is unchanged - concur_reindex_part_index_0 | I | relfilenode is unchanged - concur_reindex_part_index_0_1 | i | relfilenode has changed - concur_reindex_part_index_0_2 | i | relfilenode has changed - concur_reindex_part_index_10 | I | relfilenode is unchanged -(5 rows) - -DROP TABLE reindex_index_status; --- REINDEX for partitioned tables --- REINDEX INDEX fails for partitioned tables --- Top-most parent -REINDEX INDEX concur_reindex_part; -- error -ERROR: "concur_reindex_part" is not an index -REINDEX INDEX CONCURRENTLY concur_reindex_part; -- error -ERROR: "concur_reindex_part" is not an index --- Partitioned with no leaves -REINDEX INDEX concur_reindex_part_10; -- error -ERROR: "concur_reindex_part_10" is not an index -REINDEX INDEX CONCURRENTLY concur_reindex_part_10; -- error -ERROR: "concur_reindex_part_10" is not an index --- Cannot run in a transaction block -BEGIN; -REINDEX TABLE concur_reindex_part; -ERROR: REINDEX TABLE cannot run inside a transaction block -CONTEXT: while reindexing partitioned table "public.concur_reindex_part" -ROLLBACK; --- Check that expected relfilenodes are changed, non-concurrent case. --- Note that the partition tree changes of the *indexes* need to be checked. -SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index'); - create_relfilenode_part -------------------------- - -(1 row) - -REINDEX TABLE concur_reindex_part; -SELECT * FROM compare_relfilenode_part('reindex_index_status'); - relname | relkind | state --------------------------------+---------+-------------------------- - concur_reindex_part_index | I | relfilenode is unchanged - concur_reindex_part_index_0 | I | relfilenode is unchanged - concur_reindex_part_index_0_1 | i | relfilenode has changed - concur_reindex_part_index_0_2 | i | relfilenode has changed - concur_reindex_part_index_10 | I | relfilenode is unchanged -(5 rows) - -DROP TABLE reindex_index_status; --- concurrent case. -SELECT create_relfilenode_part('reindex_index_status', 'concur_reindex_part_index'); - create_relfilenode_part -------------------------- - -(1 row) - -REINDEX TABLE CONCURRENTLY concur_reindex_part; -SELECT * FROM compare_relfilenode_part('reindex_index_status'); - relname | relkind | state --------------------------------+---------+-------------------------- - concur_reindex_part_index | I | relfilenode is unchanged - concur_reindex_part_index_0 | I | relfilenode is unchanged - concur_reindex_part_index_0_1 | i | relfilenode has changed - concur_reindex_part_index_0_2 | i | relfilenode has changed - concur_reindex_part_index_10 | I | relfilenode is unchanged -(5 rows) - -DROP TABLE reindex_index_status; -DROP FUNCTION create_relfilenode_part; -DROP FUNCTION compare_relfilenode_part; --- Cleanup of partition tree used for REINDEX test. -DROP TABLE concur_reindex_part; --- Check errors --- Cannot run inside a transaction block -BEGIN; -REINDEX TABLE CONCURRENTLY concur_reindex_tab; -ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block -COMMIT; -REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation -ERROR: cannot reindex system catalogs concurrently -REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index -ERROR: cannot reindex system catalogs concurrently --- These are the toast table and index of pg_authid. -REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table -ERROR: cannot reindex system catalogs concurrently -REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index -ERROR: cannot reindex system catalogs concurrently -REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM -ERROR: cannot reindex system catalogs concurrently -REINDEX (CONCURRENTLY) SYSTEM postgres; -- ditto -ERROR: cannot reindex system catalogs concurrently -REINDEX (CONCURRENTLY) SYSTEM; -- ditto -ERROR: cannot reindex system catalogs concurrently --- Warns about catalog relations -REINDEX SCHEMA CONCURRENTLY pg_catalog; -WARNING: cannot reindex system catalogs concurrently, skipping all --- Not the current database -REINDEX DATABASE not_current_database; -ERROR: can only reindex the currently open database --- Check the relation status, there should not be invalid indexes -\d concur_reindex_tab - Table "public.concur_reindex_tab" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | not null | - c2 | text | | | -Indexes: - "concur_reindex_ind1" PRIMARY KEY, btree (c1) - "concur_reindex_ind2" btree (c2) - "concur_reindex_ind3" UNIQUE, btree (abs(c1)) - "concur_reindex_ind4" btree (c1, c1, c2) -Referenced by: - TABLE "concur_reindex_tab2" CONSTRAINT "concur_reindex_tab2_c1_fkey" FOREIGN KEY (c1) REFERENCES concur_reindex_tab(c1) - -DROP MATERIALIZED VIEW concur_reindex_matview; -DROP TABLE concur_reindex_tab, concur_reindex_tab2, concur_reindex_tab3; --- Check handling of invalid indexes -CREATE TABLE concur_reindex_tab4 (c1 int); -INSERT INTO concur_reindex_tab4 VALUES (1), (1), (2); --- This trick creates an invalid index. -CREATE UNIQUE INDEX CONCURRENTLY concur_reindex_ind5 ON concur_reindex_tab4 (c1); -ERROR: could not create unique index "concur_reindex_ind5" -DETAIL: Key (c1)=(1) is duplicated. --- Reindexing concurrently this index fails with the same failure. --- The extra index created is itself invalid, and can be dropped. -REINDEX INDEX CONCURRENTLY concur_reindex_ind5; -ERROR: could not create unique index "concur_reindex_ind5_ccnew" -DETAIL: Key (c1)=(1) is duplicated. -\d concur_reindex_tab4 - Table "public.concur_reindex_tab4" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | -Indexes: - "concur_reindex_ind5" UNIQUE, btree (c1) INVALID - "concur_reindex_ind5_ccnew" UNIQUE, btree (c1) INVALID - -DROP INDEX concur_reindex_ind5_ccnew; --- This makes the previous failure go away, so the index can become valid. -DELETE FROM concur_reindex_tab4 WHERE c1 = 1; --- The invalid index is not processed when running REINDEX TABLE. -REINDEX TABLE CONCURRENTLY concur_reindex_tab4; -WARNING: skipping reindex of invalid index "public.concur_reindex_ind5" -HINT: Use DROP INDEX or REINDEX INDEX. -NOTICE: table "concur_reindex_tab4" has no indexes that can be reindexed concurrently -\d concur_reindex_tab4 - Table "public.concur_reindex_tab4" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | -Indexes: - "concur_reindex_ind5" UNIQUE, btree (c1) INVALID - --- But it is fixed with REINDEX INDEX. -REINDEX INDEX CONCURRENTLY concur_reindex_ind5; -\d concur_reindex_tab4 - Table "public.concur_reindex_tab4" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - c1 | integer | | | -Indexes: - "concur_reindex_ind5" UNIQUE, btree (c1) - -DROP TABLE concur_reindex_tab4; --- Check handling of indexes with expressions and predicates. The --- definitions of the rebuilt indexes should match the original --- definitions. -CREATE TABLE concur_exprs_tab (c1 int , c2 boolean); -INSERT INTO concur_exprs_tab (c1, c2) VALUES (1369652450, FALSE), - (414515746, TRUE), - (897778963, FALSE); -CREATE UNIQUE INDEX concur_exprs_index_expr - ON concur_exprs_tab ((c1::text COLLATE "C")); -CREATE UNIQUE INDEX concur_exprs_index_pred ON concur_exprs_tab (c1) - WHERE (c1::text > 500000000::text COLLATE "C"); -CREATE UNIQUE INDEX concur_exprs_index_pred_2 - ON concur_exprs_tab ((1 / c1)) - WHERE ('-H') >= (c2::TEXT) COLLATE "C"; -ALTER INDEX concur_exprs_index_expr ALTER COLUMN 1 SET STATISTICS 100; -ANALYZE concur_exprs_tab; -SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( - 'concur_exprs_index_expr'::regclass, - 'concur_exprs_index_pred'::regclass, - 'concur_exprs_index_pred_2'::regclass) - GROUP BY starelid ORDER BY starelid::regclass::text; - starelid | count --------------------------+------- - concur_exprs_index_expr | 1 -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); - pg_get_indexdef ---------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C") -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass); - pg_get_indexdef ----------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C")) -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); - pg_get_indexdef --------------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= ((c2)::text COLLATE "C")) -(1 row) - -REINDEX TABLE CONCURRENTLY concur_exprs_tab; -SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); - pg_get_indexdef ---------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C") -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass); - pg_get_indexdef ----------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C")) -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); - pg_get_indexdef --------------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= ((c2)::text COLLATE "C")) -(1 row) - --- ALTER TABLE recreates the indexes, which should keep their collations. -ALTER TABLE concur_exprs_tab ALTER c2 TYPE TEXT; -SELECT pg_get_indexdef('concur_exprs_index_expr'::regclass); - pg_get_indexdef ---------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_expr ON public.concur_exprs_tab USING btree (((c1)::text) COLLATE "C") -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred'::regclass); - pg_get_indexdef ----------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred ON public.concur_exprs_tab USING btree (c1) WHERE ((c1)::text > ((500000000)::text COLLATE "C")) -(1 row) - -SELECT pg_get_indexdef('concur_exprs_index_pred_2'::regclass); - pg_get_indexdef ------------------------------------------------------------------------------------------------------------------------------------------- - CREATE UNIQUE INDEX concur_exprs_index_pred_2 ON public.concur_exprs_tab USING btree (((1 / c1))) WHERE ('-H'::text >= (c2 COLLATE "C")) -(1 row) - --- Statistics should remain intact. -SELECT starelid::regclass, count(*) FROM pg_statistic WHERE starelid IN ( - 'concur_exprs_index_expr'::regclass, - 'concur_exprs_index_pred'::regclass, - 'concur_exprs_index_pred_2'::regclass) - GROUP BY starelid ORDER BY starelid::regclass::text; - starelid | count --------------------------+------- - concur_exprs_index_expr | 1 -(1 row) - --- attstattarget should remain intact -SELECT attrelid::regclass, attnum, attstattarget - FROM pg_attribute WHERE attrelid IN ( - 'concur_exprs_index_expr'::regclass, - 'concur_exprs_index_pred'::regclass, - 'concur_exprs_index_pred_2'::regclass) - ORDER BY attrelid::regclass::text, attnum; - attrelid | attnum | attstattarget ----------------------------+--------+--------------- - concur_exprs_index_expr | 1 | 100 - concur_exprs_index_pred | 1 | - concur_exprs_index_pred_2 | 1 | -(3 rows) - -DROP TABLE concur_exprs_tab; --- Temporary tables and on-commit actions, where CONCURRENTLY is ignored. --- ON COMMIT PRESERVE ROWS, the default. -CREATE TEMP TABLE concur_temp_tab_1 (c1 int, c2 text) - ON COMMIT PRESERVE ROWS; -INSERT INTO concur_temp_tab_1 VALUES (1, 'foo'), (2, 'bar'); -CREATE INDEX concur_temp_ind_1 ON concur_temp_tab_1(c2); -REINDEX TABLE CONCURRENTLY concur_temp_tab_1; -REINDEX INDEX CONCURRENTLY concur_temp_ind_1; --- Still fails in transaction blocks -BEGIN; -REINDEX INDEX CONCURRENTLY concur_temp_ind_1; -ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block -COMMIT; --- ON COMMIT DELETE ROWS -CREATE TEMP TABLE concur_temp_tab_2 (c1 int, c2 text) - ON COMMIT DELETE ROWS; -CREATE INDEX concur_temp_ind_2 ON concur_temp_tab_2(c2); -REINDEX TABLE CONCURRENTLY concur_temp_tab_2; -REINDEX INDEX CONCURRENTLY concur_temp_ind_2; --- ON COMMIT DROP -BEGIN; -CREATE TEMP TABLE concur_temp_tab_3 (c1 int, c2 text) - ON COMMIT PRESERVE ROWS; -INSERT INTO concur_temp_tab_3 VALUES (1, 'foo'), (2, 'bar'); -CREATE INDEX concur_temp_ind_3 ON concur_temp_tab_3(c2); --- Fails when running in a transaction -REINDEX INDEX CONCURRENTLY concur_temp_ind_3; -ERROR: REINDEX CONCURRENTLY cannot run inside a transaction block -COMMIT; --- REINDEX SCHEMA processes all temporary relations -CREATE TABLE reindex_temp_before AS -SELECT oid, relname, relfilenode, relkind, reltoastrelid - FROM pg_class - WHERE relname IN ('concur_temp_ind_1', 'concur_temp_ind_2'); -SELECT pg_my_temp_schema()::regnamespace as temp_schema_name \gset -REINDEX SCHEMA CONCURRENTLY :temp_schema_name; -SELECT b.relname, - b.relkind, - CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged' - ELSE 'relfilenode has changed' END - FROM reindex_temp_before b JOIN pg_class a ON b.oid = a.oid - ORDER BY 1; - relname | relkind | case --------------------+---------+------------------------- - concur_temp_ind_1 | i | relfilenode has changed - concur_temp_ind_2 | i | relfilenode has changed -(2 rows) - -DROP TABLE concur_temp_tab_1, concur_temp_tab_2, reindex_temp_before; --- --- REINDEX SCHEMA --- -REINDEX SCHEMA schema_to_reindex; -- failure, schema does not exist -ERROR: schema "schema_to_reindex" does not exist -CREATE SCHEMA schema_to_reindex; -SET search_path = 'schema_to_reindex'; -CREATE TABLE table1(col1 SERIAL PRIMARY KEY); -INSERT INTO table1 SELECT generate_series(1,400); -CREATE TABLE table2(col1 SERIAL PRIMARY KEY, col2 TEXT NOT NULL); -INSERT INTO table2 SELECT generate_series(1,400), 'abc'; -CREATE INDEX ON table2(col2); -CREATE MATERIALIZED VIEW matview AS SELECT col1 FROM table2; -CREATE INDEX ON matview(col1); -CREATE VIEW view AS SELECT col2 FROM table2; -CREATE TABLE reindex_before AS -SELECT oid, relname, relfilenode, relkind, reltoastrelid - FROM pg_class - where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex'); -INSERT INTO reindex_before -SELECT oid, 'pg_toast_TABLE', relfilenode, relkind, reltoastrelid -FROM pg_class WHERE oid IN - (SELECT reltoastrelid FROM reindex_before WHERE reltoastrelid > 0); -INSERT INTO reindex_before -SELECT oid, 'pg_toast_TABLE_index', relfilenode, relkind, reltoastrelid -FROM pg_class where oid in - (select indexrelid from pg_index where indrelid in - (select reltoastrelid from reindex_before where reltoastrelid > 0)); -REINDEX SCHEMA schema_to_reindex; -CREATE TABLE reindex_after AS SELECT oid, relname, relfilenode, relkind - FROM pg_class - where relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'schema_to_reindex'); -SELECT b.relname, - b.relkind, - CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged' - ELSE 'relfilenode has changed' END - FROM reindex_before b JOIN pg_class a ON b.oid = a.oid - ORDER BY 1; - relname | relkind | case -----------------------+---------+-------------------------- - matview | m | relfilenode is unchanged - matview_col1_idx | i | relfilenode has changed - pg_toast_TABLE | t | relfilenode is unchanged - pg_toast_TABLE_index | i | relfilenode has changed - table1 | r | relfilenode is unchanged - table1_col1_seq | S | relfilenode is unchanged - table1_pkey | i | relfilenode has changed - table2 | r | relfilenode is unchanged - table2_col1_seq | S | relfilenode is unchanged - table2_col2_idx | i | relfilenode has changed - table2_pkey | i | relfilenode has changed - view | v | relfilenode is unchanged -(12 rows) - -REINDEX SCHEMA schema_to_reindex; -BEGIN; -REINDEX SCHEMA schema_to_reindex; -- failure, cannot run in a transaction -ERROR: REINDEX SCHEMA cannot run inside a transaction block -END; --- concurrently -REINDEX SCHEMA CONCURRENTLY schema_to_reindex; --- Failure for unauthorized user -CREATE ROLE regress_reindexuser NOLOGIN; -SET SESSION ROLE regress_reindexuser; -REINDEX SCHEMA schema_to_reindex; -ERROR: must be owner of schema schema_to_reindex --- Permission failures with toast tables and indexes (pg_authid here) -RESET ROLE; -GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser; -SET SESSION ROLE regress_reindexuser; -REINDEX TABLE pg_toast.pg_toast_1260; -ERROR: permission denied for table pg_toast_1260 -REINDEX INDEX pg_toast.pg_toast_1260_index; -ERROR: permission denied for index pg_toast_1260_index --- Clean up -RESET ROLE; -REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser; -DROP ROLE regress_reindexuser; -DROP SCHEMA schema_to_reindex CASCADE; -NOTICE: drop cascades to 6 other objects -DETAIL: drop cascades to table table1 -drop cascades to table table2 -drop cascades to materialized view matview -drop cascades to view view -drop cascades to table reindex_before -drop cascades to table reindex_after From aeaae4e282aed1a839362b45873f07d6ccf8fdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 25 Apr 2025 15:32:08 +0200 Subject: [PATCH 124/796] Remove double declarations For some reason these functions were declared twice, once using the macro and once without it. --- contrib/pg_tde/src/catalog/tde_keyring.c | 11 ----------- contrib/pg_tde/src/catalog/tde_principal_key.c | 7 ------- 2 files changed, 18 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 42f66ce220f49..d3ee16ff809f1 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -75,24 +75,13 @@ static void simple_list_free(SimplePtrList *list); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); PG_FUNCTION_INFO_V1(pg_tde_add_database_key_provider); -Datum pg_tde_add_database_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_add_global_key_provider); -Datum pg_tde_add_global_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_change_database_key_provider); -Datum pg_tde_change_database_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_change_global_key_provider); -Datum pg_tde_change_global_key_provider(PG_FUNCTION_ARGS); - static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pg_tde_list_all_database_key_providers); -Datum pg_tde_list_all_database_key_providers(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); -Datum pg_tde_list_all_global_key_providers(PG_FUNCTION_ARGS); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 8fdc3b5700ad1..149c6c1d6b410 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -112,16 +112,9 @@ static bool pg_tde_verify_principal_key_internal(Oid databaseOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global); PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); -Datum pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); -Datum pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); -Datum pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); -Datum pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS); enum global_status { From c1da75633541fa35e5ef9b265d0c991109efd1d7 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 21 Apr 2025 21:30:55 +0200 Subject: [PATCH 125/796] Specifiy extensions required for tde test in EXTRA_INSTALL To run pg_tde tests with `make check` we have to add pg_buffercache and test_decoding extensions to temporary pg installation. --- contrib/pg_tde/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index e31267a6365bf..f71d289593974 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -53,6 +53,7 @@ src/libkmip/libkmip/src/kmip_memset.o SCRIPTS_built = src/pg_tde_change_key_provider +EXTRA_INSTALL+=contrib/pg_buffercache contrib/test_decoding EXTRA_CLEAN += src/pg_tde_change_key_provider.o ifdef USE_PGXS From 198af9e3571ecd3fc3f5812dbd6c0cfd095bcc87 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 18 Apr 2025 17:09:02 +0200 Subject: [PATCH 126/796] Extract code coverage jobs into separate CI workflow --- .github/workflows/coverage.yml | 56 ++++++++++++++++++++ .github/workflows/psp-reusable.yml | 82 ------------------------------ ci_scripts/make-test.sh | 11 ++-- 3 files changed, 64 insertions(+), 85 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000000000..965c60a3e8f7e --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,56 @@ +name: Code coverage +on: + pull_request: + push: + branches: + - TDE_REL_17_STABLE + +jobs: + collect: + name: Collect and upload + runs-on: ubuntu-22.04 + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: ci_scripts/ubuntu-deps.sh + + - name: Build postgres + run: ci_scripts/make-build.sh debug --enable-coverage + + - name: Setup kmip and vault + run: ci_scripts/setup-keyring-servers.sh + + - name: Test postgres with TDE to generate coverage + run: ci_scripts/make-test-tde.sh --continue --tde-only + + - name: Collect coverage data + run: find . -type f -name "*.c" ! -path '*libkmip*' | xargs -t gcov -abcfu + working-directory: contrib/pg_tde + + - name: Upload coverage data to codecov.io + uses: codecov/codecov-action@v5 + with: + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + working-directory: contrib/pg_tde + files: "*.c.gcov" + + - name: Report on test fail + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: coverage-testlog-tde + path: | + build/testrun/ + contrib/pg_tde/t/ + contrib/pg_tde/results + contrib/pg_tde/regression.diffs + contrib/pg_tde/regression.out + contrib/pg_tde/*.gcov + retention-days: 3 + + diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index a851a1d0176ef..554ae6c738cc3 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -14,7 +14,6 @@ on: env: artifact_name: build-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} - coverage_artifact_name: coverage-build-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} jobs: build: @@ -46,36 +45,6 @@ jobs: artifacts.tar retention-days: 1 - build-coverage: - name: Build PSP for Coverage - runs-on: ${{ inputs.os }} - if: inputs.build_script == 'make' && inputs.build_type == 'debug' - steps: - - name: Clone repository - uses: actions/checkout@v4 - with: - path: 'src' - submodules: recursive - ref: ${{ github.ref }} - - - name: Install dependencies - run: src/ci_scripts/ubuntu-deps.sh - - - name: Build postgres - run: src/ci_scripts/${{ inputs.build_script }}-build.sh ${{ inputs.build_type }} --enable-coverage - - - name: Archive pginst to artifact tar file - run: tar -czf coverage-artifacts.tar src pginst - - - name: Upload build coverage-artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ env.coverage_artifact_name }} - overwrite: true - path: | - coverage-artifacts.tar - retention-days: 1 - test: name: Test PSP runs-on: ${{ inputs.os }} @@ -150,54 +119,3 @@ jobs: src/contrib/*/regression.diffs src/contrib/*/regression.out retention-days: 3 - - test_tde_coverage: - name: Generate Codecov Code Coverage - runs-on: ${{ inputs.os }} - if: inputs.build_script == 'make' && inputs.build_type == 'debug' - needs: build - - steps: - - name: Download build coverage-artifacts - uses: actions/download-artifact@v4 - with: - name: ${{ env.coverage_artifact_name }} - path: . - - - name: Extract artifact file - run: tar -xzf coverage-artifacts.tar - - - name: Install dependencies - run: src/ci_scripts/ubuntu-deps.sh - - - name: Setup kmip and vault - run: src/ci_scripts/setup-keyring-servers.sh - - - name: Test postgres with TDE to generate coverage - run: src/ci_scripts/${{ inputs.build_script }}-test-tde.sh --continue --tde-only - - - name: Run code coverage - run: find src/ -type f -name "*.c" ! -path '*libkmip*' | xargs -t gcov -abcfu - working-directory: src/contrib/pg_tde - - - name: Upload coverage data to codecov.io - uses: codecov/codecov-action@v5 - with: - verbose: true - token: ${{ secrets.CODECOV_TOKEN }} - working-directory: src/contrib/pg_tde - files: "*.c.gcov" - - - name: Report on test fail - uses: actions/upload-artifact@v4 - if: ${{ failure() }} - with: - name: coverage-testlog-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} - path: | - src/build/testrun/ - src/contrib/pg_tde/t/ - src/contrib/pg_tde/results - src/contrib/pg_tde/regression.diffs - src/contrib/pg_tde/regression.out - src/contrib/pg_tde/*.gcov - retention-days: 3 diff --git a/ci_scripts/make-test.sh b/ci_scripts/make-test.sh index 52c0aa58670bb..d82574d3ab690 100755 --- a/ci_scripts/make-test.sh +++ b/ci_scripts/make-test.sh @@ -4,6 +4,11 @@ SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" source $SCRIPT_DIR/env.sh -cd "$SCRIPT_DIR/.." - -make -s check-world +if [ "$TDE_ONLY" -eq 1 ]; +then + cd "$SCRIPT_DIR/../contrib/pg_tde" + make -s check +else + cd "$SCRIPT_DIR/.." + make -s check-world +fi From 082cc11c8c64f595ab1f7547bde163b5c5ed447b Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 21 Apr 2025 18:39:24 +0200 Subject: [PATCH 127/796] Run simple tests for code coverage needs --- .github/workflows/coverage.yml | 2 +- .github/workflows/psp-reusable.yml | 4 +-- ...-tde-server.sh => configure-global-tde.sh} | 0 ci_scripts/make-test-global-tde.sh | 19 ++++++++++++ ci_scripts/make-test-tde.sh | 30 ------------------- ci_scripts/make-test.sh | 12 ++++++++ 6 files changed, 34 insertions(+), 33 deletions(-) rename ci_scripts/{configure-tde-server.sh => configure-global-tde.sh} (100%) create mode 100755 ci_scripts/make-test-global-tde.sh delete mode 100755 ci_scripts/make-test-tde.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 965c60a3e8f7e..b01c01826db24 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -25,7 +25,7 @@ jobs: run: ci_scripts/setup-keyring-servers.sh - name: Test postgres with TDE to generate coverage - run: ci_scripts/make-test-tde.sh --continue --tde-only + run: ci_scripts/make-test.sh --tde-only - name: Collect coverage data run: find . -type f -name "*.c" ! -path '*libkmip*' | xargs -t gcov -abcfu diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index 554ae6c738cc3..4af5cb3b32036 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -104,8 +104,8 @@ jobs: - name: Setup kmip and vault run: src/ci_scripts/setup-keyring-servers.sh - - name: Test postgres with TDE - run: src/ci_scripts/${{ inputs.build_script }}-test-tde.sh --continue + - name: Test postgres with TDE as default access method + run: src/ci_scripts/${{ inputs.build_script }}-test-global-tde.sh --continue - name: Report on test fail uses: actions/upload-artifact@v4 diff --git a/ci_scripts/configure-tde-server.sh b/ci_scripts/configure-global-tde.sh similarity index 100% rename from ci_scripts/configure-tde-server.sh rename to ci_scripts/configure-global-tde.sh diff --git a/ci_scripts/make-test-global-tde.sh b/ci_scripts/make-test-global-tde.sh new file mode 100755 index 0000000000000..4c8e72c0fa617 --- /dev/null +++ b/ci_scripts/make-test-global-tde.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e +ADD_FLAGS= + +for arg in "$@" +do + case "$arg" in + --continue) + ADD_FLAGS="-k" + shift;; + esac +done + +SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +source $SCRIPT_DIR/env.sh +source $SCRIPT_DIR/configure-global-tde.sh + +EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck-world $ADD_FLAGS diff --git a/ci_scripts/make-test-tde.sh b/ci_scripts/make-test-tde.sh deleted file mode 100755 index 17568c0b4756d..0000000000000 --- a/ci_scripts/make-test-tde.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -set -e -ADD_FLAGS= -TDE_ONLY=0 - -for arg in "$@" -do - case "$arg" in - --continue) - ADD_FLAGS="-k" - shift;; - --tde-only) - TDE_ONLY=1 - shift;; - esac -done - -SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" -source $SCRIPT_DIR/env.sh -source $SCRIPT_DIR/configure-tde-server.sh - -if [ "$TDE_ONLY" -eq 1 ]; -then - cd "$SCRIPT_DIR/../contrib/pg_tde" - EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck $ADD_FLAGS -else - cd "$SCRIPT_DIR/.." - EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck-world $ADD_FLAGS -fi diff --git a/ci_scripts/make-test.sh b/ci_scripts/make-test.sh index d82574d3ab690..ea6377496f3b5 100755 --- a/ci_scripts/make-test.sh +++ b/ci_scripts/make-test.sh @@ -1,5 +1,17 @@ #!/bin/bash +set -e +TDE_ONLY=0 + +for arg in "$@" +do + case "$arg" in + --tde-only) + TDE_ONLY=1 + shift;; + esac +done + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" source $SCRIPT_DIR/env.sh From b7d52ab9a127ec4c0e25d23d87ce98b32f1032c8 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 18 Apr 2025 17:24:59 +0200 Subject: [PATCH 128/796] Highlight that code coverage badge in main readme file related to pg_tde --- README.md | 2 +- contrib/pg_tde/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 139bf5cfd8008..4501f53904c77 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![codecov](https://codecov.io/github/percona/postgres/graph/badge.svg?token=Wow78BMYdP)](https://codecov.io/github/percona/postgres) +### Code coverage for [pg_tde](https://github.com/percona/postgres/tree/TDE_REL_17_STABLE/contrib/pg_tde): [![codecov](https://codecov.io/github/percona/postgres/graph/badge.svg?token=Wow78BMYdP)](https://codecov.io/github/percona/postgres) Percona Server for PostgreSQL ============================= diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index 437388716c5bb..ec367e2cdcab3 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -1,4 +1,5 @@ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/percona/pg_tde/badge)](https://scorecard.dev/viewer/?uri=github.com/percona/pg_tde) +[![codecov](https://codecov.io/github/percona/postgres/graph/badge.svg?token=Wow78BMYdP)](https://codecov.io/github/percona/postgres) [![Forum](https://img.shields.io/badge/Forum-join-brightgreen)](https://forums.percona.com/) # pg_tde: Transparent Database Encryption for PostgreSQL From d602c2ec677f14799cb9bf181d1918d81d66a414 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 15:53:14 +0200 Subject: [PATCH 129/796] Remove unused return value from check_percona_api_version() There is no user of this return value and furthermore the function can only ever return true. --- src/include/utils/percona.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/include/utils/percona.h b/src/include/utils/percona.h index 87bad00df784a..e8de53618c551 100644 --- a/src/include/utils/percona.h +++ b/src/include/utils/percona.h @@ -14,16 +14,13 @@ extern const PGDLLIMPORT int percona_api_version; -static inline bool +static inline void check_percona_api_version(void) { if (PERCONA_API_VERSION != percona_api_version) { elog(FATAL, "Percona API version mismatch, the extension was built against a different PostgreSQL version!"); - return false; } - - return true; } #endif From d51c49899146b87396419521cf9902eec29b19c2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 15:56:52 +0200 Subject: [PATCH 130/796] Remove leftovers from when we remove tde_heap_basic These pgindent excludes are no longer relevant. --- src/tools/pgindent/exclude_file_patterns | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/pgindent/exclude_file_patterns b/src/tools/pgindent/exclude_file_patterns index 0746df4737200..5c25e98900418 100644 --- a/src/tools/pgindent/exclude_file_patterns +++ b/src/tools/pgindent/exclude_file_patterns @@ -64,7 +64,5 @@ src/tools/pg_bsd_indent/.* # ... and for paranoia's sake, don't touch git stuff. /\.git/ # Percona excludes -contrib/pg_tde/src16/.* -contrib/pg_tde/src17.* contrib/pg_tde/src/libkmip/.* src/backend/nodes/nodetags.h From f1d12f17b1b018db1b08e47d302f258d34787f89 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 16:03:23 +0200 Subject: [PATCH 131/796] Stop ignoring nodtags.h for pgindent Since upstream does not ignore it neither should we. --- src/tools/pgindent/exclude_file_patterns | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/pgindent/exclude_file_patterns b/src/tools/pgindent/exclude_file_patterns index 5c25e98900418..76fd671dc95eb 100644 --- a/src/tools/pgindent/exclude_file_patterns +++ b/src/tools/pgindent/exclude_file_patterns @@ -65,4 +65,3 @@ src/tools/pg_bsd_indent/.* /\.git/ # Percona excludes contrib/pg_tde/src/libkmip/.* -src/backend/nodes/nodetags.h From 435ef90a3557e846879bc9ccbb2d5556b02f6ea0 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 16:00:54 +0200 Subject: [PATCH 132/796] Move pg_tde_change_key_provider .gitignore entry The .gitignore entry was left in the old location when the source for the executable was moved. --- contrib/pg_tde/.gitignore | 1 + src/bin/pg_tde_change_key_provider/.gitignore | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/bin/pg_tde_change_key_provider/.gitignore diff --git a/contrib/pg_tde/.gitignore b/contrib/pg_tde/.gitignore index 4bc8580d6e00f..42745ca9a4aaf 100644 --- a/contrib/pg_tde/.gitignore +++ b/contrib/pg_tde/.gitignore @@ -9,6 +9,7 @@ __pycache__ /config.status /autom4te.cache /configure~ +/src/pg_tde_change_key_provider /t/results /results /tmp_check diff --git a/src/bin/pg_tde_change_key_provider/.gitignore b/src/bin/pg_tde_change_key_provider/.gitignore deleted file mode 100644 index 028680dcbc3e8..0000000000000 --- a/src/bin/pg_tde_change_key_provider/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/pg_tde_change_key_provider From e355f3827d5277b5a2495e67e88bc82b767611a1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 16:03:09 +0200 Subject: [PATCH 133/796] Clean up .gitignore --- contrib/pg_tde/.gitignore | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/.gitignore b/contrib/pg_tde/.gitignore index 42745ca9a4aaf..044af8f3c5cae 100644 --- a/contrib/pg_tde/.gitignore +++ b/contrib/pg_tde/.gitignore @@ -4,16 +4,16 @@ __pycache__ .vscode +/autom4te.cache /config.cache /config.log /config.status -/autom4te.cache /configure~ +/log +/results /src/pg_tde_change_key_provider /t/results -/results /tmp_check -/log -# tools files -typedefs-full.list +# Tool files +/typedefs-full.list From 5f64c75c5e60f94c8aa07ceb7d3ee503c6d486bb Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 04:10:51 +0200 Subject: [PATCH 134/796] Make failure artifact names consistent in Github Actions One of them even always had meson in the name even when make was ran. --- .github/workflows/psp-reusable.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index 4af5cb3b32036..ad23dcbe8af3b 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -73,7 +73,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: testlog-ubuntu-${{ inputs.ubuntu_version }}.04-meson-${{ inputs.build_type }} + name: log-test-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun/ src/contrib/*/t/ @@ -111,7 +111,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: testlog-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + name: log-test-global-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun/ src/contrib/*/t/ From 5a1fe7650d275013f4c674a0d1796a7db317049d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 04:11:17 +0200 Subject: [PATCH 135/796] Add more test result and logs to the Github failure artifact Most of the result files and the logs were missing from the artifact created when running make check-world. --- .github/workflows/psp-reusable.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index ad23dcbe8af3b..44742bab3ffc1 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -76,10 +76,17 @@ jobs: name: log-test-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun/ - src/contrib/*/t/ - src/contrib/*/results + src/contrib/*/log src/contrib/*/regression.diffs src/contrib/*/regression.out + src/contrib/*/results + src/contrib/*/tmp_check + src/contrib/*/t/results + src/src/test/*/log + src/src/test/*/regression.diffs + src/src/test/*/regression.out + src/src/test/*/results + src/src/test/*/tmp_check retention-days: 3 test_tde: @@ -114,8 +121,15 @@ jobs: name: log-test-global-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun/ - src/contrib/*/t/ - src/contrib/*/results + src/contrib/*/log src/contrib/*/regression.diffs src/contrib/*/regression.out + src/contrib/*/results + src/contrib/*/tmp_check + src/contrib/*/t/results + src/src/test/*/log + src/src/test/*/regression.diffs + src/src/test/*/regression.out + src/src/test/*/results + src/src/test/*/tmp_check retention-days: 3 From 6fef8bdf70897ad9da532f9d9e6f2a574dbf8e7a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 02:38:46 +0200 Subject: [PATCH 136/796] Rename event triggers to something more expressive Instead of giving them numbers we call them pg_tde_ddl_start and pg_tde_ddl_end. Since the triggers are not on the same event the names do not matter for the order they are executed in. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 8 ++++---- src/test/regress/expected/event_trigger_1.out | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index fc903e27cbd76..dcd73b0ad675e 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -515,15 +515,15 @@ RETURNS event_trigger LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE EVENT TRIGGER pg_tde_trigger_create_index +CREATE EVENT TRIGGER pg_tde_ddl_start ON ddl_command_start EXECUTE FUNCTION pg_tde_ddl_command_start_capture(); -ALTER EVENT TRIGGER pg_tde_trigger_create_index ENABLE ALWAYS; +ALTER EVENT TRIGGER pg_tde_ddl_start ENABLE ALWAYS; -CREATE EVENT TRIGGER pg_tde_trigger_create_index_2 +CREATE EVENT TRIGGER pg_tde_ddl_end ON ddl_command_end EXECUTE FUNCTION pg_tde_ddl_command_end_capture(); -ALTER EVENT TRIGGER pg_tde_trigger_create_index_2 ENABLE ALWAYS; +ALTER EVENT TRIGGER pg_tde_ddl_end ENABLE ALWAYS; -- Per database extension initialization SELECT pg_tde_extension_initialize(); diff --git a/src/test/regress/expected/event_trigger_1.out b/src/test/regress/expected/event_trigger_1.out index 9786bc0a00ac4..fdf2ca0124546 100644 --- a/src/test/regress/expected/event_trigger_1.out +++ b/src/test/regress/expected/event_trigger_1.out @@ -708,13 +708,13 @@ SELECT LATERAL pg_identify_object_as_address('pg_event_trigger'::regclass, e.oid, 0) as b, LATERAL pg_get_object_address(b.type, b.object_names, b.object_args) as a ORDER BY e.evtname; - evtname | descr | type | object_names | object_args | ident --------------------------------+---------------------------------------------+---------------+---------------------------------+-------------+-------------------------------------------------------------------------------- - end_rls_command | event trigger end_rls_command | event trigger | {end_rls_command} | {} | ("event trigger",,end_rls_command,end_rls_command) - pg_tde_trigger_create_index | event trigger pg_tde_trigger_create_index | event trigger | {pg_tde_trigger_create_index} | {} | ("event trigger",,pg_tde_trigger_create_index,pg_tde_trigger_create_index) - pg_tde_trigger_create_index_2 | event trigger pg_tde_trigger_create_index_2 | event trigger | {pg_tde_trigger_create_index_2} | {} | ("event trigger",,pg_tde_trigger_create_index_2,pg_tde_trigger_create_index_2) - sql_drop_command | event trigger sql_drop_command | event trigger | {sql_drop_command} | {} | ("event trigger",,sql_drop_command,sql_drop_command) - start_rls_command | event trigger start_rls_command | event trigger | {start_rls_command} | {} | ("event trigger",,start_rls_command,start_rls_command) + evtname | descr | type | object_names | object_args | ident +-------------------+---------------------------------+---------------+---------------------+-------------+-------------------------------------------------------- + end_rls_command | event trigger end_rls_command | event trigger | {end_rls_command} | {} | ("event trigger",,end_rls_command,end_rls_command) + pg_tde_ddl_end | event trigger pg_tde_ddl_end | event trigger | {pg_tde_ddl_end} | {} | ("event trigger",,pg_tde_ddl_end,pg_tde_ddl_end) + pg_tde_ddl_start | event trigger pg_tde_ddl_start | event trigger | {pg_tde_ddl_start} | {} | ("event trigger",,pg_tde_ddl_start,pg_tde_ddl_start) + sql_drop_command | event trigger sql_drop_command | event trigger | {sql_drop_command} | {} | ("event trigger",,sql_drop_command,sql_drop_command) + start_rls_command | event trigger start_rls_command | event trigger | {start_rls_command} | {} | ("event trigger",,start_rls_command,start_rls_command) (5 rows) DROP EVENT TRIGGER start_rls_command; From 712dcf597635614a8f8f4b79befd58f2f3982230 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 10:36:29 +0200 Subject: [PATCH 137/796] Clean up some switch statements and if chains In tde_keyring.c we do a lot of switching on the keyring type, some of it which could be done in a slightly nicer way. --- contrib/pg_tde/src/catalog/tde_keyring.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index d3ee16ff809f1..7637662de5943 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -165,9 +165,8 @@ get_keyring_provider_typename(ProviderType p_type) case KMIP_KEY_PROVIDER: return KMIP_KEYRING_TYPE; default: - break; + return NULL; } - return NULL; } static List * @@ -770,17 +769,13 @@ load_keyring_provider_options(ProviderType provider_type, char *keyring_options) { case FILE_KEY_PROVIDER: return (GenericKeyring *) load_file_keyring_provider_options(keyring_options); - break; case VAULT_V2_KEY_PROVIDER: return (GenericKeyring *) load_vaultV2_keyring_provider_options(keyring_options); - break; case KMIP_KEY_PROVIDER: return (GenericKeyring *) load_kmip_keyring_provider_options(keyring_options); - break; default: - break; + return NULL; } - return NULL; } static FileKeyring * @@ -952,16 +947,14 @@ fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider ProviderType get_keyring_provider_from_typename(char *provider_type) { - if (provider_type == NULL) - return UNKNOWN_KEY_PROVIDER; - if (strcmp(FILE_KEYRING_TYPE, provider_type) == 0) return FILE_KEY_PROVIDER; - if (strcmp(VAULTV2_KEYRING_TYPE, provider_type) == 0) + else if (strcmp(VAULTV2_KEYRING_TYPE, provider_type) == 0) return VAULT_V2_KEY_PROVIDER; - if (strcmp(KMIP_KEYRING_TYPE, provider_type) == 0) + else if (strcmp(KMIP_KEYRING_TYPE, provider_type) == 0) return KMIP_KEY_PROVIDER; - return UNKNOWN_KEY_PROVIDER; + else + return UNKNOWN_KEY_PROVIDER; } GenericKeyring * From e979ab71c27fd165c6d2736cd9916b631fbf0581 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 23:15:40 +0200 Subject: [PATCH 138/796] Harmonize the different Github Actions entry points The meson and the make scripts had diverged a lot, so this commit fixes that plus some other inconsistencies. --- ci_scripts/configure-global-tde.sh | 1 + ci_scripts/make-build.sh | 4 ++-- ci_scripts/make-test-global-tde.sh | 3 +-- ci_scripts/make-test.sh | 5 ++--- ci_scripts/meson-build.sh | 15 +++++++++++++- ...n-test-tde.sh => meson-test-global-tde.sh} | 0 ci_scripts/meson-test.sh | 20 ++++++++++++++++++- 7 files changed, 39 insertions(+), 9 deletions(-) rename ci_scripts/{meson-test-tde.sh => meson-test-global-tde.sh} (100%) diff --git a/ci_scripts/configure-global-tde.sh b/ci_scripts/configure-global-tde.sh index 7ce012f7c7c43..09c64082bcfbc 100644 --- a/ci_scripts/configure-global-tde.sh +++ b/ci_scripts/configure-global-tde.sh @@ -7,6 +7,7 @@ source $SCRIPT_DIR/env.sh cd "$SCRIPT_DIR/.." +source "$SCRIPT_DIR/env.sh" export PATH=$INSTALL_DIR/bin:$PATH export DATA_DIR=$INSTALL_DIR/data export PGDATA="${1:-$DATA_DIR}" diff --git a/ci_scripts/make-build.sh b/ci_scripts/make-build.sh index 799fd2364a2be..c99e77a051bb1 100755 --- a/ci_scripts/make-build.sh +++ b/ci_scripts/make-build.sh @@ -13,7 +13,7 @@ done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" -source $SCRIPT_DIR/env.sh +source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/.." @@ -22,5 +22,5 @@ if [ "$1" = "debugoptimized" ]; then export CXXFLAGS="-O2" fi -./configure --enable-debug --enable-cassert --enable-tap-tests --prefix=$INSTALL_DIR $ENABLE_COVERAGE +./configure --prefix="$INSTALL_DIR" --enable-debug --enable-cassert --enable-tap-tests $ENABLE_COVERAGE make install-world -j diff --git a/ci_scripts/make-test-global-tde.sh b/ci_scripts/make-test-global-tde.sh index 4c8e72c0fa617..ee4a8787fbf5c 100755 --- a/ci_scripts/make-test-global-tde.sh +++ b/ci_scripts/make-test-global-tde.sh @@ -13,7 +13,6 @@ do done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" -source $SCRIPT_DIR/env.sh -source $SCRIPT_DIR/configure-global-tde.sh +source "$SCRIPT_DIR/configure-global-tde.sh" EXTRA_REGRESS_OPTS="--extra-setup=$SCRIPT_DIR/tde_setup.sql" make -s installcheck-world $ADD_FLAGS diff --git a/ci_scripts/make-test.sh b/ci_scripts/make-test.sh index ea6377496f3b5..6ce136dd28bca 100755 --- a/ci_scripts/make-test.sh +++ b/ci_scripts/make-test.sh @@ -12,9 +12,8 @@ do esac done -SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" -INSTALL_DIR="$SCRIPT_DIR/../../pginst" -source $SCRIPT_DIR/env.sh +SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +source "$SCRIPT_DIR/env.sh" if [ "$TDE_ONLY" -eq 1 ]; then diff --git a/ci_scripts/meson-build.sh b/ci_scripts/meson-build.sh index c94b12421ab61..e954b04619c09 100755 --- a/ci_scripts/meson-build.sh +++ b/ci_scripts/meson-build.sh @@ -1,8 +1,21 @@ #!/bin/bash +ENABLE_COVERAGE= + +for arg in "$@" +do + case "$arg" in + --enable-coverage) + ENABLE_COVERAGE="-Db_coverage=true" + shift;; + esac +done + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +INSTALL_DIR="$SCRIPT_DIR/../../pginst" +source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/.." -meson setup build --prefix `pwd`/../pginst --buildtype=$1 -Dcassert=true -Dtap_tests=enabled +meson setup build --prefix "$INSTALL_DIR" --buildtype="$1" -Dcassert=true -Dtap_tests=enabled $ENABLE_COVERAGE cd build && ninja && ninja install diff --git a/ci_scripts/meson-test-tde.sh b/ci_scripts/meson-test-global-tde.sh similarity index 100% rename from ci_scripts/meson-test-tde.sh rename to ci_scripts/meson-test-global-tde.sh diff --git a/ci_scripts/meson-test.sh b/ci_scripts/meson-test.sh index 5d0ad07e4ce48..f3f888d31c23f 100755 --- a/ci_scripts/meson-test.sh +++ b/ci_scripts/meson-test.sh @@ -1,7 +1,25 @@ #!/bin/bash +set -e +TDE_ONLY=0 + +for arg in "$@" +do + case "$arg" in + --tde-only) + TDE_ONLY=1 + shift;; + esac +done + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" +source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/../build" -meson test +if [ "$TDE_ONLY" -eq 1 ]; +then + meson test --suite setup --suite pg_tde +else + meson test +fi From b7dfa6897fe60edf4049cf9fcaa5f4509ce354f3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 23:17:19 +0200 Subject: [PATCH 139/796] Only enable TDE_MODE for the tests with global TDE The TDE_MODE environment variable disables tests we actually want to run in our Github Actions. This change is also necessary to in a future commit disable the pg_tde tests in the global TDE mode. --- ci_scripts/configure-global-tde.sh | 1 + ci_scripts/env.sh | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ci_scripts/configure-global-tde.sh b/ci_scripts/configure-global-tde.sh index 09c64082bcfbc..40d4cdbd26dd5 100644 --- a/ci_scripts/configure-global-tde.sh +++ b/ci_scripts/configure-global-tde.sh @@ -8,6 +8,7 @@ source $SCRIPT_DIR/env.sh cd "$SCRIPT_DIR/.." source "$SCRIPT_DIR/env.sh" +export TDE_MODE=1 export PATH=$INSTALL_DIR/bin:$PATH export DATA_DIR=$INSTALL_DIR/data export PGDATA="${1:-$DATA_DIR}" diff --git a/ci_scripts/env.sh b/ci_scripts/env.sh index 558a01d7f086a..1c729ccfd21e4 100644 --- a/ci_scripts/env.sh +++ b/ci_scripts/env.sh @@ -1,4 +1,3 @@ #!/bin/bash -export TDE_MODE=1 export PERCONA_SERVER_VERSION=17.4.1 From e4c1cc012bfae7319446f8a72864c61c15ca4a95 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 23:48:09 +0200 Subject: [PATCH 140/796] Do not run pg_tde tests in make when in global TDE mode The purpose of the global TDE mode is to run PostgreSQL's normal test suite but with our extension so running the pg_tde test suite when in that mode makes no sense. Meson supports disabling test suites with --no-suite so we only need to do this for the Makefile. --- contrib/pg_tde/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index f71d289593974..80bb82819c162 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -5,6 +5,8 @@ MODULE_big = pg_tde EXTENSION = pg_tde DATA = pg_tde--1.0-rc.sql +# Since meson supports skipping test suites this is a make only feature +ifndef TDE_MODE REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf # toast_decrypt needs to be the first test when running with pg_tde # preinstalled and default_principal_key needs to run after key_provider. @@ -25,6 +27,7 @@ vault_v2_test \ version \ default_principal_key TAP_TESTS = 1 +endif OBJS = src/encryption/enc_tde.o \ src/encryption/enc_aes.o \ From 6a1e1b6495b8aff4ddc2e6347b699b018a975ac7 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 25 Apr 2025 23:48:21 +0200 Subject: [PATCH 141/796] Remove support for running pg_tde tests in global TDE mode Now that we no longer run the pg_tde suite in the global TDE mode we can remove all the code which was there to support it. --- contrib/pg_tde/Makefile | 9 +- .../expected/default_principal_key_1.out | 151 ---------------- contrib/pg_tde/expected/key_provider_1.out | 170 ------------------ contrib/pg_tde/expected/toast_decrypt_1.out | 24 --- contrib/pg_tde/meson.build | 5 +- 5 files changed, 6 insertions(+), 353 deletions(-) delete mode 100644 contrib/pg_tde/expected/default_principal_key_1.out delete mode 100644 contrib/pg_tde/expected/key_provider_1.out delete mode 100644 contrib/pg_tde/expected/toast_decrypt_1.out diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 80bb82819c162..d1dc561a80d58 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -8,10 +8,8 @@ DATA = pg_tde--1.0-rc.sql # Since meson supports skipping test suites this is a make only feature ifndef TDE_MODE REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf -# toast_decrypt needs to be the first test when running with pg_tde -# preinstalled and default_principal_key needs to run after key_provider. -REGRESS = toast_decrypt \ -access_control \ +# default_principal_key needs to run after key_provider. +REGRESS = access_control \ alter_index \ cache_alloc \ change_access_method \ @@ -23,6 +21,7 @@ pg_tde_is_encrypted \ recreate_storage \ relocate \ tablespace \ +toast_decrypt \ vault_v2_test \ version \ default_principal_key @@ -56,7 +55,7 @@ src/libkmip/libkmip/src/kmip_memset.o SCRIPTS_built = src/pg_tde_change_key_provider -EXTRA_INSTALL+=contrib/pg_buffercache contrib/test_decoding +EXTRA_INSTALL += contrib/pg_buffercache contrib/test_decoding EXTRA_CLEAN += src/pg_tde_change_key_provider.o ifdef USE_PGXS diff --git a/contrib/pg_tde/expected/default_principal_key_1.out b/contrib/pg_tde/expected/default_principal_key_1.out deleted file mode 100644 index 0a90d7696ea69..0000000000000 --- a/contrib/pg_tde/expected/default_principal_key_1.out +++ /dev/null @@ -1,151 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE EXTENSION IF NOT EXISTS pg_buffercache; -SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); - pg_tde_add_global_key_provider_file -------------------------------------- - -4 -(1 row) - --- Should fail: no default principal key for the server yet -SELECT pg_tde_verify_default_key(); -ERROR: principal key not configured for current database --- Should fail: no default principal key for the server yet -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_default_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key -SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); - pg_tde_set_default_key_using_global_key_provider --------------------------------------------------- - -(1 row) - -SELECT pg_tde_verify_default_key(); - pg_tde_verify_default_key ---------------------------- - -(1 row) - -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_default_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -4 | file-provider | default-key -(1 row) - --- fails -SELECT pg_tde_delete_global_key_provider('file-provider'); -ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+----------------- - -1 | reg_file-global - -2 | file-keyring - -4 | file-provider -(3 rows) - --- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key - --- Should succeed: "localizes" the default principal key for the database -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) -) USING tde_heap; -INSERT INTO test_enc (k) VALUES (1), (2), (3); --- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -4 | file-provider | default-key -(1 row) - -SELECT current_database() AS regress_database -\gset -CREATE DATABASE regress_pg_tde_other; -\c regress_pg_tde_other -CREATE EXTENSION pg_tde; -CREATE EXTENSION pg_buffercache; --- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key --- Should succeed: "localizes" the default principal key for the database -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) -) USING tde_heap; -INSERT INTO test_enc (k) VALUES (1), (2), (3); --- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -4 | file-provider | default-key -(1 row) - -\c :regress_database -CHECKPOINT; -SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); - pg_tde_set_default_key_using_global_key_provider --------------------------------------------------- - -(1 row) - -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+----------------- - -4 | file-provider | new-default-key -(1 row) - -\c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, key_name - FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+----------------- - -4 | file-provider | new-default-key -(1 row) - -SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); - pg_buffercache_evict ----------------------- - t -(1 row) - -SELECT * FROM test_enc; - id | k -----+--- - 1 | 1 - 2 | 2 - 3 | 3 -(3 rows) - -DROP TABLE test_enc; -DROP EXTENSION pg_tde CASCADE; -\c :regress_database -SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); - pg_buffercache_evict ----------------------- - t -(1 row) - -SELECT * FROM test_enc; - id | k -----+--- - 1 | 1 - 2 | 2 - 3 | 3 -(3 rows) - -DROP TABLE test_enc; -DROP EXTENSION pg_tde CASCADE; -DROP EXTENSION pg_buffercache; -DROP DATABASE regress_pg_tde_other; diff --git a/contrib/pg_tde/expected/key_provider_1.out b/contrib/pg_tde/expected/key_provider_1.out deleted file mode 100644 index 4b072d6f35fcd..0000000000000 --- a/contrib/pg_tde/expected/key_provider_1.out +++ /dev/null @@ -1,170 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key -SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------- -(0 rows) - -SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - -SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 2 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------- - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} -(2 rows) - -SELECT pg_tde_verify_key(); -ERROR: principal key not configured for current database -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -SELECT pg_tde_verify_key(); - pg_tde_verify_key -------------------- - -(1 row) - -SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); -ERROR: key provider "not-existent-provider" does not exists -HINT: Create the key provider -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------- - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} -(2 rows) - -SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); - pg_tde_change_database_key_provider_file ------------------------------------------- - 1 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} -(2 rows) - -SELECT pg_tde_verify_key(); -ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 -SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: parse json keyring config: unexpected field foo -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} -(2 rows) - -SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_global_key_provider_file -------------------------------------- - -2 -(1 row) - -SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_keyring2.per'); - pg_tde_add_global_key_provider_file -------------------------------------- - -3 -(1 row) - -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+----------------- - -1 | reg_file-global - -2 | file-keyring - -3 | file-keyring2 -(3 rows) - --- fails -SELECT pg_tde_delete_database_key_provider('file-provider'); -ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); - id | provider_name -----+---------------- - 1 | file-provider - 2 | file-provider2 -(2 rows) - --- works -SELECT pg_tde_delete_database_key_provider('file-provider2'); - pg_tde_delete_database_key_provider -------------------------------------- - -(1 row) - -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); - id | provider_name -----+--------------- - 1 | file-provider -(1 row) - -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+----------------- - -1 | reg_file-global - -2 | file-keyring - -3 | file-keyring2 -(3 rows) - -SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); - pg_tde_set_key_using_global_key_provider ------------------------------------------- - -(1 row) - --- fails -SELECT pg_tde_delete_global_key_provider('file-keyring'); -ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+----------------- - -1 | reg_file-global - -2 | file-keyring - -3 | file-keyring2 -(3 rows) - --- works -SELECT pg_tde_delete_global_key_provider('file-keyring2'); - pg_tde_delete_global_key_provider ------------------------------------ - -(1 row) - -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+----------------- - -1 | reg_file-global - -2 | file-keyring -(2 rows) - --- Creating a file key provider fails if we can't open or create the file -SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); -ERROR: Failed to open keyring file /cant-create-file-in-root.per: Permission denied -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/toast_decrypt_1.out b/contrib/pg_tde/expected/toast_decrypt_1.out deleted file mode 100644 index 3fc3a66ef1483..0000000000000 --- a/contrib/pg_tde/expected/toast_decrypt_1.out +++ /dev/null @@ -1,24 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -NOTICE: extension "pg_tde" already exists, skipping -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 2 -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -CREATE TABLE src (f1 TEXT STORAGE EXTERNAL) USING tde_heap; -INSERT INTO src VALUES(repeat('abcdeF',1000)); -SELECT * FROM src; - f1 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - abcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeFabcdeF -(1 row) - -DROP TABLE src; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 200894ece1c0f..3565a73b86d4f 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -80,10 +80,8 @@ install_data( kwargs: contrib_data_args, ) -# toast_decrypt needs to be the first test when running with pg_tde -# preinstalled and default_principal_key needs to run after key_provider. +# default_principal_key needs to run after key_provider. sql_tests = [ - 'toast_decrypt', 'access_control', 'alter_index', 'cache_alloc', @@ -96,6 +94,7 @@ sql_tests = [ 'relocate', 'recreate_storage', 'tablespace', + 'toast_decrypt', 'vault_v2_test', 'version', 'default_principal_key', From ae04e97e4b6a5b4317ecf053cf49254051f88e3c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 24 Apr 2025 12:36:52 +0200 Subject: [PATCH 142/796] Clean up test SQL file This removes some unnecessary queries and formats the queries to be easier to read. --- .../pg_tde/expected/change_access_method.out | 58 ++++++++---------- contrib/pg_tde/sql/change_access_method.sql | 59 ++++++++----------- 2 files changed, 47 insertions(+), 70 deletions(-) diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index ecef6d96b4e7e..886bc85737366 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -1,21 +1,21 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) CREATE TABLE country_table ( - country_id serial primary key, - country_name varchar(32) unique not null, - continent varchar(32) not null -) using tde_heap; + country_id serial primary key, + country_name varchar(32) unique not null, + continent varchar(32) not null +) USING tde_heap; INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), @@ -48,19 +48,7 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); (1 row) -- Try changing the encrypted table to an unencrypted table -ALTER TABLE country_table SET access method heap; -SELECT pg_tde_is_encrypted('country_table_country_id_seq'); - pg_tde_is_encrypted ---------------------- - f -(1 row) - -SELECT pg_tde_is_encrypted('country_table_pkey'); - pg_tde_is_encrypted ---------------------- - f -(1 row) - +ALTER TABLE country_table SET ACCESS METHOD heap; -- Insert some more data INSERT INTO country_table (country_name, continent) VALUES ('France', 'Europe'), @@ -96,7 +84,7 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); (1 row) -- Change it back to encrypted -ALTER TABLE country_table SET access method tde_heap; +ALTER TABLE country_table SET ACCESS METHOD tde_heap; INSERT INTO country_table (country_name, continent) VALUES ('China', 'Asia'), ('Brazil', 'South America'), @@ -134,35 +122,35 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); (1 row) ALTER TABLE country_table ADD y text; -SELECT pg_tde_is_encrypted(('pg_toast.pg_toast_' || 'country_table'::regclass::oid)::regclass); +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'country_table'::regclass::oid); pg_tde_is_encrypted --------------------- t (1 row) CREATE TABLE country_table2 ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ); -SET pg_tde.enforce_encryption = ON; +SET pg_tde.enforce_encryption = on; CREATE TABLE country_table3 ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ) USING heap; ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. -ALTER TABLE country_table SET access method heap; +ALTER TABLE country_table SET ACCESS METHOD heap; ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. -ALTER TABLE country_table2 SET access method tde_heap; +ALTER TABLE country_table2 SET ACCESS METHOD tde_heap; CREATE TABLE country_table3 ( - country_id serial primary key, - country_name text unique not null, - continent text not null -) using tde_heap; + country_id serial primary key, + country_name text unique not null, + continent text not null +) USING tde_heap; DROP TABLE country_table; DROP TABLE country_table2; DROP TABLE country_table3; -SET pg_tde.enforce_encryption = OFF; +SET pg_tde.enforce_encryption = off; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index 105b44d81bf0e..3e656a5afd452 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -1,13 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); CREATE TABLE country_table ( - country_id serial primary key, - country_name varchar(32) unique not null, - continent varchar(32) not null -) using tde_heap; + country_id serial primary key, + country_name varchar(32) unique not null, + continent varchar(32) not null +) USING tde_heap; INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), @@ -15,19 +15,12 @@ INSERT INTO country_table (country_name, continent) ('USA', 'North America'); SELECT * FROM country_table; - SELECT pg_tde_is_encrypted('country_table'); - SELECT pg_tde_is_encrypted('country_table_country_id_seq'); - SELECT pg_tde_is_encrypted('country_table_pkey'); -- Try changing the encrypted table to an unencrypted table -ALTER TABLE country_table SET access method heap; - -SELECT pg_tde_is_encrypted('country_table_country_id_seq'); - -SELECT pg_tde_is_encrypted('country_table_pkey'); +ALTER TABLE country_table SET ACCESS METHOD heap; -- Insert some more data INSERT INTO country_table (country_name, continent) @@ -37,57 +30,53 @@ INSERT INTO country_table (country_name, continent) SELECT * FROM country_table; SELECT pg_tde_is_encrypted('country_table'); - SELECT pg_tde_is_encrypted('country_table_country_id_seq'); - SELECT pg_tde_is_encrypted('country_table_pkey'); -- Change it back to encrypted -ALTER TABLE country_table SET access method tde_heap; +ALTER TABLE country_table SET ACCESS METHOD tde_heap; INSERT INTO country_table (country_name, continent) VALUES ('China', 'Asia'), ('Brazil', 'South America'), ('Australia', 'Oceania'); + SELECT * FROM country_table; SELECT pg_tde_is_encrypted('country_table'); - SELECT pg_tde_is_encrypted('country_table_country_id_seq'); - SELECT pg_tde_is_encrypted('country_table_pkey'); ALTER TABLE country_table ADD y text; -SELECT pg_tde_is_encrypted(('pg_toast.pg_toast_' || 'country_table'::regclass::oid)::regclass); +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'country_table'::regclass::oid); CREATE TABLE country_table2 ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ); -SET pg_tde.enforce_encryption = ON; +SET pg_tde.enforce_encryption = on; CREATE TABLE country_table3 ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ) USING heap; -ALTER TABLE country_table SET access method heap; - -ALTER TABLE country_table2 SET access method tde_heap; +ALTER TABLE country_table SET ACCESS METHOD heap; +ALTER TABLE country_table2 SET ACCESS METHOD tde_heap; CREATE TABLE country_table3 ( - country_id serial primary key, - country_name text unique not null, - continent text not null -) using tde_heap; + country_id serial primary key, + country_name text unique not null, + continent text not null +) USING tde_heap; DROP TABLE country_table; DROP TABLE country_table2; DROP TABLE country_table3; -SET pg_tde.enforce_encryption = OFF; +SET pg_tde.enforce_encryption = off; DROP EXTENSION pg_tde; From dffece72a53f0fd3b470642babc97d6558b0e195 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 24 Apr 2025 14:44:20 +0200 Subject: [PATCH 143/796] Make ALTER TABLE ... SET ACCESS METHOD logic easier to read Also add a couple of tests for the DEFAULT case to avoid regressions. --- .../pg_tde/expected/change_access_method.out | 18 +++++++++ contrib/pg_tde/sql/change_access_method.sql | 15 ++++++++ contrib/pg_tde/src/pg_tde_event_capture.c | 37 +++++++++---------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index 886bc85737366..ce8f54af7067d 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -121,6 +121,24 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); t (1 row) +-- Test that we honor the default value +SET default_table_access_method = 'heap'; +ALTER TABLE country_table SET ACCESS METHOD DEFAULT; +SELECT pg_tde_is_encrypted('country_table'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SET default_table_access_method = 'tde_heap'; +ALTER TABLE country_table SET ACCESS METHOD DEFAULT; +SELECT pg_tde_is_encrypted('country_table'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +RESET default_table_access_method; ALTER TABLE country_table ADD y text; SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'country_table'::regclass::oid); pg_tde_is_encrypted diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index 3e656a5afd452..dc01fbed8955a 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -46,6 +46,21 @@ SELECT pg_tde_is_encrypted('country_table'); SELECT pg_tde_is_encrypted('country_table_country_id_seq'); SELECT pg_tde_is_encrypted('country_table_pkey'); +-- Test that we honor the default value +SET default_table_access_method = 'heap'; + +ALTER TABLE country_table SET ACCESS METHOD DEFAULT; + +SELECT pg_tde_is_encrypted('country_table'); + +SET default_table_access_method = 'tde_heap'; + +ALTER TABLE country_table SET ACCESS METHOD DEFAULT; + +SELECT pg_tde_is_encrypted('country_table'); + +RESET default_table_access_method; + ALTER TABLE country_table ADD y text; SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'country_table'::regclass::oid); diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index d9cbe1ebf750d..d6b7a7ab4bb0b 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -32,7 +32,6 @@ /* Global variable that gets set at ddl start and cleard out at ddl end*/ static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}}; -static bool alterSetAccessMethod = false; static void reset_current_tde_create_event(void); static Oid get_tde_table_am_oid(void); @@ -176,6 +175,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree); ListCell *lcmd; Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); + AlterTableCmd *setAccessMethod = NULL; validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); @@ -185,29 +185,27 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); if (cmd->subtype == AT_SetAccessMethod) - { - tdeCurrentCreateEvent.baseTableOid = relationId; - tdeCurrentCreateEvent.alterAccessMethodMode = true; - - if (shouldEncryptTable(cmd->name)) - tdeCurrentCreateEvent.encryptMode = true; - - checkEncryptionStatus(); - - alterSetAccessMethod = true; - } + setAccessMethod = cmd; } - if (!alterSetAccessMethod) + tdeCurrentCreateEvent.baseTableOid = relationId; + + /* + * With a SET ACCESS METHOD clause, use that as the basis for + * decisions. But if it's not present, look up encryption status of + * the table. + */ + if (setAccessMethod) { - /* - * With a SET ACCESS METHOD clause, use that as the basis for - * decisions. But if it's not present, look up encryption status - * of the table. - */ + if (shouldEncryptTable(setAccessMethod->name)) + tdeCurrentCreateEvent.encryptMode = true; - tdeCurrentCreateEvent.baseTableOid = relationId; + checkEncryptionStatus(); + tdeCurrentCreateEvent.alterAccessMethodMode = true; + } + else + { if (relationId != InvalidOid) { Relation rel = relation_open(relationId, NoLock); @@ -307,7 +305,6 @@ reset_current_tde_create_event(void) tdeCurrentCreateEvent.baseTableOid = InvalidOid; tdeCurrentCreateEvent.tid = InvalidFullTransactionId; tdeCurrentCreateEvent.alterAccessMethodMode = false; - alterSetAccessMethod = false; } static Oid From 7177c2df6e42fb3a9412d64a6ed0348e4033f244 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 22:17:51 +0200 Subject: [PATCH 144/796] Simplify logic for global, local, server, default Improve readability for the provider vs key type logic. --- .../pg_tde/src/catalog/tde_principal_key.c | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 149c6c1d6b410..273b2cd9cbe04 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -109,7 +109,7 @@ static void set_principal_key_with_keyring(const char *key_name, static bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId); static bool pg_tde_verify_principal_key_internal(Oid databaseOid); -static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global); +static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid); PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); @@ -585,28 +585,28 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) static void pg_tde_set_principal_key_internal(char *key_name, enum global_status global, char *provider_name, bool ensure_new_key) { - Oid providerOid = MyDatabaseId; - Oid dbOid = MyDatabaseId; + Oid providerOid; + Oid dbOid; TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); - if (global == GS_GLOBAL) /* using a global provider for the current - * database */ + if (global == GS_GLOBAL) { + /* Using a global provider for the current database */ providerOid = GLOBAL_DATA_TDE_OID; + dbOid = MyDatabaseId; } - if (global == GS_SERVER) /* using a globla provider for the global - * (wal) database */ + else if (global == GS_SERVER) { + /* Using a global provider for the global (wal) database */ providerOid = GLOBAL_DATA_TDE_OID; dbOid = GLOBAL_DATA_TDE_OID; } - - if (global == GS_DEFAULT) /* using a globla provider for the default - * encryption setting */ + else if (global == GS_DEFAULT) { + /* Using a global provider for the default encryption setting */ providerOid = GLOBAL_DATA_TDE_OID; dbOid = DEFAULT_DATA_TDE_OID; @@ -619,6 +619,12 @@ pg_tde_set_principal_key_internal(char *key_name, enum global_status global, cha } LWLockRelease(tde_lwlock_enc_keys()); } + else + { + /* Using a local provider for the current database */ + providerOid = MyDatabaseId; + dbOid = MyDatabaseId; + } set_principal_key_with_keyring(key_name, provider_name, @@ -1086,7 +1092,7 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci Datum pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) { - return pg_tde_delete_key_provider_internal(fcinfo, 0); + return pg_tde_delete_key_provider_internal(fcinfo, MyDatabaseId); } Datum @@ -1097,14 +1103,13 @@ pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to modify global key providers")); - return pg_tde_delete_key_provider_internal(fcinfo, 1); + return pg_tde_delete_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } Datum -pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, int is_global) +pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) { char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); - Oid db_oid = (is_global == 1) ? GLOBAL_DATA_TDE_OID : MyDatabaseId; GenericKeyring *provider = GetKeyProviderByName(provider_name, db_oid); int provider_id; bool provider_used; From 7ed31159333a5b24e65e4a4641db2293262e3003 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 22:30:40 +0200 Subject: [PATCH 145/796] Get rid of global_status enum This enum was only used in one place and oscured the two dimensions of provider types (database vs global) and principal keys (server vs default vs database). --- .../pg_tde/src/catalog/tde_principal_key.c | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 273b2cd9cbe04..6329bc11f6a58 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -116,15 +116,7 @@ PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); -enum global_status -{ - GS_LOCAL, - GS_GLOBAL, - GS_SERVER, - GS_DEFAULT -}; - -static void pg_tde_set_principal_key_internal(char *principal_key_name, enum global_status global, char *provider_name, bool ensure_new_key); +static void pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *principal_key_name, const char *provider_name, bool ensure_new_key); static const TDEShmemSetupRoutine principal_key_info_shmem_routine = { .init_shared_state = initialize_shared_state, @@ -531,7 +523,8 @@ pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to access global key providers")); - pg_tde_set_principal_key_internal(principal_key_name, GS_DEFAULT, provider_name, ensure_new_key); + /* Using a global provider for the default encryption setting */ + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, DEFAULT_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); PG_RETURN_VOID(); } @@ -543,7 +536,8 @@ pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); - pg_tde_set_principal_key_internal(principal_key_name, GS_LOCAL, provider_name, ensure_new_key); + /* Using a local provider for the current database */ + pg_tde_set_principal_key_internal(MyDatabaseId, MyDatabaseId, principal_key_name, provider_name, ensure_new_key); PG_RETURN_VOID(); } @@ -560,7 +554,8 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to access global key providers")); - pg_tde_set_principal_key_internal(principal_key_name, GS_GLOBAL, provider_name, ensure_new_key); + /* Using a global provider for the current database */ + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, MyDatabaseId, principal_key_name, provider_name, ensure_new_key); PG_RETURN_VOID(); } @@ -577,39 +572,22 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to access global key providers")); - pg_tde_set_principal_key_internal(principal_key_name, GS_SERVER, provider_name, ensure_new_key); + /* Using a global provider for the global (wal) database */ + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, GLOBAL_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); PG_RETURN_VOID(); } static void -pg_tde_set_principal_key_internal(char *key_name, enum global_status global, char *provider_name, bool ensure_new_key) +pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_name, const char *provider_name, bool ensure_new_key) { - Oid providerOid; - Oid dbOid; TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); - if (global == GS_GLOBAL) - { - /* Using a global provider for the current database */ - providerOid = GLOBAL_DATA_TDE_OID; - dbOid = MyDatabaseId; - } - else if (global == GS_SERVER) - { - /* Using a global provider for the global (wal) database */ - providerOid = GLOBAL_DATA_TDE_OID; - dbOid = GLOBAL_DATA_TDE_OID; - } - else if (global == GS_DEFAULT) + if (dbOid == DEFAULT_DATA_TDE_OID) { - /* Using a global provider for the default encryption setting */ - providerOid = GLOBAL_DATA_TDE_OID; - dbOid = DEFAULT_DATA_TDE_OID; - /* Do we already have a default key? If yes, look up the name of it */ LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); existingDefaultKey = GetPrincipalKeyNoDefault(dbOid, LW_SHARED); @@ -619,12 +597,6 @@ pg_tde_set_principal_key_internal(char *key_name, enum global_status global, cha } LWLockRelease(tde_lwlock_enc_keys()); } - else - { - /* Using a local provider for the current database */ - providerOid = MyDatabaseId; - dbOid = MyDatabaseId; - } set_principal_key_with_keyring(key_name, provider_name, @@ -632,7 +604,7 @@ pg_tde_set_principal_key_internal(char *key_name, enum global_status global, cha dbOid, ensure_new_key); - if (global == GS_DEFAULT && existingDefaultKey != NULL) + if (dbOid == DEFAULT_DATA_TDE_OID && existingDefaultKey != NULL) { /* * In the previous step, we marked a new default provider Now we have From bb000ef9e8db145a195b209ce0e3648b1e0450d3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Apr 2025 22:33:49 +0200 Subject: [PATCH 146/796] Simplify superuser check for pg_tde_set_*_key_using_*_key_provider() Make the intent a lot cleaner by doing the check when a global provider us used instead of deciding to do it or not per user facing function. --- .../pg_tde/src/catalog/tde_principal_key.c | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 6329bc11f6a58..377bdbdf20e11 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -518,11 +518,6 @@ pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers")); - /* Using a global provider for the default encryption setting */ pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, DEFAULT_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); @@ -549,11 +544,6 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers")); - /* Using a global provider for the current database */ pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, MyDatabaseId, principal_key_name, provider_name, ensure_new_key); @@ -567,11 +557,6 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to access global key providers")); - /* Using a global provider for the global (wal) database */ pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, GLOBAL_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); @@ -584,6 +569,11 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; + if (providerOid == GLOBAL_DATA_TDE_OID && !superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); + ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); if (dbOid == DEFAULT_DATA_TDE_OID) From 23786e99223d21c380b90536ca70f39e4848904c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 00:34:55 +0200 Subject: [PATCH 147/796] Move all pg_tde related tests into contrib/pg_tde While these tests test our changes to pg_waldump they are quite easy to overlook right now and where exactly should we draw the line? These tests are not something we ever want to upstream and in the future when we figure out how we want to make sure pg_waldump works with encrypted WAL we likely will want to have the tests for that solution in the same folder as our other tests anyway. --- contrib/pg_tde/meson.build | 2 + .../pg_tde/t/014_pg_waldump_basic.pl | 70 ++++++++++++++----- .../pg_tde/t/015_pg_waldump_fullpage.pl | 10 ++- src/bin/pg_waldump/meson.build | 2 - 4 files changed, 60 insertions(+), 24 deletions(-) rename src/bin/pg_waldump/t/003_basic_encrypted.pl => contrib/pg_tde/t/014_pg_waldump_basic.pl (83%) rename src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl => contrib/pg_tde/t/015_pg_waldump_fullpage.pl (91%) diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 3565a73b86d4f..59bb5a21c702f 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -114,6 +114,8 @@ tap_tests = [ 't/011_unlogged_tables.pl', 't/012_replication.pl', 't/013_crash_recovery.pl', + 't/014_pg_waldump_basic.pl', + 't/015_pg_waldump_fullpage.pl', ] tests += { diff --git a/src/bin/pg_waldump/t/003_basic_encrypted.pl b/contrib/pg_tde/t/014_pg_waldump_basic.pl similarity index 83% rename from src/bin/pg_waldump/t/003_basic_encrypted.pl rename to contrib/pg_tde/t/014_pg_waldump_basic.pl index 9f064f0c205fa..09315cf39d421 100644 --- a/src/bin/pg_waldump/t/003_basic_encrypted.pl +++ b/contrib/pg_tde/t/014_pg_waldump_basic.pl @@ -27,8 +27,12 @@ $node->start; $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); -$node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); $node->append_conf( 'postgresql.conf', q{ @@ -128,48 +132,74 @@ [ 'pg_waldump', 'foo', 'bar' ], qr/error: could not locate WAL file "foo"/, 'start file not found'); -command_like([ 'pg_waldump', '-k', $node->data_dir. '/pg_tde', $node->data_dir . '/pg_wal/' . $start_walfile ], - qr/./, 'runs with start segment specified'); +command_like( + [ + 'pg_waldump', '-k', + $node->data_dir . '/pg_tde', + $node->data_dir . '/pg_wal/' . $start_walfile + ], + qr/./, + 'runs with start segment specified'); command_fails_like( - [ 'pg_waldump', '-k', $node->data_dir. '/pg_tde', $node->data_dir . '/pg_wal/' . $start_walfile, 'bar' ], + [ + 'pg_waldump', '-k', + $node->data_dir . '/pg_tde', + $node->data_dir . '/pg_wal/' . $start_walfile, 'bar' + ], qr/error: could not open file "bar"/, 'end file not found'); command_like( [ 'pg_waldump', - '-k', $node->data_dir. '/pg_tde', + '-k', + $node->data_dir . '/pg_tde', $node->data_dir . '/pg_wal/' . $start_walfile, $node->data_dir . '/pg_wal/' . $end_walfile ], qr/./, 'runs with start and end segment specified'); command_fails_like( - [ 'pg_waldump', '-p', $node->data_dir, '-k', $node->data_dir. '/pg_tde' ], + [ + 'pg_waldump', '-p', $node->data_dir, '-k', + $node->data_dir . '/pg_tde' + ], qr/error: no start WAL location given/, 'path option requires start location'); command_like( [ - 'pg_waldump', '-p', $node->data_dir, '--start', - $start_lsn, '--end', $end_lsn, - '-k', $node->data_dir. '/pg_tde' + 'pg_waldump', '-p', + $node->data_dir, '--start', + $start_lsn, '--end', + $end_lsn, '-k', + $node->data_dir . '/pg_tde' ], qr/./, 'runs with path option and start and end locations'); command_fails_like( - [ 'pg_waldump', '-k', $node->data_dir. '/pg_tde', '-p', $node->data_dir, '--start', $start_lsn ], + [ + 'pg_waldump', '-k', + $node->data_dir . '/pg_tde', '-p', + $node->data_dir, '--start', + $start_lsn + ], qr/error: error in WAL record at/, 'falling off the end of the WAL results in an error'); command_like( [ - 'pg_waldump', '--quiet', - '-k', $node->data_dir. '/pg_tde', + 'pg_waldump', '--quiet', '-k', + $node->data_dir . '/pg_tde', $node->data_dir . '/pg_wal/' . $start_walfile ], qr/^$/, 'no output with --quiet option'); command_fails_like( - [ 'pg_waldump', '--quiet', '-k', $node->data_dir. '/pg_tde', '-p', $node->data_dir, '--start', $start_lsn ], + [ + 'pg_waldump', '--quiet', + '-k', $node->data_dir . '/pg_tde', + '-p', $node->data_dir, + '--start', $start_lsn + ], qr/error: error in WAL record at/, 'errors are shown with --quiet'); @@ -187,9 +217,8 @@ my (@cmd, $stdout, $stderr, $result); @cmd = ( - 'pg_waldump', '-k', $node->data_dir. '/pg_tde', - '--start', $new_start, - $node->data_dir . '/pg_wal/' . $start_walfile); + 'pg_waldump', '-k', $node->data_dir . '/pg_tde', + '--start', $new_start, $node->data_dir . '/pg_wal/' . $start_walfile); $result = IPC::Run::run \@cmd, '>', \$stdout, '2>', \$stderr; ok($result, "runs with start segment and start LSN specified"); like($stderr, qr/first record is after/, 'info message printed'); @@ -206,8 +235,11 @@ sub test_pg_waldump my (@cmd, $stdout, $stderr, $result, @lines); @cmd = ( - 'pg_waldump', '-k', $node->data_dir. '/pg_tde', '-p', $node->data_dir, - '--start', $start_lsn, '--end', $end_lsn); + 'pg_waldump', '-k', + $node->data_dir . '/pg_tde', '-p', + $node->data_dir, '--start', + $start_lsn, '--end', + $end_lsn); push @cmd, @opts; $result = IPC::Run::run \@cmd, '>', \$stdout, '2>', \$stderr; ok($result, "pg_waldump @opts: runs ok"); diff --git a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl b/contrib/pg_tde/t/015_pg_waldump_fullpage.pl similarity index 91% rename from src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl rename to contrib/pg_tde/t/015_pg_waldump_fullpage.pl index f737daa4232a4..877ff2d34599b 100644 --- a/src/bin/pg_waldump/t/004_save_fullpage_encrypted.pl +++ b/contrib/pg_tde/t/015_pg_waldump_fullpage.pl @@ -41,8 +41,12 @@ sub get_block_lsn $node->start; $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); -$node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');");; -$node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); $node->append_conf( 'postgresql.conf', q{ @@ -84,7 +88,7 @@ sub get_block_lsn $node->command_ok( [ 'pg_waldump', '--quiet', - '-k', $node->data_dir. '/pg_tde', + '-k', $node->data_dir . '/pg_tde', '--save-fullpage', "$tmp_folder/raw", '--relation', $relation, $walfile diff --git a/src/bin/pg_waldump/meson.build b/src/bin/pg_waldump/meson.build index cab9de90846f2..2d9d67a02ed82 100644 --- a/src/bin/pg_waldump/meson.build +++ b/src/bin/pg_waldump/meson.build @@ -42,8 +42,6 @@ tests += { 'tests': [ 't/001_basic.pl', 't/002_save_fullpage.pl', - 't/003_basic_encrypted.pl', - 't/004_save_fullpage_encrypted.pl', ], }, } From 4e0280979e5cd9177f40ee4b38d2eb5f3a395792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 23 Apr 2025 18:43:28 +0200 Subject: [PATCH 148/796] PG-1504 Make partitions inherit encryption status Since the access method isn't included in the statement we'll have to look it up on the parent in the same way DefineRelation() does. --- contrib/pg_tde/expected/partition_table.out | 60 +++++++++++++++++++++ contrib/pg_tde/sql/partition_table.sql | 38 +++++++++++++ contrib/pg_tde/src/pg_tde_event_capture.c | 44 ++++++++++++++- 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index 05cdc5708e66c..22d67a0b2100f 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -87,4 +87,64 @@ SELECT pg_tde_is_encrypted('partition_q4_2024'); (1 row) DROP TABLE partitioned_table; +-- Partition inherits encryption status from parent table if default is heap and parent is tde_heap +SET default_table_access_method = "heap"; +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING tde_heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partition_parent; +RESET default_table_access_method; +-- Partition inherits encryption status from parent table if default is tde_heap and parent is heap +SET default_table_access_method = "tde_heap"; +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +DROP TABLE partition_parent; +RESET default_table_access_method; +-- Partition uses default access method to determine encryption status if neither parent nor child have an access method set +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +SET default_table_access_method = "tde_heap"; +CREATE TABLE partition_child_tde PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child_tde'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SET default_table_access_method = "heap"; +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (10) TO (19); +SELECT pg_tde_is_encrypted('partition_child_heap'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +DROP TABLE partition_parent; +RESET default_table_access_method; +-- Enforce encryption GUC is respected when creating partitions even if parent is plain text +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING heap; +SET pg_tde.enforce_encryption = on; +CREATE TABLE partition_child_inherit PARTITION OF partition_parent FOR VALUES FROM (0) TO (10); +ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (11) TO (20) USING heap; +ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. +CREATE TABLE partition_child_tde_heap PARTITION OF partition_parent FOR VALUES FROM (11) TO (20) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child_tde_heap'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partition_parent; +RESET pg_tde.enforce_encryption; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index 2651ef71d726f..ddaf30033adab 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -32,4 +32,42 @@ SELECT pg_tde_is_encrypted('partition_q3_2024'); SELECT pg_tde_is_encrypted('partition_q4_2024'); DROP TABLE partitioned_table; + +-- Partition inherits encryption status from parent table if default is heap and parent is tde_heap +SET default_table_access_method = "heap"; +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING tde_heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child'); +DROP TABLE partition_parent; +RESET default_table_access_method; + +-- Partition inherits encryption status from parent table if default is tde_heap and parent is heap +SET default_table_access_method = "tde_heap"; +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child'); +DROP TABLE partition_parent; +RESET default_table_access_method; + +-- Partition uses default access method to determine encryption status if neither parent nor child have an access method set +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +SET default_table_access_method = "tde_heap"; +CREATE TABLE partition_child_tde PARTITION OF partition_parent FOR VALUES FROM (0) TO (9); +SELECT pg_tde_is_encrypted('partition_child_tde'); +SET default_table_access_method = "heap"; +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (10) TO (19); +SELECT pg_tde_is_encrypted('partition_child_heap'); +DROP TABLE partition_parent; +RESET default_table_access_method; + +-- Enforce encryption GUC is respected when creating partitions even if parent is plain text +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a) USING heap; +SET pg_tde.enforce_encryption = on; +CREATE TABLE partition_child_inherit PARTITION OF partition_parent FOR VALUES FROM (0) TO (10); +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (11) TO (20) USING heap; +CREATE TABLE partition_child_tde_heap PARTITION OF partition_parent FOR VALUES FROM (11) TO (20) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child_tde_heap'); +DROP TABLE partition_parent; +RESET pg_tde.enforce_encryption; + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index d6b7a7ab4bb0b..23bc02a22bd3a 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -14,6 +14,7 @@ #include "fmgr.h" #include "utils/rel.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "catalog/pg_class.h" #include "commands/defrem.h" #include "commands/sequence.h" @@ -149,12 +150,53 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) else if (IsA(parsetree, CreateStmt)) { CreateStmt *stmt = castNode(CreateStmt, parsetree); + bool foundAccessMethod = false; validateCurrentEventTriggerState(true); tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - if (shouldEncryptTable(stmt->accessMethod)) + + if (stmt->accessMethod) + { + foundAccessMethod = true; + if (strcmp(stmt->accessMethod, "tde_heap") == 0) + { + tdeCurrentCreateEvent.encryptMode = true; + } + } + else if (stmt->partbound) + { + /* + * If no access method is specified, and this is a partition of a + * parent table, access method can be inherited from the parent + * table if it has one set. + * + * AccessExclusiveLock might seem excessive, but it's what + * DefineRelation() will take on any partitioned parent relation + * in this transaction anyway. + */ + Oid parentOid; + Oid parentAmOid; + + Assert(list_length(stmt->inhRelations) == 1); + + parentOid = RangeVarGetRelid(linitial(stmt->inhRelations), + AccessExclusiveLock, + false); + parentAmOid = get_rel_relam(parentOid); + foundAccessMethod = parentAmOid != InvalidOid; + + if (foundAccessMethod && parentAmOid == get_tde_table_am_oid()) + { + tdeCurrentCreateEvent.encryptMode = true; + } + } + + if (!foundAccessMethod + && strcmp(default_table_access_method, "tde_heap") == 0) + { tdeCurrentCreateEvent.encryptMode = true; + } checkEncryptionStatus(); } From fd43eadf0c81023d96df4300d305a85b89b9d380 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 28 Apr 2025 10:37:50 +0200 Subject: [PATCH 149/796] Remove leftovers from the refactoring of key rotation WAL logging Some now dead code was not remove in commit 0d86245ccdc594705c1f7096853fd9623e574cbe when we changed how we WAL log principal key rotiations. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 43 ------------------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 1 - 2 files changed, 44 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 91fa23a53b82a..01ac4b39db34d 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -664,49 +664,6 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p } } -/* - * Rotate keys on a standby. - */ -void -pg_tde_write_map_keydata_file(off_t file_size, char *file_data) -{ - TDEFileHeader *fheader; - char db_map_path[MAXPGPATH]; - char path_new[MAXPGPATH]; - int fd_new; - off_t curr_pos = 0; - - /* Let's get the header. Buff should start with the map file header. */ - fheader = (TDEFileHeader *) file_data; - - pg_tde_set_db_file_path(fheader->signed_key_info.data.databaseId, db_map_path); - - /* Initialize the new file and set the name */ - fd_new = keyrotation_init_file(&fheader->signed_key_info, path_new, db_map_path, &curr_pos); - - if (pg_pwrite(fd_new, file_data, file_size, 0) != file_size) - { - ereport(WARNING, - errcode_for_file_access(), - errmsg("could not write tde file \"%s\": %m", path_new)); - close(fd_new); - return; - } - - if (pg_fsync(fd_new) != 0) - { - ereport(WARNING, - errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", path_new)); - close(fd_new); - return; - } - - close(fd_new); - - finalize_key_rotation(db_map_path, path_new); -} - /* * It's called by seg_write inside crit section so no pallocs, hence * needs keyfile_path diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 81b11fc399a4a..52c83db59ad05 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -119,7 +119,6 @@ extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_k extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); -extern void pg_tde_write_map_keydata_file(off_t size, char *file_data); const char *tde_sprint_key(InternalKey *k); From 0346daca91163d4018810a39a476c1376cea703e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 28 Apr 2025 19:50:45 +0200 Subject: [PATCH 150/796] Fix typo in error messages Multiple error messages spelled "length" as "lenght" --- contrib/pg_tde/src/catalog/tde_keyring.c | 4 ++-- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 7637662de5943..c90f1a8c18601 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -227,7 +227,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) if (nlen >= sizeof(provider.provider_name)) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1)); + errmsg("too long provider name, maximum length is %ld bytes", sizeof(provider.provider_name) - 1)); olen = strlen(options); if (olen >= sizeof(provider.options)) @@ -280,7 +280,7 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) if (nlen >= sizeof(provider.provider_name) - 1) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum lenght is %ld bytes", sizeof(provider.provider_name) - 1)); + errmsg("too long provider name, maximum length is %ld bytes", sizeof(provider.provider_name) - 1)); olen = strlen(options); if (olen >= sizeof(provider.options)) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 377bdbdf20e11..4ccd581b541eb 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -306,7 +306,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (strlen(key_name) >= sizeof(keyInfo->name)) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long principal key name, maximum lenght is %ld bytes", sizeof(keyInfo->name) - 1)); + errmsg("too long principal key name, maximum length is %ld bytes", sizeof(keyInfo->name) - 1)); if (keyInfo == NULL) keyInfo = KeyringGenerateNewKeyAndStore(new_keyring, key_name, PRINCIPAL_KEY_LEN); From 0ba6dcf9a5b0f8bd48503885f6ee61741933ac6a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 28 Apr 2025 19:54:21 +0200 Subject: [PATCH 151/796] Clean up pg_tde_get_tde_data_dir() Make sure the function returns a const pointer and make the name consistent with pg_tde_set_data_dir(). --- contrib/pg_tde/src/catalog/tde_keyring.c | 2 +- contrib/pg_tde/src/common/pg_tde_utils.c | 4 ++-- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/src/include/common/pg_tde_utils.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index c90f1a8c18601..83586e5c2b37b 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -896,7 +896,7 @@ debug_print_kerying(GenericKeyring *keyring) static inline void get_keyring_infofile_path(char *resPath, Oid dbOid) { - join_path_components(resPath, pg_tde_get_tde_data_dir(), psprintf(PG_TDE_KEYRING_FILENAME, dbOid)); + join_path_components(resPath, pg_tde_get_data_dir(), psprintf(PG_TDE_KEYRING_FILENAME, dbOid)); } static int diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 5067a8d04816e..08dde50b1317b 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -106,8 +106,8 @@ pg_tde_set_data_dir(const char *dir) } /* returns the palloc'd string */ -char * -pg_tde_get_tde_data_dir(void) +const char * +pg_tde_get_data_dir(void) { return tde_data_dir; } diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 52c83db59ad05..87a9d9aa828bf 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -107,7 +107,7 @@ extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); static inline void pg_tde_set_db_file_path(Oid dbOid, char *path) { - join_path_components(path, pg_tde_get_tde_data_dir(), psprintf(PG_TDE_MAP_FILENAME, dbOid)); + join_path_components(path, pg_tde_get_data_dir(), psprintf(PG_TDE_MAP_FILENAME, dbOid)); } extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel); diff --git a/contrib/pg_tde/src/include/common/pg_tde_utils.h b/contrib/pg_tde/src/include/common/pg_tde_utils.h index b8cd7bd11c7aa..93c792c458da1 100644 --- a/contrib/pg_tde/src/include/common/pg_tde_utils.h +++ b/contrib/pg_tde/src/include/common/pg_tde_utils.h @@ -13,6 +13,6 @@ extern int get_tde_tables_count(void); #endif /* !FRONTEND */ extern void pg_tde_set_data_dir(const char *dir); -extern char *pg_tde_get_tde_data_dir(void); +extern const char *pg_tde_get_data_dir(void); #endif /* PG_TDE_UTILS_H */ From 07b799266fc9a22ee10556ce65465b2e09e31daa Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Apr 2025 09:49:50 +0200 Subject: [PATCH 152/796] Remove incorrect comment about string being palloced As far as I can tell this comment has never been true but was just accidentally copied from some other fucntion. --- contrib/pg_tde/src/common/pg_tde_utils.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 08dde50b1317b..3bb122f51d34d 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -105,7 +105,6 @@ pg_tde_set_data_dir(const char *dir) strlcpy(tde_data_dir, dir, sizeof(tde_data_dir)); } -/* returns the palloc'd string */ const char * pg_tde_get_data_dir(void) { From d0ff80452db3a3ec3045a179efc4f7e1e49b9ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 25 Apr 2025 14:07:14 +0200 Subject: [PATCH 153/796] Clean up declarations in tde_keyring.c Sort declarations and visually separate static functions from user facing functions. --- contrib/pg_tde/src/catalog/tde_keyring.c | 32 ++++++++++-------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 83586e5c2b37b..67b7886273766 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -53,16 +53,15 @@ typedef enum ProviderScanType #define VAULTV2_KEYRING_TYPE "vault-v2" #define KMIP_KEYRING_TYPE "kmip" +static void debug_print_kerying(GenericKeyring *keyring); +static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider); +static inline void get_keyring_infofile_path(char *resPath, Oid dbOid); static FileKeyring *load_file_keyring_provider_options(char *keyring_options); +static GenericKeyring *load_keyring_provider_from_record(KeyringProviderRecord *provider); static GenericKeyring *load_keyring_provider_options(ProviderType provider_type, char *keyring_options); -static VaultV2Keyring *load_vaultV2_keyring_provider_options(char *keyring_options); static KmipKeyring *load_kmip_keyring_provider_options(char *keyring_options); -static void debug_print_kerying(GenericKeyring *keyring); -static GenericKeyring *load_keyring_provider_from_record(KeyringProviderRecord *provider); -static inline void get_keyring_infofile_path(char *resPath, Oid dbOid); +static VaultV2Keyring *load_vaultV2_keyring_provider_options(char *keyring_options); static int open_keyring_infofile(Oid dbOid, int flags); -static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider); - static void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog); #ifdef FRONTEND @@ -72,30 +71,25 @@ static void simple_list_free(SimplePtrList *list); #else -static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); - PG_FUNCTION_INFO_V1(pg_tde_add_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_add_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_change_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_change_global_key_provider); -static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); - PG_FUNCTION_INFO_V1(pg_tde_list_all_database_key_providers); PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); -static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); - -static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); - -#define PG_TDE_LIST_PROVIDERS_COLS 4 - -static void key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); +static void cleanup_key_provider_info(Oid databaseId); static const char *get_keyring_provider_typename(ProviderType p_type); static List *GetAllKeyringProviders(Oid dbOid); -static void cleanup_key_provider_info(Oid databaseId); - static Size initialize_shared_state(void *start_address); +static void key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); +static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); +static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); +static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); static Size required_shared_mem_size(void); +static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); + +#define PG_TDE_LIST_PROVIDERS_COLS 4 typedef struct TdeKeyProviderInfoSharedState { From 0ac9bc0837289f08f56f0e7713569f3b735b482a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 24 Apr 2025 16:47:24 +0200 Subject: [PATCH 154/796] Move key provider deletion functions Seems like these should belong in the same file as the functions for creating and modifying these entries. --- contrib/pg_tde/src/catalog/tde_keyring.c | 50 +++++++++++++++++ .../pg_tde/src/catalog/tde_principal_key.c | 54 +------------------ .../src/include/catalog/tde_principal_key.h | 1 + 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 67b7886273766..f85aaed67e4cc 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -75,6 +75,8 @@ PG_FUNCTION_INFO_V1(pg_tde_add_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_add_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_change_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_change_global_key_provider); +PG_FUNCTION_INFO_V1(pg_tde_delete_database_key_provider); +PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_list_all_database_key_providers); PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); @@ -85,6 +87,7 @@ static Size initialize_shared_state(void *start_address); static void key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); +static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); static Size required_shared_mem_size(void); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); @@ -293,6 +296,53 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) PG_RETURN_INT32(provider.provider_id); } +Datum +pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) +{ + return pg_tde_delete_key_provider_internal(fcinfo, MyDatabaseId); +} + +Datum +pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) +{ + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify global key providers")); + + return pg_tde_delete_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); + +} + +Datum +pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) +{ + char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + GenericKeyring *provider = GetKeyProviderByName(provider_name, db_oid); + int provider_id; + bool provider_used; + + if (provider == NULL) + { + ereport(ERROR, errmsg("Keyring provider not found")); + } + + provider_id = provider->keyring_id; + provider_used = pg_tde_is_provider_used(db_oid, provider_id); + + pfree(provider); + + if (provider_used) + { + ereport(ERROR, + errmsg("Can't delete a provider which is currently in use")); + } + + delete_key_provider_info(provider_name, db_oid, true); + + PG_RETURN_VOID(); +} + Datum pg_tde_list_all_database_key_providers(PG_FUNCTION_ARGS) { diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 4ccd581b541eb..31b40cd384bdb 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -49,9 +49,6 @@ #ifndef FRONTEND -PG_FUNCTION_INFO_V1(pg_tde_delete_database_key_provider); -PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); - PG_FUNCTION_INFO_V1(pg_tde_verify_key); PG_FUNCTION_INFO_V1(pg_tde_verify_server_key); PG_FUNCTION_INFO_V1(pg_tde_verify_default_key); @@ -106,11 +103,8 @@ static void set_principal_key_with_keyring(const char *key_name, Oid providerOid, Oid dbOid, bool ensure_new_key); -static bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId); static bool pg_tde_verify_principal_key_internal(Oid databaseOid); -static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid); - PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); @@ -902,7 +896,7 @@ pg_tde_principal_key_configured(Oid databaseId) return principalKey != NULL; } -static bool +bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId) { bool is_global = (databaseOid == GLOBAL_DATA_TDE_OID); @@ -1051,52 +1045,6 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci table_close(rel, RowExclusiveLock); } -Datum -pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) -{ - return pg_tde_delete_key_provider_internal(fcinfo, MyDatabaseId); -} - -Datum -pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) -{ - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers")); - - return pg_tde_delete_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); -} - -Datum -pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) -{ - char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); - GenericKeyring *provider = GetKeyProviderByName(provider_name, db_oid); - int provider_id; - bool provider_used; - - if (provider == NULL) - { - ereport(ERROR, errmsg("Keyring provider not found")); - } - - provider_id = provider->keyring_id; - provider_used = pg_tde_is_provider_used(db_oid, provider_id); - - pfree(provider); - - if (provider_used) - { - ereport(ERROR, - errmsg("Can't delete a provider which is currently in use")); - } - - delete_key_provider_info(provider_name, db_oid, true); - - PG_RETURN_VOID(); -} - static bool pg_tde_verify_principal_key_internal(Oid databaseOid) { diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index d7ee7806154ef..6ac864fac8c0c 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -54,5 +54,6 @@ extern TDEPrincipalKey *GetPrincipalKey(Oid dbOid, void *lockMode); #endif extern void xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec); +extern bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId); #endif /* PG_TDE_PRINCIPAL_KEY_H */ From c7f0a8168c132defe0e47a3842a7a64579f300c2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 01:37:25 +0200 Subject: [PATCH 155/796] PG-1447 Add tests for template databases with encrypted relations Using a database with encrypted relations as template works works with the WAL_LOG strateg as long as you have configured a default principal key. It is a bit hackish but works and since encrypted objects in a template database is likely to be an uncommon use case this is fine for now. The test file is put last in the test run order since there is still no good way to clean up default principal keys. --- contrib/pg_tde/Makefile | 6 +- contrib/pg_tde/expected/create_database.out | 97 +++++++++++++++++++++ contrib/pg_tde/meson.build | 4 +- contrib/pg_tde/sql/create_database.sql | 51 +++++++++++ 4 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 contrib/pg_tde/expected/create_database.out create mode 100644 contrib/pg_tde/sql/create_database.sql diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index d1dc561a80d58..f8500336c00c2 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -8,7 +8,8 @@ DATA = pg_tde--1.0-rc.sql # Since meson supports skipping test suites this is a make only feature ifndef TDE_MODE REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf -# default_principal_key needs to run after key_provider. +# create_database must run after default_principal_key which must run after +# key_provider. REGRESS = access_control \ alter_index \ cache_alloc \ @@ -24,7 +25,8 @@ tablespace \ toast_decrypt \ vault_v2_test \ version \ -default_principal_key +default_principal_key \ +create_database TAP_TESTS = 1 endif diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out new file mode 100644 index 0000000000000..37f27c698e6bd --- /dev/null +++ b/contrib/pg_tde/expected/create_database.out @@ -0,0 +1,97 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE DATABASE template_db; +SELECT current_database() AS regress_database +\gset +\c template_db +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/template_provider.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +CREATE TABLE test_enc (id serial PRIMARY KEY, x int) USING tde_heap; +CREATE TABLE test_plain (id serial PRIMARY KEY, x int) USING heap; +INSERT INTO test_enc (x) VALUES (10), (20); +INSERT INTO test_plain (x) VALUES (30), (40); +\c :regress_database +-- TODO: Test the case where we have no default key once we can delete default keys +--CREATE DATABASE new_db TEMPLATE template_db; +SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per'); + pg_tde_add_global_key_provider_file +------------------------------------- + -4 +(1 row) + +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- + +(1 row) + +CREATE DATABASE new_db TEMPLATE template_db; +\c new_db +INSERT INTO test_enc (x) VALUES (25); +SELECT * FROM test_enc; + id | x +----+---- + 1 | 10 + 2 | 20 + 3 | 25 +(3 rows) + +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('test_enc_pkey'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('test_enc_id_seq'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +INSERT INTO test_plain (x) VALUES (45); +SELECT * FROM test_plain; + id | x +----+---- + 1 | 30 + 2 | 40 + 3 | 45 +(3 rows) + +SELECT pg_tde_is_encrypted('test_plain'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('test_plain_pkey'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +SELECT pg_tde_is_encrypted('test_plain_id_seq'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +\c :regress_database +DROP DATABASE new_db; +DROP DATABASE template_db; +DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 59bb5a21c702f..c78db880e7a2a 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -80,7 +80,8 @@ install_data( kwargs: contrib_data_args, ) -# default_principal_key needs to run after key_provider. +# create_database must run after default_principal_key which must run after +# key_provider. sql_tests = [ 'access_control', 'alter_index', @@ -98,6 +99,7 @@ sql_tests = [ 'vault_v2_test', 'version', 'default_principal_key', + 'create_database', ] tap_tests = [ diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql new file mode 100644 index 0000000000000..7e9dbcde77051 --- /dev/null +++ b/contrib/pg_tde/sql/create_database.sql @@ -0,0 +1,51 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; + +CREATE DATABASE template_db; + +SELECT current_database() AS regress_database +\gset + +\c template_db + +CREATE EXTENSION pg_tde; + +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/template_provider.per'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); + +CREATE TABLE test_enc (id serial PRIMARY KEY, x int) USING tde_heap; +CREATE TABLE test_plain (id serial PRIMARY KEY, x int) USING heap; + +INSERT INTO test_enc (x) VALUES (10), (20); +INSERT INTO test_plain (x) VALUES (30), (40); + +\c :regress_database + +-- TODO: Test the case where we have no default key once we can delete default keys +--CREATE DATABASE new_db TEMPLATE template_db; + +SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per'); + +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); + +CREATE DATABASE new_db TEMPLATE template_db; + +\c new_db + +INSERT INTO test_enc (x) VALUES (25); +SELECT * FROM test_enc; +SELECT pg_tde_is_encrypted('test_enc'); +SELECT pg_tde_is_encrypted('test_enc_pkey'); +SELECT pg_tde_is_encrypted('test_enc_id_seq'); + +INSERT INTO test_plain (x) VALUES (45); +SELECT * FROM test_plain; +SELECT pg_tde_is_encrypted('test_plain'); +SELECT pg_tde_is_encrypted('test_plain_pkey'); +SELECT pg_tde_is_encrypted('test_plain_id_seq'); + +\c :regress_database + +DROP DATABASE new_db; +DROP DATABASE template_db; + +DROP EXTENSION pg_tde; From 3f7806ae395325d275cffe3273b1b5150b16ec47 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 01:39:16 +0200 Subject: [PATCH 156/796] PG-1447 Assert there are no encrypted relations when using FILE_COPY The FILE_COPY strategy of CREATE DATABASE does not use the SMGR so it cannot properly copy and re-encrypt encrypted relations. So we check for such relations and error out if any are found. We look for encrypted relations simply by counting the number of keys in the key map file of the database. This simple approach is fine even with the risk of a left-over key from a crash or a bug due to the FILE_COPY method being rare and that we therefore want to keep this code minimal. --- contrib/pg_tde/expected/create_database.out | 8 + contrib/pg_tde/sql/create_database.sql | 11 ++ contrib/pg_tde/src/access/pg_tde_tdemap.c | 57 +++++- .../pg_tde/src/include/access/pg_tde_tdemap.h | 1 + .../pg_tde/src/include/pg_tde_event_capture.h | 2 +- contrib/pg_tde/src/pg_tde.c | 2 + contrib/pg_tde/src/pg_tde_event_capture.c | 164 ++++++++++++++++++ 7 files changed, 237 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index 37f27c698e6bd..6e3eaa5cf2143 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -92,6 +92,14 @@ SELECT pg_tde_is_encrypted('test_plain_id_seq'); (1 row) \c :regress_database +CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY; +ERROR: The FILE_COPY strategy cannot be used when there are encrypted objects in the template database: 3 objects found +HINT: Use the WAL_LOG strategy instead. +\c template_db +DROP TABLE test_enc; +\c :regress_database +CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY; +DROP DATABASE new_db_file_copy; DROP DATABASE new_db; DROP DATABASE template_db; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql index 7e9dbcde77051..d62cdc12f5092 100644 --- a/contrib/pg_tde/sql/create_database.sql +++ b/contrib/pg_tde/sql/create_database.sql @@ -45,6 +45,17 @@ SELECT pg_tde_is_encrypted('test_plain_id_seq'); \c :regress_database +CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY; + +\c template_db + +DROP TABLE test_enc; + +\c :regress_database + +CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY; + +DROP DATABASE new_db_file_copy; DROP DATABASE new_db; DROP DATABASE template_db; diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 01ac4b39db34d..5a521b43c45d8 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -115,7 +115,7 @@ static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset); static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); -static int pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos); +static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); static InternalKey *pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type); static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, InternalKey *key); @@ -599,7 +599,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); - old_fd = pg_tde_open_file_read(old_path, &old_curr_pos); + old_fd = pg_tde_open_file_read(old_path, false, &old_curr_pos); new_fd = keyrotation_init_file(&new_signed_key_info, new_path, old_path, &new_curr_pos); /* Read all entries until EOF */ @@ -831,7 +831,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_ Assert(rlocator != NULL); - map_fd = pg_tde_open_file_read(db_map_path, &curr_pos); + map_fd = pg_tde_open_file_read(db_map_path, false, &curr_pos); while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { @@ -847,6 +847,47 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_ return found; } +/* + * Counts number of encrypted objects in a database. + * + * Does not check if objects actually exist but just that they have keys in + * the map file. For the only current caller, checking if we can use + * FILE_COPY, this is good enough but for other workloads where a false + * positive is more harmful this might not be. + * + * Works even if the database has no map file. + */ +int +pg_tde_count_relations(Oid dbOid) +{ + char db_map_path[MAXPGPATH]; + LWLock *lock_pk = tde_lwlock_enc_keys(); + File map_fd; + off_t curr_pos = 0; + TDEMapEntry map_entry; + int count = 0; + + pg_tde_set_db_file_path(dbOid, db_map_path); + + LWLockAcquire(lock_pk, LW_SHARED); + + map_fd = pg_tde_open_file_read(db_map_path, true, &curr_pos); + if (map_fd < 0) + return count; + + while (pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) + { + if (map_entry.flags & TDE_KEY_TYPE_SMGR) + count++; + } + + close(map_fd); + + LWLockRelease(lock_pk); + + return count; +} + bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) { @@ -883,7 +924,7 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) * is raised. */ static int -pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos) +pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos) { int fd; TDEFileHeader fheader; @@ -891,7 +932,9 @@ pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos) Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - fd = pg_tde_open_file_basic(tde_filename, O_RDONLY | PG_BINARY, false); + fd = pg_tde_open_file_basic(tde_filename, O_RDONLY | PG_BINARY, ignore_missing); + if (ignore_missing && fd < 0) + return fd; pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read); *curr_pos = bytes_read; @@ -1144,7 +1187,7 @@ pg_tde_read_last_wal_key(void) } pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - fd = pg_tde_open_file_read(db_map_path, &read_pos); + fd = pg_tde_open_file_read(db_map_path, false, &read_pos); fsize = lseek(fd, 0, SEEK_END); /* No keys */ if (fsize == TDE_FILE_HEADER_SIZE) @@ -1187,7 +1230,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - fd = pg_tde_open_file_read(db_map_path, &read_pos); + fd = pg_tde_open_file_read(db_map_path, false, &read_pos); keys_count = (lseek(fd, 0, SEEK_END) - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 87a9d9aa828bf..72a1ea4ad62a7 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -111,6 +111,7 @@ pg_tde_set_db_file_path(Oid dbOid, char *path) } extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel); +extern int pg_tde_count_relations(Oid dbOid); extern void pg_tde_delete_tde_files(Oid dbOid); diff --git a/contrib/pg_tde/src/include/pg_tde_event_capture.h b/contrib/pg_tde/src/include/pg_tde_event_capture.h index 6ddf64e420b09..d1cccf2a38f91 100644 --- a/contrib/pg_tde/src/include/pg_tde_event_capture.h +++ b/contrib/pg_tde/src/include/pg_tde_event_capture.h @@ -26,8 +26,8 @@ typedef struct TdeCreateEvent * based on earlier encryption status. */ } TdeCreateEvent; +extern void TdeEventCaptureInit(void); extern TdeCreateEvent *GetCurrentTdeCreateEvent(void); - extern void validateCurrentEventTriggerState(bool mightStartTransaction); #endif diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 8576c41c7d923..a30b5c47a4c7d 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -33,6 +33,7 @@ #include "utils/builtins.h" #include "smgr/pg_tde_smgr.h" #include "catalog/tde_global_space.h" +#include "pg_tde_event_capture.h" #include "utils/percona.h" #include "pg_tde_guc.h" #include "access/tableam.h" @@ -111,6 +112,7 @@ _PG_init(void) check_percona_api_version(); TdeGucInit(); + TdeEventCaptureInit(); InitializePrincipalKeyInfo(); InitializeKeyProviderInfo(); diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 23bc02a22bd3a..a75a3f462a7e2 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -16,6 +16,7 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "catalog/pg_class.h" +#include "catalog/pg_database.h" #include "commands/defrem.h" #include "commands/sequence.h" #include "access/table.h" @@ -24,8 +25,13 @@ #include "catalog/namespace.h" #include "commands/event_trigger.h" #include "common/pg_tde_utils.h" +#include "storage/lmgr.h" +#include "tcop/utility.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" #include "pg_tde_event_capture.h" #include "pg_tde_guc.h" +#include "access/pg_tde_tdemap.h" #include "catalog/tde_principal_key.h" #include "miscadmin.h" #include "access/tableam.h" @@ -34,6 +40,7 @@ /* Global variable that gets set at ddl start and cleard out at ddl end*/ static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}}; +static Oid get_db_oid(const char *name); static void reset_current_tde_create_event(void); static Oid get_tde_table_am_oid(void); @@ -354,3 +361,160 @@ get_tde_table_am_oid(void) { return get_table_am_oid("tde_heap", false); } + +static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; + +/* + * Handles utility commands which we cannot handle in the event trigger. + */ +static void +pg_tde_proccess_utility(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + + switch (nodeTag(parsetree)) + { + case T_CreatedbStmt: + { + CreatedbStmt *stmt = castNode(CreatedbStmt, parsetree); + ListCell *option; + char *dbtemplate = "template1"; + char *strategy = "wal_log"; + + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "template") == 0) + dbtemplate = defGetString(defel); + else if (strcmp(defel->defname, "strategy") == 0) + strategy = defGetString(defel); + } + + if (pg_strcasecmp(strategy, "file_copy") == 0) + { + Oid dbOid = get_db_oid(dbtemplate); + + if (dbOid != InvalidOid) + { + int count = pg_tde_count_relations(dbOid); + + if (count > 0) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("The FILE_COPY strategy cannot be used when there are encrypted objects in the template database: %d objects found", count), + errhint("Use the WAL_LOG strategy instead.")); + } + } + } + break; + default: + break; + } + + if (next_ProcessUtility_hook) + (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); +} + +void +TdeEventCaptureInit(void) +{ + next_ProcessUtility_hook = ProcessUtility_hook; + ProcessUtility_hook = pg_tde_proccess_utility; +} + +/* + * A stripped down version of get_db_info() from src/backend/commands/dbcommands.c + */ +static Oid +get_db_oid(const char *name) +{ + Oid resDbOid = InvalidOid; + Relation relation; + + Assert(name); + + relation = table_open(DatabaseRelationId, AccessShareLock); + + /* + * Loop covers the rare case where the database is renamed before we can + * lock it. We try again just in case we can find a new one of the same + * name. + */ + for (;;) + { + ScanKeyData scanKey; + SysScanDesc scan; + HeapTuple tuple; + Oid dbOid; + + /* + * there's no syscache for database-indexed-by-name, so must do it the + * hard way + */ + ScanKeyInit(&scanKey, + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(name)); + + scan = systable_beginscan(relation, DatabaseNameIndexId, true, + NULL, 1, &scanKey); + + tuple = systable_getnext(scan); + + if (!HeapTupleIsValid(tuple)) + { + /* definitely no database of that name */ + systable_endscan(scan); + break; + } + + dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; + + systable_endscan(scan); + + /* + * Now that we have a database OID, we can try to lock the DB. + */ + LockSharedObject(DatabaseRelationId, dbOid, 0, AccessExclusiveLock); + + /* + * And now, re-fetch the tuple by OID. If it's still there and still + * the same name, we win; else, drop the lock and loop back to try + * again. + */ + tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbOid)); + if (HeapTupleIsValid(tuple)) + { + Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); + + if (strcmp(name, NameStr(dbform->datname)) == 0) + { + resDbOid = dbOid; + ReleaseSysCache(tuple); + break; + } + /* can only get here if it was just renamed */ + ReleaseSysCache(tuple); + } + + UnlockSharedObject(DatabaseRelationId, dbOid, 0, AccessExclusiveLock); + } + + table_close(relation, AccessShareLock); + + return resDbOid; +} From 9fe84262cb00dfb3720368a72df86fce3856c595 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 28 Apr 2025 20:40:51 +0300 Subject: [PATCH 157/796] Fix WAL decryption buffer offset calculation A petty typo. It flew undetected all this time due to WAL is written by pages, hence encryption start will always be a multiple of the page size, and in most cases is read page-by-page, hence decryption_offset == read_offest. But crashing the primary after some writes and switching WAL encryption to "on" makes the beginning of the encrypted section be in the middle of the walsender read. Which, in turn, forces the replica to fail and reveals the bug. Fixes PG-1574 --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 9 +++--- contrib/pg_tde/t/012_replication.pl | 30 +++++++++++++++++-- contrib/pg_tde/t/expected/012_replication.out | 24 +++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index f6b0a69cf0a51..788305c7f31a7 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -330,6 +330,9 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, off_t dec_off = XLogSegmentOffset(Max(data_start, curr_key->start_lsn), segSize); off_t dec_end = XLogSegmentOffset(Min(data_end, curr_key->end_lsn), segSize); size_t dec_sz; + char *dec_buf = (char *) buf + (dec_off - offset); + + Assert(dec_off >= offset); CalcXLogPageIVPrefix(tli, segno, curr_key->key->base_iv, iv_prefix); @@ -343,11 +346,9 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %X/%X", - dec_off, offset - dec_off, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); + dec_off, dec_off - offset, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); #endif - PG_TDE_DECRYPT_DATA(iv_prefix, dec_off, - (char *) buf + (offset - dec_off), - dec_sz, (char *) buf + (offset - dec_off), + PG_TDE_DECRYPT_DATA(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, curr_key->key, &curr_key->crypt_ctx); if (dec_off + dec_sz == offset) diff --git a/contrib/pg_tde/t/012_replication.pl b/contrib/pg_tde/t/012_replication.pl index 2de29970471f2..82ebf7c0ab3d6 100644 --- a/contrib/pg_tde/t/012_replication.pl +++ b/contrib/pg_tde/t/012_replication.pl @@ -11,8 +11,11 @@ my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init(allows_streaming => 1); -$primary->append_conf('postgresql.conf', - "shared_preload_libraries = 'pg_tde'"); +$primary->append_conf( + 'postgresql.conf', q{ +checkpoint_timeout = 1h +shared_preload_libraries = 'pg_tde' +}); $primary->start; $primary->backup('backup'); @@ -56,6 +59,29 @@ "SELECT pg_tde_is_encrypted('test_plain_pkey');"); PGTDE::psql($replica, 'postgres', "SELECT * FROM test_plain ORDER BY x;"); +PGTDE::append_to_result_file("-- check primary crash with WAL encryption"); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" +); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file-vault');" +); + +PGTDE::psql($primary, 'postgres', + "CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap;"); +PGTDE::psql($primary, 'postgres', + "INSERT INTO test_enc2 (x) VALUES (1), (2);"); + +PGTDE::psql($primary, 'postgres', + "ALTER SYSTEM SET pg_tde.wal_encrypt = 'on';"); +$primary->kill9; + +PGTDE::append_to_result_file("-- primary start"); +$primary->start; +$primary->wait_for_catchup('replica'); + +PGTDE::psql($replica, 'postgres', "SELECT * FROM test_enc2 ORDER BY x;"); + $replica->stop; $primary->stop; diff --git a/contrib/pg_tde/t/expected/012_replication.out b/contrib/pg_tde/t/expected/012_replication.out index 0ea5f8470599b..ca2938d34bb67 100644 --- a/contrib/pg_tde/t/expected/012_replication.out +++ b/contrib/pg_tde/t/expected/012_replication.out @@ -55,3 +55,27 @@ SELECT * FROM test_plain ORDER BY x; 4 (2 rows) +-- check primary crash with WAL encryption +SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); + pg_tde_add_global_key_provider_file +------------------------------------- + -1 +(1 row) + +SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file-vault'); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap; +INSERT INTO test_enc2 (x) VALUES (1), (2); +ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; +-- primary start +SELECT * FROM test_enc2 ORDER BY x; + x +--- + 1 + 2 +(2 rows) + From 1b2512a19cde61c7031fc95e6ad0a8837872aaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 29 Apr 2025 13:48:18 +0200 Subject: [PATCH 158/796] Remove noop from change key provider GetKeyProviderByName() will not be able to find a match if the name it's supposed to search for is longer than allowed. --- contrib/pg_tde/src/catalog/tde_keyring.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index f85aaed67e4cc..325d969c7a746 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -213,19 +213,12 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) char *provider_type = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(1)); char *options = text_to_cstring(PG_GETARG_TEXT_PP(2)); - int nlen, - olen; + int olen; KeyringProviderRecord provider; /* reports error if not found */ GenericKeyring *keyring = GetKeyProviderByName(provider_name, dbOid); - nlen = strlen(provider_name); - if (nlen >= sizeof(provider.provider_name)) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum length is %ld bytes", sizeof(provider.provider_name) - 1)); - olen = strlen(options); if (olen >= sizeof(provider.options)) ereport(ERROR, @@ -235,7 +228,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) /* Struct will be saved to disk so keep clean */ memset(&provider, 0, sizeof(provider)); provider.provider_id = keyring->keyring_id; - memcpy(provider.provider_name, provider_name, nlen); + memcpy(provider.provider_name, provider_name, strlen(provider_name)); memcpy(provider.options, options, olen); provider.provider_type = get_keyring_provider_from_typename(provider_type); From 286b96bf220336f1f2ecbea20a14d9ffcd6723fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 25 Apr 2025 10:29:44 +0200 Subject: [PATCH 159/796] Validate key name when creating principal key Passing NULL here crashed the server, also do not accept the empty string as a key name. --- contrib/pg_tde/expected/key_provider.out | 18 ++++++++++++++++++ contrib/pg_tde/sql/key_provider.sql | 12 ++++++++++++ contrib/pg_tde/src/catalog/tde_principal_key.c | 17 +++++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 51f45f3eb2200..2eec1e39d0fc9 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -163,4 +163,22 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Creating a file key provider fails if we can't open or create the file SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); ERROR: Failed to open keyring file /cant-create-file-in-root.per: Permission denied +-- Setting principal key fails if key name is NULL +SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); +ERROR: key name cannot be null +SELECT pg_tde_set_key_using_database_key_provider(NULL, 'file-keyring'); +ERROR: key name cannot be null +SELECT pg_tde_set_key_using_global_key_provider(NULL, 'file-keyring'); +ERROR: key name cannot be null +SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); +ERROR: key name cannot be null +-- Empty string is not allowed for a principal key name +SELECT pg_tde_set_default_key_using_global_key_provider('', 'file-keyring'); +ERROR: key name "" is too short +SELECT pg_tde_set_key_using_database_key_provider('', 'file-keyring'); +ERROR: key name "" is too short +SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); +ERROR: key name "" is too short +SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); +ERROR: key name "" is too short DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 78fcc71de83a0..f6044bb786321 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -55,4 +55,16 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Creating a file key provider fails if we can't open or create the file SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); +-- Setting principal key fails if key name is NULL +SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); +SELECT pg_tde_set_key_using_database_key_provider(NULL, 'file-keyring'); +SELECT pg_tde_set_key_using_global_key_provider(NULL, 'file-keyring'); +SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); + +-- Empty string is not allowed for a principal key name +SELECT pg_tde_set_default_key_using_global_key_provider('', 'file-keyring'); +SELECT pg_tde_set_key_using_database_key_provider('', 'file-keyring'); +SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); +SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 31b40cd384bdb..7ab6ddf9a53a1 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -508,7 +508,7 @@ clear_principal_key_cache(Oid databaseId) Datum pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) { - char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); @@ -521,7 +521,7 @@ pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS) { - char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); @@ -534,7 +534,7 @@ pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) { - char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); @@ -547,7 +547,7 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) { - char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); @@ -568,6 +568,15 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to access global key providers")); + if (key_name == NULL) + ereport(ERROR, + errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("key name cannot be null")); + if (strlen(key_name) == 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key name \"\" is too short")); + ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); if (dbOid == DEFAULT_DATA_TDE_OID) From 2a2498a964a3df4236ea1770ccd12ed18587bb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 24 Apr 2025 17:13:27 +0200 Subject: [PATCH 160/796] Require parameters in key provider functions Previously these simply crashed the server on NULL input. --- contrib/pg_tde/expected/key_provider.out | 31 ++++++++++++++++++ contrib/pg_tde/sql/key_provider.sql | 20 ++++++++++++ contrib/pg_tde/src/catalog/tde_keyring.c | 41 ++++++++++++++++++------ 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 2eec1e39d0fc9..828ad6b214e5d 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -163,6 +163,37 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Creating a file key provider fails if we can't open or create the file SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); ERROR: Failed to open keyring file /cant-create-file-in-root.per: Permission denied +-- Creating key providers fails if any required parameter is NULL +SELECT pg_tde_add_database_key_provider(NULL, 'name', '{}'); +ERROR: provider type cannot be null +SELECT pg_tde_add_database_key_provider('file', NULL, '{}'); +ERROR: provider name cannot be null +SELECT pg_tde_add_database_key_provider('file', 'name', NULL); +ERROR: provider options cannot be null +SELECT pg_tde_add_global_key_provider(NULL, 'name', '{}'); +ERROR: provider type cannot be null +SELECT pg_tde_add_global_key_provider('file', NULL, '{}'); +ERROR: provider name cannot be null +SELECT pg_tde_add_global_key_provider('file', 'name', NULL); +ERROR: provider options cannot be null +-- Modifying key providers fails if any required parameter is NULL +SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); +ERROR: provider type cannot be null +SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); +ERROR: provider name cannot be null +SELECT pg_tde_change_database_key_provider('file', 'file-keyring', NULL); +ERROR: provider options cannot be null +SELECT pg_tde_change_global_key_provider(NULL, 'file-keyring', '{}'); +ERROR: provider type cannot be null +SELECT pg_tde_change_global_key_provider('file', NULL, '{}'); +ERROR: provider name cannot be null +SELECT pg_tde_change_global_key_provider('file', 'file-keyring', NULL); +ERROR: provider options cannot be null +-- Deleting key providers fails if key name is NULL +SELECT pg_tde_delete_database_key_provider(NULL); +ERROR: provider_name cannot be null +SELECT pg_tde_delete_global_key_provider(NULL); +ERROR: provider_name cannot be null -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); ERROR: key name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index f6044bb786321..0f2f3fcfc3e23 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -55,6 +55,26 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Creating a file key provider fails if we can't open or create the file SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); +-- Creating key providers fails if any required parameter is NULL +SELECT pg_tde_add_database_key_provider(NULL, 'name', '{}'); +SELECT pg_tde_add_database_key_provider('file', NULL, '{}'); +SELECT pg_tde_add_database_key_provider('file', 'name', NULL); +SELECT pg_tde_add_global_key_provider(NULL, 'name', '{}'); +SELECT pg_tde_add_global_key_provider('file', NULL, '{}'); +SELECT pg_tde_add_global_key_provider('file', 'name', NULL); + +-- Modifying key providers fails if any required parameter is NULL +SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); +SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); +SELECT pg_tde_change_database_key_provider('file', 'file-keyring', NULL); +SELECT pg_tde_change_global_key_provider(NULL, 'file-keyring', '{}'); +SELECT pg_tde_change_global_key_provider('file', NULL, '{}'); +SELECT pg_tde_change_global_key_provider('file', 'file-keyring', NULL); + +-- Deleting key providers fails if key name is NULL +SELECT pg_tde_delete_database_key_provider(NULL); +SELECT pg_tde_delete_global_key_provider(NULL); + -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); SELECT pg_tde_set_key_using_database_key_provider(NULL, 'file-keyring'); diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 325d969c7a746..951c2901299be 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -190,6 +190,17 @@ cleanup_key_provider_info(Oid databaseId) PathNameDeleteTemporaryFile(kp_info_path, false); } +static char * +required_text_argument(NullableDatum arg, const char *name) +{ + if (arg.isnull) + ereport(ERROR, + errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("%s cannot be null", name)); + + return text_to_cstring(DatumGetTextPP(arg.value)); +} + Datum pg_tde_change_database_key_provider(PG_FUNCTION_ARGS) { @@ -210,14 +221,19 @@ pg_tde_change_global_key_provider(PG_FUNCTION_ARGS) static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) { - char *provider_type = text_to_cstring(PG_GETARG_TEXT_PP(0)); - char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(1)); - char *options = text_to_cstring(PG_GETARG_TEXT_PP(2)); + char *provider_type; + char *provider_name; + char *options; int olen; KeyringProviderRecord provider; + GenericKeyring *keyring; + + provider_type = required_text_argument(fcinfo->args[0], "provider type"); + provider_name = required_text_argument(fcinfo->args[1], "provider name"); + options = required_text_argument(fcinfo->args[2], "provider options"); /* reports error if not found */ - GenericKeyring *keyring = GetKeyProviderByName(provider_name, dbOid); + keyring = GetKeyProviderByName(provider_name, dbOid); olen = strlen(options); if (olen >= sizeof(provider.options)) @@ -259,13 +275,17 @@ pg_tde_add_global_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) { - char *provider_type = text_to_cstring(PG_GETARG_TEXT_PP(0)); - char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(1)); - char *options = text_to_cstring(PG_GETARG_TEXT_PP(2)); + char *provider_type; + char *provider_name; + char *options; int nlen, olen; KeyringProviderRecord provider; + provider_type = required_text_argument(fcinfo->args[0], "provider type"); + provider_name = required_text_argument(fcinfo->args[1], "provider name"); + options = required_text_argument(fcinfo->args[2], "provider options"); + nlen = strlen(provider_name); if (nlen >= sizeof(provider.provider_name) - 1) ereport(ERROR, @@ -310,11 +330,14 @@ pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) { - char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); - GenericKeyring *provider = GetKeyProviderByName(provider_name, db_oid); + char *provider_name; + GenericKeyring *provider; int provider_id; bool provider_used; + provider_name = required_text_argument(fcinfo->args[0], "provider_name"); + + provider = GetKeyProviderByName(provider_name, db_oid); if (provider == NULL) { ereport(ERROR, errmsg("Keyring provider not found")); From 52d957e4a1dcebc8b58443d0b2690d280853ba57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 29 Apr 2025 12:50:23 +0200 Subject: [PATCH 161/796] Disallow the empty string as key provider name Also update the error message for too long provider names to make it symmetrical. --- contrib/pg_tde/expected/key_provider.out | 5 +++++ contrib/pg_tde/sql/key_provider.sql | 4 ++++ contrib/pg_tde/src/catalog/tde_keyring.c | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 828ad6b214e5d..57eaf437816e1 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -176,6 +176,11 @@ SELECT pg_tde_add_global_key_provider('file', NULL, '{}'); ERROR: provider name cannot be null SELECT pg_tde_add_global_key_provider('file', 'name', NULL); ERROR: provider options cannot be null +-- Empty string is not allowed for a key provider name +SELECT pg_tde_add_database_key_provider('file', '', '{}'); +ERROR: provider name "" is too short +SELECT pg_tde_add_global_key_provider('file', '', '{}'); +ERROR: provider name "" is too short -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 0f2f3fcfc3e23..f57ef0f73e136 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -63,6 +63,10 @@ SELECT pg_tde_add_global_key_provider(NULL, 'name', '{}'); SELECT pg_tde_add_global_key_provider('file', NULL, '{}'); SELECT pg_tde_add_global_key_provider('file', 'name', NULL); +-- Empty string is not allowed for a key provider name +SELECT pg_tde_add_database_key_provider('file', '', '{}'); +SELECT pg_tde_add_global_key_provider('file', '', '{}'); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 951c2901299be..10735a1855755 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -287,10 +287,15 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) options = required_text_argument(fcinfo->args[2], "provider options"); nlen = strlen(provider_name); + if (nlen == 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("provider name \"\" is too short")); if (nlen >= sizeof(provider.provider_name) - 1) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long provider name, maximum length is %ld bytes", sizeof(provider.provider_name) - 1)); + errmsg("provider name \"%s\" is too long", provider_name), + errhint("Maximum length is %ld bytes.", sizeof(provider.provider_name) - 1)); olen = strlen(options); if (olen >= sizeof(provider.options)) From 2a6667390fbc990d20db3c41fda938fab28f7022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 29 Apr 2025 20:49:13 +0200 Subject: [PATCH 162/796] Add tests for too long names and options This is expected behavior and easy to test. --- contrib/pg_tde/expected/key_provider.out | 26 ++++++++++++++++++++++++ contrib/pg_tde/sql/key_provider.sql | 18 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 57eaf437816e1..f641eb3809bc6 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -181,6 +181,18 @@ SELECT pg_tde_add_database_key_provider('file', '', '{}'); ERROR: provider name "" is too short SELECT pg_tde_add_global_key_provider('file', '', '{}'); ERROR: provider name "" is too short +-- Creating key providers fails if the name is too long +SELECT pg_tde_add_database_key_provider('file', repeat('K', 128), '{}'); +ERROR: provider name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 127 bytes. +SELECT pg_tde_add_global_key_provider('file', repeat('K', 128), '{}'); +ERROR: provider name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 127 bytes. +-- Creating key providers fails if options is too long +SELECT pg_tde_add_database_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); +ERROR: too large provider options, maximum size is 1023 bytes +SELECT pg_tde_add_global_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); +ERROR: too large provider options, maximum size is 1023 bytes -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -194,6 +206,11 @@ SELECT pg_tde_change_global_key_provider('file', NULL, '{}'); ERROR: provider name cannot be null SELECT pg_tde_change_global_key_provider('file', 'file-keyring', NULL); ERROR: provider options cannot be null +-- Modifying key providers fails if options is too long +SELECT pg_tde_change_database_key_provider('file', 'file-provider', json_build_object('key', repeat('V', 1024))); +ERROR: too large provider options, maximum size is 1023 bytes +SELECT pg_tde_change_global_key_provider('file', 'file-keyring', json_build_object('key', repeat('V', 1024))); +ERROR: too large provider options, maximum size is 1023 bytes -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null @@ -217,4 +234,13 @@ SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); ERROR: key name "" is too short SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); ERROR: key name "" is too short +-- Setting principal key fails if the key name is too long +SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +ERROR: too long principal key name, maximum length is 255 bytes +SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provider'); +ERROR: too long principal key name, maximum length is 255 bytes +SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +ERROR: too long principal key name, maximum length is 255 bytes +SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +ERROR: too long principal key name, maximum length is 255 bytes DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index f57ef0f73e136..e18834de35a92 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -67,6 +67,14 @@ SELECT pg_tde_add_global_key_provider('file', 'name', NULL); SELECT pg_tde_add_database_key_provider('file', '', '{}'); SELECT pg_tde_add_global_key_provider('file', '', '{}'); +-- Creating key providers fails if the name is too long +SELECT pg_tde_add_database_key_provider('file', repeat('K', 128), '{}'); +SELECT pg_tde_add_global_key_provider('file', repeat('K', 128), '{}'); + +-- Creating key providers fails if options is too long +SELECT pg_tde_add_database_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); +SELECT pg_tde_add_global_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); @@ -75,6 +83,10 @@ SELECT pg_tde_change_global_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_global_key_provider('file', NULL, '{}'); SELECT pg_tde_change_global_key_provider('file', 'file-keyring', NULL); +-- Modifying key providers fails if options is too long +SELECT pg_tde_change_database_key_provider('file', 'file-provider', json_build_object('key', repeat('V', 1024))); +SELECT pg_tde_change_global_key_provider('file', 'file-keyring', json_build_object('key', repeat('V', 1024))); + -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); @@ -91,4 +103,10 @@ SELECT pg_tde_set_key_using_database_key_provider('', 'file-keyring'); SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); +-- Setting principal key fails if the key name is too long +SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provider'); +SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); + DROP EXTENSION pg_tde; From 488b2ced58d57d78339c223f74d0600b87591981 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 28 Apr 2025 18:49:54 +0200 Subject: [PATCH 163/796] Remove checking for leftover tables on extension install This check makes no sense since tde_heap will get a new oid every time we install it so if there are any leftovers from last time we will not find them anyway. Additionally for this to happen something would need to be broken with pg_depend which I do not see why we should expend this much effort with trying to assert against. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 1 - contrib/pg_tde/src/catalog/tde_keyring.c | 11 +--- .../pg_tde/src/catalog/tde_principal_key.c | 11 +--- contrib/pg_tde/src/common/pg_tde_utils.c | 55 ++----------------- .../pg_tde/src/include/common/pg_tde_utils.h | 4 -- contrib/pg_tde/src/include/pg_tde.h | 2 +- contrib/pg_tde/src/pg_tde.c | 15 +---- 7 files changed, 13 insertions(+), 86 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 5a521b43c45d8..8971ca13cb1ed 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -28,7 +28,6 @@ #include "catalog/tde_principal_key.h" #include "encryption/enc_aes.h" #include "keyring/keyring_api.h" -#include "common/pg_tde_utils.h" #include #include diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 10735a1855755..c4953d612292b 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -84,7 +84,7 @@ static void cleanup_key_provider_info(Oid databaseId); static const char *get_keyring_provider_typename(ProviderType p_type); static List *GetAllKeyringProviders(Oid dbOid); static Size initialize_shared_state(void *start_address); -static void key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); +static void key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); @@ -137,16 +137,11 @@ InitializeKeyProviderInfo(void) RegisterShmemRequest(&key_provider_info_shmem_routine); on_ext_install(key_provider_startup_cleanup, NULL); } + static void -key_provider_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg) +key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg) { - if (tde_tbl_count > 0) - { - ereport(WARNING, - errmsg("failed to perform initialization. database already has %d TDE tables", tde_tbl_count)); - return; - } cleanup_key_provider_info(ext_info->database_id); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 7ab6ddf9a53a1..db2f30cbe276b 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -88,7 +88,7 @@ static Size initialize_shared_state(void *start_address); static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area); static Size required_shared_mem_size(void); static void shared_memory_shutdown(int code, Datum arg); -static void principal_key_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); +static void principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg); static void clear_principal_key_cache(Oid databaseId); static inline dshash_table *get_principal_key_Hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); @@ -472,15 +472,8 @@ push_principal_key_to_cache(TDEPrincipalKey *principalKey) * but unfortunately we do not have any such mechanism in PG. */ static void -principal_key_startup_cleanup(int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg) +principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg) { - if (tde_tbl_count > 0) - { - ereport(WARNING, - errmsg("Failed to perform initialization. database already has %d TDE tables", tde_tbl_count)); - return; - } - clear_principal_key_cache(ext_info->database_id); /* Remove the tde files */ diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 3bb122f51d34d..d7ed21af75b2a 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -11,23 +11,15 @@ #include "postgres.h" -#include "utils/snapmgr.h" -#include "commands/defrem.h" #include "common/pg_tde_utils.h" -#include "miscadmin.h" -#include "catalog/tde_principal_key.h" -#include "access/pg_tde_tdemap.h" #include "pg_tde.h" #ifndef FRONTEND -#include "access/genam.h" -#include "access/heapam.h" - -static Oid -get_tde_table_am_oid(void) -{ - return get_table_am_oid("tde_heap", false); -} +#include "fmgr.h" +#include "catalog/pg_class.h" +#include "access/pg_tde_tdemap.h" +#include "access/relation.h" +#include "utils/rel.h" PG_FUNCTION_INFO_V1(pg_tde_is_encrypted); Datum @@ -57,43 +49,6 @@ pg_tde_is_encrypted(PG_FUNCTION_ARGS) PG_RETURN_BOOL(key != NULL); } -/* - * Returns the number of TDE tables in a database - */ -int -get_tde_tables_count(void) -{ - Relation pg_class; - SysScanDesc scan; - HeapTuple tuple; - int count = 0; - Oid am_oid = get_tde_table_am_oid(); - - /* Open the pg_class table */ - pg_class = table_open(RelationRelationId, AccessShareLock); - - /* Start a scan */ - scan = systable_beginscan(pg_class, ClassOidIndexId, true, - SnapshotSelf, 0, NULL); - - /* Iterate over all tuples in the table */ - while ((tuple = systable_getnext(scan)) != NULL) - { - Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); - - /* Check if the table uses the specified access method */ - if (classForm->relam == am_oid) - count++; - } - - /* End the scan */ - systable_endscan(scan); - - /* Close the pg_class table */ - table_close(pg_class, AccessShareLock); - return count; -} - #endif /* !FRONTEND */ static char tde_data_dir[MAXPGPATH] = PG_TDE_DATA_DIR; diff --git a/contrib/pg_tde/src/include/common/pg_tde_utils.h b/contrib/pg_tde/src/include/common/pg_tde_utils.h index 93c792c458da1..fc2062a0692ad 100644 --- a/contrib/pg_tde/src/include/common/pg_tde_utils.h +++ b/contrib/pg_tde/src/include/common/pg_tde_utils.h @@ -8,10 +8,6 @@ #ifndef PG_TDE_UTILS_H #define PG_TDE_UTILS_H -#ifndef FRONTEND -extern int get_tde_tables_count(void); -#endif /* !FRONTEND */ - extern void pg_tde_set_data_dir(const char *dir); extern const char *pg_tde_get_data_dir(void); diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index d3c14387045a0..e2e83a14b7b60 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -19,7 +19,7 @@ typedef struct XLogExtensionInstall Oid database_id; } XLogExtensionInstall; -typedef void (*pg_tde_on_ext_install_callback) (int tde_tbl_count, XLogExtensionInstall *ext_info, bool redo, void *arg); +typedef void (*pg_tde_on_ext_install_callback) (XLogExtensionInstall *ext_info, bool redo, void *arg); extern void on_ext_install(pg_tde_on_ext_install_callback function, void *arg); diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index a30b5c47a4c7d..f45b5d11d19f3 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -25,7 +25,6 @@ #include "access/xloginsert.h" #include "keyring/keyring_api.h" #include "common/pg_tde_shmem.h" -#include "common/pg_tde_utils.h" #include "catalog/tde_principal_key.h" #include "keyring/keyring_file.h" #include "keyring/keyring_vault.h" @@ -202,19 +201,9 @@ pg_tde_init_data_dir(void) static void run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo) { - int i; - int tde_table_count = 0; - - /* - * Get the number of tde tables in this database should always be zero. - * But still, it prevents the cleanup if someone explicitly calls this - * function. - */ - if (!redo) - tde_table_count = get_tde_tables_count(); - for (i = 0; i < on_ext_install_index; i++) + for (int i = 0; i < on_ext_install_index; i++) on_ext_install_list[i] - .function(tde_table_count, xlrec, redo, on_ext_install_list[i].arg); + .function(xlrec, redo, on_ext_install_list[i].arg); } /* Returns package version */ From 401695021dd5489d9375271eca3de5df38de8534 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 28 Apr 2025 18:55:49 +0200 Subject: [PATCH 164/796] Drop extra unused argument to extension install callback We do not need this level of flexibility for what is an internal hook. --- contrib/pg_tde/src/catalog/tde_keyring.c | 6 +++--- contrib/pg_tde/src/catalog/tde_principal_key.c | 6 +++--- contrib/pg_tde/src/include/pg_tde.h | 5 ++--- contrib/pg_tde/src/pg_tde.c | 18 ++++-------------- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index c4953d612292b..bb71b12ca3984 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -84,7 +84,7 @@ static void cleanup_key_provider_info(Oid databaseId); static const char *get_keyring_provider_typename(ProviderType p_type); static List *GetAllKeyringProviders(Oid dbOid); static Size initialize_shared_state(void *start_address); -static void key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg); +static void key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); @@ -135,11 +135,11 @@ InitializeKeyProviderInfo(void) { ereport(LOG, errmsg("initializing TDE key provider info")); RegisterShmemRequest(&key_provider_info_shmem_routine); - on_ext_install(key_provider_startup_cleanup, NULL); + on_ext_install(key_provider_startup_cleanup); } static void -key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg) +key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo) { cleanup_key_provider_info(ext_info->database_id); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index db2f30cbe276b..15b300c7fd2a4 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -88,7 +88,7 @@ static Size initialize_shared_state(void *start_address); static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area); static Size required_shared_mem_size(void); static void shared_memory_shutdown(int code, Datum arg); -static void principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg); +static void principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo); static void clear_principal_key_cache(Oid databaseId); static inline dshash_table *get_principal_key_Hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); @@ -124,7 +124,7 @@ InitializePrincipalKeyInfo(void) { ereport(LOG, errmsg("Initializing TDE principal key info")); RegisterShmemRequest(&principal_key_info_shmem_routine); - on_ext_install(principal_key_startup_cleanup, NULL); + on_ext_install(principal_key_startup_cleanup); } /* @@ -472,7 +472,7 @@ push_principal_key_to_cache(TDEPrincipalKey *principalKey) * but unfortunately we do not have any such mechanism in PG. */ static void -principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo, void *arg) +principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo) { clear_principal_key_cache(ext_info->database_id); diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index e2e83a14b7b60..651e73a3480bc 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -19,10 +19,9 @@ typedef struct XLogExtensionInstall Oid database_id; } XLogExtensionInstall; -typedef void (*pg_tde_on_ext_install_callback) (XLogExtensionInstall *ext_info, bool redo, void *arg); - -extern void on_ext_install(pg_tde_on_ext_install_callback function, void *arg); +typedef void (*pg_tde_on_ext_install_callback) (XLogExtensionInstall *ext_info, bool redo); +extern void on_ext_install(pg_tde_on_ext_install_callback function); extern void extension_install_redo(XLogExtensionInstall *xlrec); #endif /* PG_TDE_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index f45b5d11d19f3..5c045a3d525b0 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -43,13 +43,7 @@ PG_MODULE_MAGIC; -struct OnExtInstall -{ - pg_tde_on_ext_install_callback function; - void *arg; -}; - -static struct OnExtInstall on_ext_install_list[MAX_ON_INSTALLS]; +static pg_tde_on_ext_install_callback on_ext_install_list[MAX_ON_INSTALLS]; static int on_ext_install_index = 0; static void pg_tde_init_data_dir(void); static void run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo); @@ -165,17 +159,14 @@ extension_install_redo(XLogExtensionInstall *xlrec) * ---------------------------------------------------------------- */ void -on_ext_install(pg_tde_on_ext_install_callback function, void *arg) +on_ext_install(pg_tde_on_ext_install_callback function) { if (on_ext_install_index >= MAX_ON_INSTALLS) ereport(FATAL, errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg_internal("out of on extension install slots")); - on_ext_install_list[on_ext_install_index].function = function; - on_ext_install_list[on_ext_install_index].arg = arg; - - ++on_ext_install_index; + on_ext_install_list[on_ext_install_index++] = function; } /* Creates a tde directory for internal files if not exists */ @@ -202,8 +193,7 @@ static void run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo) { for (int i = 0; i < on_ext_install_index; i++) - on_ext_install_list[i] - .function(xlrec, redo, on_ext_install_list[i].arg); + on_ext_install_list[i] (xlrec, redo); } /* Returns package version */ From a242ddd2d6b2282c8abad351c812dc0f5ccf19a1 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 23 Apr 2025 15:54:24 +0200 Subject: [PATCH 165/796] PG-1467 Install meson with pip in CI scripts Ubunut has outdated meson version in its repos. So intall it with pip instead. --- ci_scripts/ubuntu-deps.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index f8028187dcfa7..4646bee3ad423 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -25,10 +25,13 @@ DEPS=( libzstd-dev lz4 mawk - meson perl pkgconf python3-dev + python3 + python3-pip + python3-setuptools + python3-wheel systemtap-sdt-dev tcl-dev uuid-dev @@ -51,6 +54,7 @@ sudo apt-get update sudo apt-get install -y ${DEPS[@]} bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" +pip3 install meson # Vault wget -O - https://apt.releases.hashicorp.com/gpg | sudo tee /etc/apt/keyrings/hashicorp-archive-keyring.asc From 0da99c695f19e03d57abce26e1081c8dc7da4eb3 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 17 Apr 2025 17:23:20 +0200 Subject: [PATCH 166/796] PG-1467 Add clang builds to CI Add clang compiler to CI matrix --- .github/workflows/psp-matrix.yml | 2 ++ .github/workflows/psp-reusable.yml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/psp-matrix.yml b/.github/workflows/psp-matrix.yml index ec64492a20913..4c1f3319e5fbb 100644 --- a/.github/workflows/psp-matrix.yml +++ b/.github/workflows/psp-matrix.yml @@ -12,11 +12,13 @@ jobs: strategy: matrix: os: ['ubuntu-22.04'] + compiler: [gcc, clang] build_type: [debug,debugoptimized] build_script: [make, meson] uses: ./.github/workflows/psp-reusable.yml with: os: ${{ matrix.os }} + compiler: ${{ matrix.compiler }} build_type: ${{ matrix.build_type }} build_script: ${{ matrix.build_script }} secrets: inherit diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index 44742bab3ffc1..b6f4de3f90eef 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -5,6 +5,9 @@ on: os: type: string required: true + compiler: + type: string + required: true build_type: type: string required: true @@ -13,7 +16,8 @@ on: required: true env: - artifact_name: build-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + artifact_name: build-${{ inputs.os }}-${{ inputs.compiler }}-${{ inputs.build_script }}-${{ inputs.build_type }} + CC: ${{ inputs.compiler }} jobs: build: From 25fe43b21180f26edb65b379b73def58dca5b7f5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 24 Apr 2025 17:58:08 +0200 Subject: [PATCH 167/796] PG-1540 Refactor event triggers to use a stack for the DDL The lack of a stack to handle recurisve calls made it hard to reason about the control flow and hard to add new features like support for ALTER SEQUENCE which also needs to register work to do in the pg_tde_ddl_end trigger. We also simplify the communication with the SMGR code by only giving it a tri-state logic value which tells it what to do when creating a new relation file: encrypt, do not encrypt or retain the old encryption status. --- .../pg_tde/src/include/pg_tde_event_capture.h | 21 +- contrib/pg_tde/src/pg_tde_event_capture.c | 238 +++++++++--------- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 42 ++-- 3 files changed, 143 insertions(+), 158 deletions(-) diff --git a/contrib/pg_tde/src/include/pg_tde_event_capture.h b/contrib/pg_tde/src/include/pg_tde_event_capture.h index d1cccf2a38f91..7c1e3fdd2a4f2 100644 --- a/contrib/pg_tde/src/include/pg_tde_event_capture.h +++ b/contrib/pg_tde/src/include/pg_tde_event_capture.h @@ -11,23 +11,14 @@ #include "nodes/parsenodes.h" #include "access/transam.h" -typedef struct TdeCreateEvent +typedef enum { - FullTransactionId tid; /* transaction id of the last event trigger, - * or 0 */ - bool encryptMode; /* true when the table uses encryption */ - Oid baseTableOid; /* Oid of table on which index is being - * created on. For create table statement this - * contains InvalidOid */ - RangeVar *relation; /* Reference to the parsed relation from - * create statement */ - bool alterAccessMethodMode; /* during ALTER ... SET ACCESS METHOD, - * new file permissions shouldn't be - * based on earlier encryption status. */ -} TdeCreateEvent; + TDE_ENCRYPT_MODE_RETAIN = 0, + TDE_ENCRYPT_MODE_ENCRYPT, + TDE_ENCRYPT_MODE_PLAIN, +} TDEEncryptMode; extern void TdeEventCaptureInit(void); -extern TdeCreateEvent *GetCurrentTdeCreateEvent(void); -extern void validateCurrentEventTriggerState(bool mightStartTransaction); +extern TDEEncryptMode currentTdeEncryptModeValidated(void); #endif diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index a75a3f462a7e2..da349043c8fca 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -37,20 +37,43 @@ #include "access/tableam.h" #include "catalog/tde_global_space.h" -/* Global variable that gets set at ddl start and cleard out at ddl end*/ -static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}}; +typedef struct +{ + Node *parsetree; + TDEEncryptMode encryptMode; + Oid rebuildSequencesFor; +} TdeDdlEvent; + +static FullTransactionId ddlEventStackTid = {}; +static List *ddlEventStack = NIL; static Oid get_db_oid(const char *name); -static void reset_current_tde_create_event(void); static Oid get_tde_table_am_oid(void); PG_FUNCTION_INFO_V1(pg_tde_ddl_command_start_capture); PG_FUNCTION_INFO_V1(pg_tde_ddl_command_end_capture); -TdeCreateEvent * -GetCurrentTdeCreateEvent(void) +static TDEEncryptMode +currentTdeEncryptMode(void) +{ + if (ddlEventStack == NIL) + return TDE_ENCRYPT_MODE_RETAIN; + else + return ((TdeDdlEvent *) llast(ddlEventStack))->encryptMode; +} + +/* + * Make sure that even if a statement failed, and an event trigger end + * trigger didn't fire, we don't accidentaly create encrypted files when + * we don't have to. + */ +TDEEncryptMode +currentTdeEncryptModeValidated(void) { - return &tdeCurrentCreateEvent; + if (!FullTransactionIdEquals(ddlEventStackTid, GetCurrentFullTransactionIdIfAny())) + return TDE_ENCRYPT_MODE_RETAIN; + + return currentTdeEncryptMode(); } static bool @@ -74,7 +97,7 @@ checkPrincipalKeyConfigured(void) static void checkEncryptionStatus(void) { - if (tdeCurrentCreateEvent.encryptMode) + if (currentTdeEncryptMode() == TDE_ENCRYPT_MODE_ENCRYPT) { checkPrincipalKeyConfigured(); } @@ -85,22 +108,38 @@ checkEncryptionStatus(void) } } -void -validateCurrentEventTriggerState(bool mightStartTransaction) +static void +verify_event_stack(void) { - FullTransactionId tid = mightStartTransaction ? GetCurrentFullTransactionId() : GetCurrentFullTransactionIdIfAny(); + FullTransactionId tid = GetCurrentFullTransactionId(); - if (RecoveryInProgress()) + if (!FullTransactionIdEquals(ddlEventStackTid, tid)) { - reset_current_tde_create_event(); - } - else if (tdeCurrentCreateEvent.tid.value != InvalidFullTransactionId.value && tid.value != tdeCurrentCreateEvent.tid.value) - { - /* There was a failed query, end event trigger didn't execute */ - reset_current_tde_create_event(); + ListCell *lc; + + foreach(lc, ddlEventStack) + pfree(lfirst(lc)); + + ddlEventStack = NIL; + ddlEventStackTid = tid; } } +static void +push_event_stack(const TdeDdlEvent *event) +{ + MemoryContext oldCtx; + TdeDdlEvent *e; + + verify_event_stack(); + + oldCtx = MemoryContextSwitchTo(TopMemoryContext); + e = palloc_object(TdeDdlEvent); + *e = *event; + ddlEventStack = lappend(ddlEventStack, e); + MemoryContextSwitchTo(oldCtx); +} + /* * pg_tde_ddl_command_start_capture is an event trigger function triggered * at the start of any DDL command execution. @@ -131,45 +170,36 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) { IndexStmt *stmt = castNode(IndexStmt, parsetree); Relation rel; - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); + TdeDdlEvent event = {.parsetree = parsetree}; rel = table_openrv(stmt->relation, AccessShareLock); - tdeCurrentCreateEvent.baseTableOid = rel->rd_id; - if (rel->rd_rel->relam == get_tde_table_am_oid()) { - /* - * We are creating an index on an encrypted table so set the - * global state. - */ - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); } + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; /* Hold on to lock until end of transaction */ table_close(rel, NoLock); - if (tdeCurrentCreateEvent.encryptMode) - checkPrincipalKeyConfigured(); + push_event_stack(&event); } else if (IsA(parsetree, CreateStmt)) { CreateStmt *stmt = castNode(CreateStmt, parsetree); bool foundAccessMethod = false; - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - + TdeDdlEvent event = {.parsetree = parsetree}; if (stmt->accessMethod) { foundAccessMethod = true; if (strcmp(stmt->accessMethod, "tde_heap") == 0) - { - tdeCurrentCreateEvent.encryptMode = true; - } + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } else if (stmt->partbound) { @@ -193,99 +223,90 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) parentAmOid = get_rel_relam(parentOid); foundAccessMethod = parentAmOid != InvalidOid; - if (foundAccessMethod && parentAmOid == get_tde_table_am_oid()) + if (foundAccessMethod) { - tdeCurrentCreateEvent.encryptMode = true; + if (parentAmOid == get_tde_table_am_oid()) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } } - if (!foundAccessMethod - && strcmp(default_table_access_method, "tde_heap") == 0) + if (!foundAccessMethod) { - tdeCurrentCreateEvent.encryptMode = true; + if (strcmp(default_table_access_method, "tde_heap") == 0) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } + push_event_stack(&event); checkEncryptionStatus(); } else if (IsA(parsetree, CreateTableAsStmt)) { CreateTableAsStmt *stmt = castNode(CreateTableAsStmt, parsetree); - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); + TdeDdlEvent event = {.parsetree = parsetree}; if (shouldEncryptTable(stmt->into->accessMethod)) - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + push_event_stack(&event); checkEncryptionStatus(); } else if (IsA(parsetree, AlterTableStmt)) { AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree); ListCell *lcmd; - Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); AlterTableCmd *setAccessMethod = NULL; + TdeDdlEvent event = {.parsetree = parsetree}; + Oid relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true); - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - - foreach(lcmd, stmt->cmds) + if (relid != InvalidOid) { - AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); - - if (cmd->subtype == AT_SetAccessMethod) - setAccessMethod = cmd; - } - - tdeCurrentCreateEvent.baseTableOid = relationId; + foreach(lcmd, stmt->cmds) + { + AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); - /* - * With a SET ACCESS METHOD clause, use that as the basis for - * decisions. But if it's not present, look up encryption status of - * the table. - */ - if (setAccessMethod) - { - if (shouldEncryptTable(setAccessMethod->name)) - tdeCurrentCreateEvent.encryptMode = true; + if (cmd->subtype == AT_SetAccessMethod) + setAccessMethod = cmd; + } - checkEncryptionStatus(); + /* + * With a SET ACCESS METHOD clause, use that as the basis for + * decisions. But if it's not present, look up encryption status + * of the table. + */ + if (setAccessMethod) + { + event.rebuildSequencesFor = relid; - tdeCurrentCreateEvent.alterAccessMethodMode = true; - } - else - { - if (relationId != InvalidOid) + if (shouldEncryptTable(setAccessMethod->name)) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + } + else { - Relation rel = relation_open(relationId, NoLock); + Relation rel = relation_open(relid, AccessShareLock); if (rel->rd_rel->relam == get_tde_table_am_oid()) { /* * We are altering an encrypted table ALTER TABLE can - * create possibly new files set the global state + * create possibly new files set the global state. */ - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); } relation_close(rel, NoLock); - - if (tdeCurrentCreateEvent.encryptMode) - checkPrincipalKeyConfigured(); } - } - } - else - { - if (!tdeCurrentCreateEvent.alterAccessMethodMode) - { - /* - * Any other type of statement doesn't need TDE mode, except - * during alter access method. To make sure that we have no - * leftover setting from a previous error or something, we just - * reset the status here. - */ - reset_current_tde_create_event(); + + push_event_stack(&event); + checkEncryptionStatus(); } } @@ -301,6 +322,7 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) { EventTriggerData *trigdata; Node *parsetree; + TdeDdlEvent *event; /* Ensure this function is being called as an event trigger */ if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ @@ -310,15 +332,20 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) trigdata = castNode(EventTriggerData, fcinfo->context); parsetree = trigdata->parsetree; - if (IsA(parsetree, AlterTableStmt) && tdeCurrentCreateEvent.alterAccessMethodMode) + if (ddlEventStack == NIL || ((TdeDdlEvent *) llast(ddlEventStack))->parsetree != parsetree) + PG_RETURN_VOID(); + + event = (TdeDdlEvent *) llast(ddlEventStack); + + if (event->rebuildSequencesFor != InvalidOid) { /* * sequences are not updated automatically so force rewrite by * updating their persistence to be the same as before. */ - List *seqlist = getOwnedSequences(tdeCurrentCreateEvent.baseTableOid); + List *seqlist = getOwnedSequences(event->rebuildSequencesFor); ListCell *lc; - Relation rel = relation_open(tdeCurrentCreateEvent.baseTableOid, NoLock); + Relation rel = relation_open(event->rebuildSequencesFor, NoLock); char persistence = rel->rd_rel->relpersistence; relation_close(rel, NoLock); @@ -329,33 +356,14 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) SequenceChangePersistence(seq_relid, persistence); } - - tdeCurrentCreateEvent.alterAccessMethodMode = false; } - /* - * All we need to do is to clear the event state. Except when we are in - * alter access method mode, because during that, we have multiple nested - * event trigger running. Reset should only be called in the end, when it - * is set to false. - */ - if (!tdeCurrentCreateEvent.alterAccessMethodMode) - { - reset_current_tde_create_event(); - } + ddlEventStack = list_delete_last(ddlEventStack); + pfree(event); PG_RETURN_VOID(); } -static void -reset_current_tde_create_event(void) -{ - tdeCurrentCreateEvent.encryptMode = false; - tdeCurrentCreateEvent.baseTableOid = InvalidOid; - tdeCurrentCreateEvent.tid = InvalidFullTransactionId; - tdeCurrentCreateEvent.alterAccessMethodMode = false; -} - static Oid get_tde_table_am_oid(void) { diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index a14a868a21577..d863a497c803e 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -41,40 +41,26 @@ tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) static bool tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { - TdeCreateEvent *event; - /* Do not try to encrypt/decrypt catalog tables */ if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) return false; - /* - * Make sure that even if a statement failed, and an event trigger end - * trigger didn't fire, we don't accidentaly create encrypted files when - * we don't have to. - */ - validateCurrentEventTriggerState(false); - - event = GetCurrentTdeCreateEvent(); - - /* - * Can be many things, such as: CREATE TABLE ALTER TABLE SET ACCESS METHOD - * ALTER TABLE something else on an encrypted table CREATE INDEX ... - * - * Every file has its own key, that makes logistics easier. - */ - if (event->encryptMode) - return true; - - /* check if we had a key for the old locator, if there's one */ - if (!event->alterAccessMethodMode && old_locator) + switch (currentTdeEncryptModeValidated()) { - RelFileLocatorBackend old_smgr_locator = { - .locator = *old_locator, - .backend = smgr_rlocator->backend, - }; - - if (GetSMGRRelationKey(old_smgr_locator)) + case TDE_ENCRYPT_MODE_PLAIN: + return false; + case TDE_ENCRYPT_MODE_ENCRYPT: return true; + case TDE_ENCRYPT_MODE_RETAIN: + if (old_locator) + { + RelFileLocatorBackend old_smgr_locator = { + .locator = *old_locator, + .backend = smgr_rlocator->backend, + }; + + return GetSMGRRelationKey(old_smgr_locator) != 0; + } } return false; From 7f2f26e60b78f9eb31bccfc2ab1a3d3b04c8e85d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 26 Apr 2025 12:29:26 +0200 Subject: [PATCH 168/796] PG-1540 Add support for CREATE and ALTER SEQUENCE to the event trigger When creating a table with a serial/identity column the sequence will get the same encryption status as the owning table but when doing the same using CREATE SEQUENCE and ALTER SEQUENCE the sequence ended up not encrypted so this add support for those. A design choice is that sequences not owned by any table are never encrypted which is unclear if it is a good choice or not. --- contrib/pg_tde/expected/recreate_storage.out | 95 +++++++++++++++ contrib/pg_tde/sql/recreate_storage.sql | 43 +++++++ contrib/pg_tde/src/pg_tde_event_capture.c | 122 ++++++++++++++++++- 3 files changed, 258 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index 58c3b3a055752..0b28c4a72cb76 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -104,6 +104,7 @@ SELECT pg_tde_is_encrypted('rewritemetoo2'); CREATE TABLE encrypted_table ( id SERIAL, + id2 INT, data TEXT, created_at DATE NOT NULL, PRIMARY KEY (id, created_at) @@ -135,6 +136,100 @@ SELECT pg_tde_is_encrypted('encrypted_table_id_seq'); t (1 row) +CREATE TABLE plain_table ( + id2 INT +) USING heap; +-- Starts independent and becomes encrypted +CREATE SEQUENCE independent_seq; +SELECT pg_tde_is_encrypted('independent_seq'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +ALTER SEQUENCE independent_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('independent_seq'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +-- Starts independent and stays plain +CREATE SEQUENCE independent_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('independent_seq2'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +ALTER SEQUENCE independent_seq2 OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('independent_seq2'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +-- Starts owned by an encrypted table and becomes owned by a plain table +CREATE SEQUENCE encrypted_table_id2_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +ALTER SEQUENCE encrypted_table_id2_seq OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +-- Starts owned by an encrypted table and becomes independent +CREATE SEQUENCE encrypted_table_id2_seq2 OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq2'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +ALTER SEQUENCE encrypted_table_id2_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq2'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +-- Starts owned by a plain table and becomes owned by an encrypted table +CREATE SEQUENCE plain_table_id2_seq OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +ALTER SEQUENCE plain_table_id2_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +-- Starts owned by a plain table and becomes independent +CREATE SEQUENCE plain_table_id2_seq2 OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +ALTER SEQUENCE plain_table_id2_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +DROP TABLE plain_table; DROP EXTENSION pg_tde CASCADE; NOTICE: drop cascades to 7 other objects DETAIL: drop cascades to table t1 diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 3296a87152767..8b3e5565966bb 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -41,10 +41,12 @@ SELECT pg_tde_is_encrypted('rewritemetoo2'); CREATE TABLE encrypted_table ( id SERIAL, + id2 INT, data TEXT, created_at DATE NOT NULL, PRIMARY KEY (id, created_at) ) USING tde_heap; + CREATE INDEX idx_date ON encrypted_table (created_at); SELECT pg_tde_is_encrypted('encrypted_table'); CLUSTER encrypted_table USING idx_date; @@ -54,5 +56,46 @@ SELECT pg_tde_is_encrypted('encrypted_table_id_seq'); ALTER SEQUENCE encrypted_table_id_seq RESTART; SELECT pg_tde_is_encrypted('encrypted_table_id_seq'); +CREATE TABLE plain_table ( + id2 INT +) USING heap; + +-- Starts independent and becomes encrypted +CREATE SEQUENCE independent_seq; +SELECT pg_tde_is_encrypted('independent_seq'); +ALTER SEQUENCE independent_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('independent_seq'); + +-- Starts independent and stays plain +CREATE SEQUENCE independent_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('independent_seq2'); +ALTER SEQUENCE independent_seq2 OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('independent_seq2'); + +-- Starts owned by an encrypted table and becomes owned by a plain table +CREATE SEQUENCE encrypted_table_id2_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq'); +ALTER SEQUENCE encrypted_table_id2_seq OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq'); + +-- Starts owned by an encrypted table and becomes independent +CREATE SEQUENCE encrypted_table_id2_seq2 OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq2'); +ALTER SEQUENCE encrypted_table_id2_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('encrypted_table_id2_seq2'); + +-- Starts owned by a plain table and becomes owned by an encrypted table +CREATE SEQUENCE plain_table_id2_seq OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq'); +ALTER SEQUENCE plain_table_id2_seq OWNED BY encrypted_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq'); + +-- Starts owned by a plain table and becomes independent +CREATE SEQUENCE plain_table_id2_seq2 OWNED BY plain_table.id2; +SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); +ALTER SEQUENCE plain_table_id2_seq2 OWNED BY NONE; +SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); + +DROP TABLE plain_table; DROP EXTENSION pg_tde CASCADE; RESET default_table_access_method; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index da349043c8fca..04c859358a8dd 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -42,6 +42,7 @@ typedef struct Node *parsetree; TDEEncryptMode encryptMode; Oid rebuildSequencesFor; + Oid rebuildSequence; } TdeDdlEvent; static FullTransactionId ddlEventStackTid = {}; @@ -295,8 +296,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (rel->rd_rel->relam == get_tde_table_am_oid()) { /* - * We are altering an encrypted table ALTER TABLE can - * create possibly new files set the global state. + * We are altering an encrypted table and ALTER TABLE can + * possibly create new files so set the global state. */ event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; checkPrincipalKeyConfigured(); @@ -309,6 +310,108 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) checkEncryptionStatus(); } } + else if (IsA(parsetree, CreateSeqStmt)) + { + CreateSeqStmt *stmt = (CreateSeqStmt *) parsetree; + ListCell *option; + List *owned_by = NIL; + TdeDdlEvent event = {.parsetree = parsetree}; + + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "owned_by") == 0) + { + owned_by = defGetQualifiedName(defel); + break; + } + } + + if (list_length(owned_by) > 1) + { + List *relname; + RangeVar *rel; + Relation tablerel; + + relname = list_copy_head(owned_by, list_length(owned_by) - 1); + + /* Open and lock rel to ensure it won't go away meanwhile */ + rel = makeRangeVarFromNameList(relname); + tablerel = relation_openrv(rel, AccessShareLock); + + if (tablerel->rd_rel->relam == get_tde_table_am_oid()) + { + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); + } + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + + /* Hold lock until end of transaction */ + relation_close(tablerel, NoLock); + } + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + + push_event_stack(&event); + } + else if (IsA(parsetree, AlterSeqStmt)) + { + AlterSeqStmt *stmt = (AlterSeqStmt *) parsetree; + ListCell *option; + List *owned_by = NIL; + TdeDdlEvent event = {.parsetree = parsetree}; + Oid relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, true); + + if (relid != InvalidOid) + { + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "owned_by") == 0) + { + owned_by = defGetQualifiedName(defel); + break; + } + } + + if (list_length(owned_by) > 1) + { + List *relname; + RangeVar *rel; + Relation tablerel; + + event.rebuildSequence = relid; + + relname = list_copy_head(owned_by, list_length(owned_by) - 1); + + /* Open and lock rel to ensure it won't go away meanwhile */ + rel = makeRangeVarFromNameList(relname); + tablerel = relation_openrv(rel, AccessShareLock); + + if (tablerel->rd_rel->relam == get_tde_table_am_oid()) + { + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); + } + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + + /* Hold lock until end of transaction */ + relation_close(tablerel, NoLock); + } + else if (list_length(owned_by) == 1) + { + event.rebuildSequence = relid; + + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + } + + push_event_stack(&event); + } + } PG_RETURN_VOID(); } @@ -358,6 +461,21 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) } } + if (event->rebuildSequence != InvalidOid) + { + /* + * Seqeunces are not rewritten when just changing owner so force a + * rewrite. There is a small risk of extra overhead if someone changes + * sequence owner and something else at the same time. + */ + Relation rel = relation_open(event->rebuildSequence, NoLock); + char persistence = rel->rd_rel->relpersistence; + + relation_close(rel, NoLock); + + SequenceChangePersistence(event->rebuildSequence, persistence); + } + ddlEventStack = list_delete_last(ddlEventStack); pfree(event); From 6a19214a728ed08b2fd9337ecef2f38ff480000d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 17:55:45 +0200 Subject: [PATCH 169/796] PG-1550 Only let superusers create and modify key providers When creating a key provider you can point its configuration towards any path or network address that PostgreSQL can reach which can be used to attack the environment of PostgreSQL in way normally only restricted to superusers (comapre trusted vs untrusted languages). So make the administration of key providers also follow this convention. This limits the powers of our multitenancy but since we are not sure yet about how users want to use our mutlitenancy it is better to start off restrictive and build a complex solution later. --- .../pg_tde/documentation/docs/functions.md | 8 +-- contrib/pg_tde/expected/access_control.out | 58 ++++++++++--------- contrib/pg_tde/pg_tde--1.0-rc.sql | 40 ------------- contrib/pg_tde/sql/access_control.sql | 24 ++++---- contrib/pg_tde/src/catalog/tde_keyring.c | 31 +++++----- 5 files changed, 61 insertions(+), 100 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 4af66a4fb31be..3f36437e4fd8a 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -4,15 +4,13 @@ The `pg_tde` extension provides functions for managing different aspects of its ## Permission management -By default, `pg_tde` is restrictive. It doesn't allow any operations until permissions are granted to the user. Only superusers can run permission management functions to manage user permissions. Operations on the global scope are limited to superusers only. - -Permissions are based on the normal `EXECUTE` permission on the functions provided by `pg_tde`. Superusers manage them using the `GRANT EXECUTE` and `REVOKE EXECUTE` commands. +By default, `pg_tde` is restrictive. It doesn't allow any operations until permissions are granted to the user. Only superusers can create or modify to key providers or modify objects in the global scope. Functions for viewing keys and for setting the principal key in a database local key provider can on the other hand be run by the database owner and be delegated to normal users using the `GRANT EXECUTE` and `REVOKE EXECUTE` commands. The following functions are also provided for easier management of functionality groups: ### Database local key management -Use these functions to grant or revoke permissions to manage permissions for the current database. They enable or disable all functions related to the providers and keys on the current database: +Use these functions to grant or revoke permissions to manage the key of the current database. They enable or disable all functions related to the key of the current database: * `pg_tde_grant_database_key_management_to_role(role)` * `pg_tde_revoke_database_key_management_from_role(role)` @@ -28,7 +26,6 @@ These functions allow or revoke the use of the permissions management functions: * `pg_tde_grant_grant_management_to_role(role)` * `pg_tde_revoke_grant_management_from_role(role)` - ### Inspections Use these functions to grant or revoke the use of query functions, which do not modify the encryption settings: @@ -36,7 +33,6 @@ Use these functions to grant or revoke the use of query functions, which do not * `pg_tde_grant_key_viewer_to_role(role)` * `pg_tde_revoke_key_viewer_from_role(role)` - ## Key provider management A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 5186a6b117ce2..53083b232ce06 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -1,23 +1,15 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); -ERROR: permission denied for function pg_tde_add_database_key_provider_file SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); ERROR: permission denied for function pg_tde_set_key_using_database_key_provider -SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); -ERROR: must be superuser to modify global key providers -SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'global-file-provider'); -ERROR: must be superuser to access global key providers -SELECT pg_tde_set_server_key_using_global_key_provider('wal-key','global-file-provider'); -ERROR: must be superuser to access global key providers -SELECT pg_tde_set_default_key_using_global_key_provider('def-key', 'global-file-provider'); -ERROR: must be superuser to access global key providers -SELECT pg_tde_delete_database_key_provider('local-file-provider'); -ERROR: permission denied for function pg_tde_delete_database_key_provider -SELECT pg_tde_delete_global_key_provider('global-file-provider'); -ERROR: must be superuser to modify global key providers SELECT pg_tde_list_all_database_key_providers(); ERROR: permission denied for function pg_tde_list_all_database_key_providers SELECT pg_tde_list_all_global_key_providers(); @@ -49,12 +41,6 @@ SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -73,19 +59,35 @@ SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); test-db-key | local-file-provider | 1 (1 row) +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); +ERROR: Principal key does not exists for the database +HINT: Use set_key interface to set the principal key +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_default_key_info(); +ERROR: Principal key does not exists for the database +HINT: Use set_key interface to set the principal key SELECT pg_tde_verify_key(); pg_tde_verify_key ------------------- (1 row) +SELECT pg_tde_verify_server_key(); +ERROR: principal key not configured for current database +SELECT pg_tde_verify_default_key(); +ERROR: principal key not configured for current database -- only superuser +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +ERROR: must be superuser to modify key providers +SELECT pg_tde_change_global_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +ERROR: must be superuser to modify key providers +SELECT pg_tde_delete_database_key_provider('local-file-provider'); +ERROR: must be superuser to modify key providers SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); -ERROR: must be superuser to modify global key providers +ERROR: must be superuser to modify key providers SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); -ERROR: must be superuser to modify global key providers +ERROR: must be superuser to modify key providers SELECT pg_tde_delete_global_key_provider('global-file-provider'); -ERROR: must be superuser to modify global key providers +ERROR: must be superuser to modify key providers SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); @@ -101,16 +103,18 @@ SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked -SELECT * FROM pg_tde_list_all_database_key_providers(); +SELECT pg_tde_list_all_database_key_providers(); ERROR: permission denied for function pg_tde_list_all_database_key_providers -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +SELECT pg_tde_list_all_global_key_providers(); +ERROR: permission denied for function pg_tde_list_all_global_key_providers +SELECT pg_tde_key_info(); ERROR: permission denied for function pg_tde_key_info -SELECT pg_tde_verify_key(); -ERROR: permission denied for function pg_tde_verify_key SELECT pg_tde_server_key_info(); ERROR: permission denied for function pg_tde_server_key_info SELECT pg_tde_default_key_info(); ERROR: permission denied for function pg_tde_default_key_info +SELECT pg_tde_verify_key(); +ERROR: permission denied for function pg_tde_verify_key SELECT pg_tde_verify_server_key(); ERROR: permission denied for function pg_tde_verify_server_key SELECT pg_tde_verify_default_key(); diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index dcd73b0ad675e..260d0fac56dfe 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -535,26 +535,6 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(text, text, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, text, text, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(text, text, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, json) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, text, text,text,text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, JSON, JSON,JSON,JSON) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, text, int, text, text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) TO %I', target_role); END; $$; @@ -586,26 +566,6 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider(text, text, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_add_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider(text, text, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, json) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_file(text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, text, text, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_vault_v2(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, text, int, text, text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_change_database_key_provider_kmip(text, JSON, JSON, JSON, JSON) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(text) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) FROM %I', target_role); END; $$; diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 9ec1b36d733b0..dab6e2d6cc7a1 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -1,18 +1,13 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); + CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied -SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); -SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); -SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'global-file-provider'); -SELECT pg_tde_set_server_key_using_global_key_provider('wal-key','global-file-provider'); -SELECT pg_tde_set_default_key_using_global_key_provider('def-key', 'global-file-provider'); -SELECT pg_tde_delete_database_key_provider('local-file-provider'); -SELECT pg_tde_delete_global_key_provider('global-file-provider'); SELECT pg_tde_list_all_database_key_providers(); SELECT pg_tde_list_all_global_key_providers(); SELECT pg_tde_key_info(); @@ -30,13 +25,19 @@ SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- should now be allowed -SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_default_key_info(); SELECT pg_tde_verify_key(); +SELECT pg_tde_verify_server_key(); +SELECT pg_tde_verify_default_key(); -- only superuser +SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_change_global_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_delete_database_key_provider('local-file-provider'); SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_delete_global_key_provider('global-file-provider'); @@ -51,11 +52,12 @@ SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); SET ROLE regress_pg_tde_access_control; -- verify the view access is revoked -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); -SELECT pg_tde_verify_key(); +SELECT pg_tde_list_all_database_key_providers(); +SELECT pg_tde_list_all_global_key_providers(); +SELECT pg_tde_key_info(); SELECT pg_tde_server_key_info(); SELECT pg_tde_default_key_info(); +SELECT pg_tde_verify_key(); SELECT pg_tde_verify_server_key(); SELECT pg_tde_verify_default_key(); diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index bb71b12ca3984..854da2d3a5fab 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -205,11 +205,6 @@ pg_tde_change_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_change_global_key_provider(PG_FUNCTION_ARGS) { - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers")); - return pg_tde_change_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } @@ -223,6 +218,11 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) KeyringProviderRecord provider; GenericKeyring *keyring; + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify key providers")); + provider_type = required_text_argument(fcinfo->args[0], "provider type"); provider_name = required_text_argument(fcinfo->args[1], "provider name"); options = required_text_argument(fcinfo->args[2], "provider options"); @@ -259,11 +259,6 @@ pg_tde_add_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_add_global_key_provider(PG_FUNCTION_ARGS) { - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers")); - return pg_tde_add_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } @@ -277,6 +272,11 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) olen; KeyringProviderRecord provider; + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify key providers")); + provider_type = required_text_argument(fcinfo->args[0], "provider type"); provider_name = required_text_argument(fcinfo->args[1], "provider name"); options = required_text_argument(fcinfo->args[2], "provider options"); @@ -318,13 +318,7 @@ pg_tde_delete_database_key_provider(PG_FUNCTION_ARGS) Datum pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) { - if (!superuser()) - ereport(ERROR, - errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to modify global key providers")); - return pg_tde_delete_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); - } Datum @@ -335,6 +329,11 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) int provider_id; bool provider_used; + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to modify key providers")); + provider_name = required_text_argument(fcinfo->args[0], "provider_name"); provider = GetKeyProviderByName(provider_name, db_oid); From 1743967eaf727d0ec5bd42ef0f08e2627fb89f71 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 18:35:03 +0200 Subject: [PATCH 170/796] Upload all error logs also on coverage When I made sure we upload all logs I missed to update the coverage action. --- .github/workflows/coverage.yml | 18 ++++++++++++------ .github/workflows/psp-reusable.yml | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b01c01826db24..32b2e17b49646 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -45,12 +45,18 @@ jobs: with: name: coverage-testlog-tde path: | - build/testrun/ - contrib/pg_tde/t/ - contrib/pg_tde/results - contrib/pg_tde/regression.diffs - contrib/pg_tde/regression.out - contrib/pg_tde/*.gcov + build/testrun + contrib/*/log + contrib/*/regression.diffs + contrib/*/regression.out + contrib/*/results + contrib/*/tmp_check + contrib/*/t/results + src/test/*/log + src/test/*/regression.diffs + src/test/*/regression.out + src/test/*/results + src/test/*/tmp_check retention-days: 3 diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index b6f4de3f90eef..bb1972bf32b36 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -79,7 +79,7 @@ jobs: with: name: log-test-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | - src/build/testrun/ + src/build/testrun src/contrib/*/log src/contrib/*/regression.diffs src/contrib/*/regression.out @@ -124,7 +124,7 @@ jobs: with: name: log-test-global-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | - src/build/testrun/ + src/build/testrun src/contrib/*/log src/contrib/*/regression.diffs src/contrib/*/regression.out From ab812c70582e659cf1d5fba652e033827e32bc65 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 19:29:58 +0200 Subject: [PATCH 171/796] Increase pg_ctl timeout when running with code coverage We get random timeouts from pg_ctl in the coverage build only so let's increase the timeout from 60 to 120 seconds. These timeouts might indicate that we have some issue with recovery being too slow in general but for now let's just increase the timeout to avoid random test failures. --- .github/workflows/coverage.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 32b2e17b49646..805be41ba7ac0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,6 +5,9 @@ on: branches: - TDE_REL_17_STABLE +env: + PGCTLTIMEOUT: 120 # Avoid failures on slow recovery + jobs: collect: name: Collect and upload From 8443c7e32a0db6f8b7c22a4336b1d1d94aa5429e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 18:25:07 +0200 Subject: [PATCH 172/796] Remove the single-batch version of pg_tde_crypt() After the removal of tde_heap_basic we almost never encrypt short pieces data. That only happens we decrypt WAL after or before a key switch. It makes no sense to have extra code to optimize for such a rare and non-performance critical case. --- contrib/pg_tde/src/encryption/enc_tde.c | 68 ++----------------------- 1 file changed, 3 insertions(+), 65 deletions(-) diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index aafb3adb541b3..3cdc7398e994a 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -24,55 +24,12 @@ iv_prefix_debug(const char *iv_prefix, char *out_hex) #endif /* - * ================================================================ - * ACTUAL ENCRYPTION/DECRYPTION FUNCTIONS - * ================================================================ - */ - -/* - * pg_tde_crypt_simple: - * Encrypts/decrypts `data` with a given `key`. The result is written to `out`. - * start_offset: is the absolute location of start of data in the file. - * This function assumes that everything is in a single block, and has an assertion ensuring this - */ -static void -pg_tde_crypt_simple(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context) -{ - const uint64 aes_start_block = start_offset / AES_BLOCK_SIZE; - const uint64 aes_end_block = (start_offset + data_len + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE; - const uint64 aes_block_no = start_offset % AES_BLOCK_SIZE; - unsigned char enc_key[DATA_BYTES_PER_AES_BATCH + AES_BLOCK_SIZE]; - - Assert(aes_end_block - aes_start_block <= NUM_AES_BLOCKS_IN_BATCH + 1); - - Aes128EncryptedZeroBlocks(ctxPtr, key->key, iv_prefix, aes_start_block, aes_end_block, enc_key); - -#ifdef ENCRYPTION_DEBUG - { - char ivp_debug[33]; - - iv_prefix_debug(iv_prefix, ivp_debug); - ereport(LOG, - errmsg("%s: Start offset: %lu Data_Len: %u, aes_start_block: %lu, aes_end_block: %lu, IV prefix: %s", - context ? context : "", start_offset, data_len, aes_start_block, aes_end_block, ivp_debug)); - } -#endif - - for (uint32 i = 0; i < data_len; ++i) - { - out[i] = data[i] ^ enc_key[i + aes_block_no]; - } -} - - -/* - * pg_tde_crypt_complex: * Encrypts/decrypts `data` with a given `key`. The result is written to `out`. + * * start_offset: is the absolute location of start of data in the file. - * This is a generic function intended for large data, that do not fit into a single block */ -static void -pg_tde_crypt_complex(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context) +void +pg_tde_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context) { const uint64 aes_start_block = start_offset / AES_BLOCK_SIZE; const uint64 aes_end_block = (start_offset + data_len + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE; @@ -132,22 +89,3 @@ pg_tde_crypt_complex(const char *iv_prefix, uint32 start_offset, const char *dat batch_no++; } } - -/* - * pg_tde_crypt: - * Encrypts/decrypts `data` with a given `key`. The result is written to `out`. - * start_offset: is the absolute location of start of data in the file. - * This function simply selects between the two above variations based on the data length - */ -void -pg_tde_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context) -{ - if (data_len >= DATA_BYTES_PER_AES_BATCH) - { - pg_tde_crypt_complex(iv_prefix, start_offset, data, data_len, out, key, ctxPtr, context); - } - else - { - pg_tde_crypt_simple(iv_prefix, start_offset, data, data_len, out, key, ctxPtr, context); - } -} From f5e79b3f1c5a845b845c9594ad2eb9d6d366feb4 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 19:22:12 +0200 Subject: [PATCH 173/796] Remove an unrelated query from the pg_tde_is_encrypted() test As far as I can tell this query is in no way related to the actual test suite. --- contrib/pg_tde/expected/pg_tde_is_encrypted.out | 3 --- contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 2 -- 2 files changed, 5 deletions(-) diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index b3f3812ac98c0..5c0bea8f4df05 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -1,7 +1,4 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index ceb0831c5c486..b030661962e03 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -1,7 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT * FROM pg_tde_key_info(); - SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); From 9ea4bf6a0bc1aeeb5ec28adfd9cb08d8a88d681f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 19:52:07 +0200 Subject: [PATCH 174/796] Simplify pg_tde_is_provider_used() The early return made use have to close the table in two different places making the code unnecessarily complicated. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 15b300c7fd2a4..a6d585a1508d6 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -911,6 +911,7 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) HeapTuple tuple; SysScanDesc scan; Relation rel; + bool used = false; /* First verify that the global/default oid doesn't use it */ @@ -943,21 +944,12 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) while (HeapTupleIsValid(tuple = systable_getnext(scan))) { dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); - if (principal_key == NULL) - { - continue; - } - - if (providerId == principal_key->keyInfo.keyringId) + if (principal_key && principal_key->keyInfo.keyringId == providerId) { - systable_endscan(scan); - table_close(rel, AccessShareLock); - LWLockRelease(tde_lwlock_enc_keys()); - - return true; + used = true; + break; } } @@ -965,7 +957,7 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) table_close(rel, AccessShareLock); LWLockRelease(tde_lwlock_enc_keys()); - return false; + return used; } else { From 1ef7cd8b2ea66456fb2e43be9283d8960e371bc4 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 20:34:20 +0200 Subject: [PATCH 175/796] Remove extension install hook system Since we only have two hooks it is much easier for the reader to just hardcode the two callbacks. --- contrib/pg_tde/src/catalog/tde_keyring.c | 21 ++------ .../pg_tde/src/catalog/tde_principal_key.c | 13 ++--- .../pg_tde/src/include/catalog/tde_keyring.h | 1 + .../src/include/catalog/tde_principal_key.h | 1 + contrib/pg_tde/src/pg_tde.c | 52 +++++-------------- 5 files changed, 25 insertions(+), 63 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 854da2d3a5fab..2ebac43a28894 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -80,11 +80,9 @@ PG_FUNCTION_INFO_V1(pg_tde_delete_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_list_all_database_key_providers); PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); -static void cleanup_key_provider_info(Oid databaseId); static const char *get_keyring_provider_typename(ProviderType p_type); static List *GetAllKeyringProviders(Oid dbOid); static Size initialize_shared_state(void *start_address); -static void key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); @@ -135,14 +133,15 @@ InitializeKeyProviderInfo(void) { ereport(LOG, errmsg("initializing TDE key provider info")); RegisterShmemRequest(&key_provider_info_shmem_routine); - on_ext_install(key_provider_startup_cleanup); } -static void -key_provider_startup_cleanup(XLogExtensionInstall *ext_info, bool redo) +void +key_provider_startup_cleanup(Oid databaseId) { + char kp_info_path[MAXPGPATH]; - cleanup_key_provider_info(ext_info->database_id); + get_keyring_infofile_path(kp_info_path, databaseId); + PathNameDeleteTemporaryFile(kp_info_path, false); } static const char * @@ -175,16 +174,6 @@ redo_key_provider_info(KeyringProviderRecordInFile *xlrec) LWLockRelease(tde_provider_info_lock()); } -static void -cleanup_key_provider_info(Oid databaseId) -{ - /* Remove the key provider info file */ - char kp_info_path[MAXPGPATH] = {0}; - - get_keyring_infofile_path(kp_info_path, databaseId); - PathNameDeleteTemporaryFile(kp_info_path, false); -} - static char * required_text_argument(NullableDatum arg, const char *name) { diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index a6d585a1508d6..687e1a2cca74c 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -88,7 +88,6 @@ static Size initialize_shared_state(void *start_address); static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area); static Size required_shared_mem_size(void); static void shared_memory_shutdown(int code, Datum arg); -static void principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo); static void clear_principal_key_cache(Oid databaseId); static inline dshash_table *get_principal_key_Hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); @@ -124,7 +123,6 @@ InitializePrincipalKeyInfo(void) { ereport(LOG, errmsg("Initializing TDE principal key info")); RegisterShmemRequest(&principal_key_info_shmem_routine); - on_ext_install(principal_key_startup_cleanup); } /* @@ -470,14 +468,13 @@ push_principal_key_to_cache(TDEPrincipalKey *principalKey) * at the time of extension creation to start fresh again. * Idelly we should have a mechanism to remove these when the extension * but unfortunately we do not have any such mechanism in PG. -*/ -static void -principal_key_startup_cleanup(XLogExtensionInstall *ext_info, bool redo) + */ +void +principal_key_startup_cleanup(Oid databaseId) { - clear_principal_key_cache(ext_info->database_id); + clear_principal_key_cache(databaseId); - /* Remove the tde files */ - pg_tde_delete_tde_files(ext_info->database_id); + pg_tde_delete_tde_files(databaseId); } static void diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 0cd7e4470e91e..c40b5a54925c2 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -34,6 +34,7 @@ extern GenericKeyring *GetKeyProviderByName(const char *provider_name, Oid dbOid extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); extern ProviderType get_keyring_provider_from_typename(char *provider_type); extern void InitializeKeyProviderInfo(void); +extern void key_provider_startup_cleanup(Oid databaseId); extern void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); extern void modify_key_provider_info(KeyringProviderRecord *provider, diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index 6ac864fac8c0c..d83c8bdaeaeee 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -46,6 +46,7 @@ typedef struct XLogPrincipalKeyRotate extern void InitializePrincipalKeyInfo(void); #ifndef FRONTEND +extern void principal_key_startup_cleanup(Oid databaseId); extern LWLock *tde_lwlock_enc_keys(void); extern bool pg_tde_principal_key_configured(Oid databaseId); extern TDEPrincipalKey *GetPrincipalKey(Oid dbOid, LWLockMode lockMode); diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 5c045a3d525b0..6e8cbbf310fc0 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -39,14 +39,11 @@ #include -#define MAX_ON_INSTALLS 5 PG_MODULE_MAGIC; -static pg_tde_on_ext_install_callback on_ext_install_list[MAX_ON_INSTALLS]; -static int on_ext_install_index = 0; static void pg_tde_init_data_dir(void); -static void run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo); + void _PG_init(void); Datum pg_tde_extension_initialize(PG_FUNCTION_ARGS); Datum pg_tde_version(PG_FUNCTION_ARGS); @@ -123,16 +120,22 @@ _PG_init(void) RegisterStorageMgr(); } +static void +extension_install(Oid databaseId) +{ + /* Initialize the TDE dir */ + pg_tde_init_data_dir(); + key_provider_startup_cleanup(databaseId); + principal_key_startup_cleanup(databaseId); +} + Datum pg_tde_extension_initialize(PG_FUNCTION_ARGS) { - /* Initialize the TDE map */ XLogExtensionInstall xlrec; - pg_tde_init_data_dir(); - xlrec.database_id = MyDatabaseId; - run_extension_install_callbacks(&xlrec, false); + extension_install(xlrec.database_id); /* * Also put this info in xlog, so we can replicate the same on the other @@ -144,29 +147,11 @@ pg_tde_extension_initialize(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } -void -extension_install_redo(XLogExtensionInstall *xlrec) -{ - pg_tde_init_data_dir(); - run_extension_install_callbacks(xlrec, true); -} -/* ---------------------------------------------------------------- - * on_ext_install - * - * Register ordinary callback to perform initializations - * run at the time of pg_tde extension installs. - * ---------------------------------------------------------------- - */ void -on_ext_install(pg_tde_on_ext_install_callback function) +extension_install_redo(XLogExtensionInstall *xlrec) { - if (on_ext_install_index >= MAX_ON_INSTALLS) - ereport(FATAL, - errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg_internal("out of on extension install slots")); - - on_ext_install_list[on_ext_install_index++] = function; + extension_install(xlrec->database_id); } /* Creates a tde directory for internal files if not exists */ @@ -185,17 +170,6 @@ pg_tde_init_data_dir(void) } } -/* ------------------ - * Run all of the on_ext_install routines and execute those one by one - * ------------------ - */ -static void -run_extension_install_callbacks(XLogExtensionInstall *xlrec, bool redo) -{ - for (int i = 0; i < on_ext_install_index; i++) - on_ext_install_list[i] (xlrec, redo); -} - /* Returns package version */ Datum pg_tde_version(PG_FUNCTION_ARGS) From f658cd0e7ec1bae198da3a9d32a30e9e5cfa50a2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 20:48:34 +0200 Subject: [PATCH 176/796] Do not keep function for clearing out old files around The pg_tde_extension_initialize() function remained after CREATE EXTENSION and was executable by any user allowing any use to delete all keys and break the server. This function is so dangerous that we should not leave it around at all and instead drop it after having used it. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 260d0fac56dfe..b59a158cb7fec 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -440,11 +440,6 @@ RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C; -CREATE FUNCTION pg_tde_extension_initialize() -RETURNS VOID -LANGUAGE C -AS 'MODULE_PATHNAME'; - CREATE FUNCTION pg_tde_verify_key() RETURNS VOID LANGUAGE C @@ -526,7 +521,12 @@ EXECUTE FUNCTION pg_tde_ddl_command_end_capture(); ALTER EVENT TRIGGER pg_tde_ddl_end ENABLE ALWAYS; -- Per database extension initialization +CREATE FUNCTION pg_tde_extension_initialize() +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; SELECT pg_tde_extension_initialize(); +DROP FUNCTION pg_tde_extension_initialize(); CREATE FUNCTION pg_tde_grant_database_key_management_to_role( target_role TEXT) From 31a07fea36a5e26c40274bb10513f03fd15db883 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 20:58:57 +0200 Subject: [PATCH 177/796] Fix return from pg_tde_extension_initialize() to be void --- contrib/pg_tde/src/pg_tde.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 6e8cbbf310fc0..a657144687050 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -145,7 +145,7 @@ pg_tde_extension_initialize(PG_FUNCTION_ARGS) XLogRegisterData((char *) &xlrec, sizeof(XLogExtensionInstall)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_INSTALL_EXTENSION); - PG_RETURN_NULL(); + PG_RETURN_VOID(); } void From ad2c0a7d3a44494e0ddd1d087ceaaddb3d90176b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 14:28:29 +0200 Subject: [PATCH 178/796] Remove unnecessary function prototypes They do not seem to add anything other than noise. --- contrib/pg_tde/src/pg_tde.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index a657144687050..ffc90e7d49e1c 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -44,11 +44,6 @@ PG_MODULE_MAGIC; static void pg_tde_init_data_dir(void); -void _PG_init(void); -Datum pg_tde_extension_initialize(PG_FUNCTION_ARGS); -Datum pg_tde_version(PG_FUNCTION_ARGS); -Datum pg_tdeam_handler(PG_FUNCTION_ARGS); - static shmem_startup_hook_type prev_shmem_startup_hook = NULL; static shmem_request_hook_type prev_shmem_request_hook = NULL; From ec471de84f656044a33f7c72d57f030162fe69a5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sun, 6 Apr 2025 00:05:25 +0200 Subject: [PATCH 179/796] PG-1551 Handle errors and short reads while reading WAL Do not try to decrypt data not actually read by returning directly if we reached the end of file or got an error. If we get a short read we also return directly which is safe snice pg_tde_crypt() supports any offset, not just multiples of the AES block size. This likely makes it a bit trickier to move to OpenSSL's built-in CTR which we probably want to do in the future but let's cross that bridge when we get to it. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 788305c7f31a7..1bc0348c2eca7 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -270,11 +270,11 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, count, offset, offset, LSN_FORMAT_ARGS(segno)); #endif - /* - * Read data from disk - */ readsz = pg_pread(fd, buf, count, offset); + if (readsz <= 0) + return readsz; + if (!keys) { /* cache is empty, try to read keys from disk */ @@ -302,7 +302,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, #endif XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start); - XLogSegNoOffsetToRecPtr(segno, offset + count, segSize, data_end); + XLogSegNoOffsetToRecPtr(segno, offset + readsz, segSize, data_end); /* * TODO: this is higly ineffective. We should get rid of linked list and @@ -339,7 +339,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, /* We have reached the end of the segment */ if (dec_end == 0) { - dec_end = offset + count; + dec_end = offset + readsz; } dec_sz = dec_end - dec_off; From 51e48623dd0f960d3dd9a445dc96f4c1306974b8 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 18:21:07 +0200 Subject: [PATCH 180/796] PG-1456 Remove grant managment functions These functions did never really do anything since they added no extra permissions since you would need to be allowed to grant and revoke access anyway to call them since they did not use SECURITY DEFINER. --- .../pg_tde/documentation/docs/functions.md | 7 ---- contrib/pg_tde/pg_tde--1.0-rc.sql | 35 ------------------- 2 files changed, 42 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 3f36437e4fd8a..45463fb2b12ff 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -19,13 +19,6 @@ Use these functions to grant or revoke permissions to manage the key of the curr Managment of the global scope is restricted to superusers only. -### Permission management - -These functions allow or revoke the use of the permissions management functions: - -* `pg_tde_grant_grant_management_to_role(role)` -* `pg_tde_revoke_grant_management_from_role(role)` - ### Inspections Use these functions to grant or revoke the use of query functions, which do not modify the encryption settings: diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index b59a158cb7fec..a8cf95b7c0564 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -590,41 +590,6 @@ BEGIN END; $$; -CREATE FUNCTION pg_tde_grant_grant_management_to_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) TO %I', target_role); -END; -$$; - -CREATE FUNCTION pg_tde_revoke_grant_management_from_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_database_key_management_to_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_grant_management_to_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_grant_key_viewer_to_role(TEXT) FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_database_key_management_from_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_grant_management_from_role(TEXT) FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_revoke_key_viewer_from_role(TEXT) FROM %I', target_role); -END; -$$; - -- Revoking all the privileges from the public role SELECT pg_tde_revoke_database_key_management_from_role('public'); -SELECT pg_tde_revoke_grant_management_from_role('public'); SELECT pg_tde_revoke_key_viewer_from_role('public'); From 8d1ef844a0480f186bf571c613a73a9e14cfabf8 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 21:05:02 +0200 Subject: [PATCH 181/796] Use lfirst_node() macro This adds some extra safety plus makes to itnent of the code more obvious. --- contrib/pg_tde/src/pg_tde_event_capture.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 04c859358a8dd..317a00fae6b5b 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -269,7 +269,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) { foreach(lcmd, stmt->cmds) { - AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); + AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd); if (cmd->subtype == AT_SetAccessMethod) setAccessMethod = cmd; @@ -319,7 +319,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) foreach(option, stmt->options) { - DefElem *defel = (DefElem *) lfirst(option); + DefElem *defel = lfirst_node(DefElem, option); if (strcmp(defel->defname, "owned_by") == 0) { @@ -368,7 +368,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) { foreach(option, stmt->options) { - DefElem *defel = (DefElem *) lfirst(option); + DefElem *defel = lfirst_node(DefElem, option); if (strcmp(defel->defname, "owned_by") == 0) { @@ -516,7 +516,7 @@ pg_tde_proccess_utility(PlannedStmt *pstmt, foreach(option, stmt->options) { - DefElem *defel = (DefElem *) lfirst(option); + DefElem *defel = lfirst_node(DefElem, option); if (strcmp(defel->defname, "template") == 0) dbtemplate = defGetString(defel); From 7ca503879dcf680bc42d6c52f024aba06c0f0676 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 22:06:08 +0200 Subject: [PATCH 182/796] Add the compiler to the name of the error log artifacts Since we have some fields we should have all. --- .github/workflows/psp-reusable.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index bb1972bf32b36..e72169af6353a 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -77,7 +77,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: log-test-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + name: log-test-${{ inputs.os }}-${{ inputs.compiler }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun src/contrib/*/log @@ -122,7 +122,7 @@ jobs: uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: log-test-global-tde-${{ inputs.os }}-${{ inputs.build_script }}-${{ inputs.build_type }} + name: log-test-global-tde-${{ inputs.os }}-${{ inputs.compiler }}-${{ inputs.build_script }}-${{ inputs.build_type }} path: | src/build/testrun src/contrib/*/log From 8a73c7a2e60456a896ad613ea539edb213048063 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 21:07:53 +0200 Subject: [PATCH 183/796] Clean up variable initialization in tde_keyring.c --- contrib/pg_tde/src/catalog/tde_keyring.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 2ebac43a28894..e29c9fb79bab3 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -427,6 +427,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid) keyring = (GenericKeyring *) linitial(providers); list_free(providers); } + return keyring; } @@ -435,9 +436,9 @@ GetKeyProviderByID(int provider_id, Oid dbOid) static void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) { - off_t bytes_written = 0; + off_t bytes_written; int fd; - char kp_info_path[MAXPGPATH] = {0}; + char kp_info_path[MAXPGPATH]; Assert(record != NULL); Assert(record->offset_in_file >= 0); @@ -689,15 +690,15 @@ GetKeyProviderByID(int provider_id, Oid dbOid) keyring = (GenericKeyring *) providers->head->ptr; simple_list_free(providers); } + return keyring; } static void simple_list_free(SimplePtrList *list) { - SimplePtrListCell *cell; + SimplePtrListCell *cell = list->head; - cell = list->head; while (cell != NULL) { SimplePtrListCell *next; @@ -721,7 +722,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) { off_t curr_pos = 0; int fd; - char kp_info_path[MAXPGPATH] = {0}; + char kp_info_path[MAXPGPATH]; KeyringProviderRecord provider; #ifndef FRONTEND List *providers_list = NIL; @@ -795,9 +796,10 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) static GenericKeyring * load_keyring_provider_from_record(KeyringProviderRecord *provider) { - GenericKeyring *keyring = NULL; + GenericKeyring *keyring; keyring = load_keyring_provider_options(provider->provider_type, provider->options); + if (keyring) { keyring->keyring_id = provider->provider_id; @@ -806,6 +808,7 @@ load_keyring_provider_from_record(KeyringProviderRecord *provider) memcpy(keyring->options, provider->options, sizeof(keyring->options)); debug_print_kerying(keyring); } + return keyring; } @@ -951,7 +954,7 @@ static int open_keyring_infofile(Oid database_id, int flags) { int fd; - char kp_info_path[MAXPGPATH] = {0}; + char kp_info_path[MAXPGPATH]; get_keyring_infofile_path(kp_info_path, database_id); fd = BasicOpenFile(kp_info_path, flags | PG_BINARY); @@ -970,7 +973,7 @@ open_keyring_infofile(Oid database_id, int flags) static bool fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider) { - off_t bytes_read = 0; + off_t bytes_read; Assert(provider != NULL); Assert(fd >= 0); From 54d20b98f339db38c786f458664b7f31910c4411 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 21:08:31 +0200 Subject: [PATCH 184/796] Call pg_tde_list_all_key_providers_internal() in a more consistent way --- contrib/pg_tde/src/catalog/tde_keyring.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index e29c9fb79bab3..3a4d6885f453b 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -86,7 +86,7 @@ static Size initialize_shared_state(void *start_address); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); -static Datum pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS); +static Datum pg_tde_list_all_key_providers_internal(PG_FUNCTION_ARGS, const char *fname, Oid dbOid); static Size required_shared_mem_size(void); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); @@ -350,20 +350,19 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) Datum pg_tde_list_all_database_key_providers(PG_FUNCTION_ARGS) { - return pg_tde_list_all_key_providers_internal("pg_tde_list_all_database_key_providers_database", false, fcinfo); + return pg_tde_list_all_key_providers_internal(fcinfo, "pg_tde_list_all_database_key_providers_database", MyDatabaseId); } Datum pg_tde_list_all_global_key_providers(PG_FUNCTION_ARGS) { - return pg_tde_list_all_key_providers_internal("pg_tde_list_all_database_key_providers_global", true, fcinfo); + return pg_tde_list_all_key_providers_internal(fcinfo, "pg_tde_list_all_database_key_providers_global", GLOBAL_DATA_TDE_OID); } static Datum -pg_tde_list_all_key_providers_internal(const char *fname, bool global, PG_FUNCTION_ARGS) +pg_tde_list_all_key_providers_internal(PG_FUNCTION_ARGS, const char *fname, Oid dbOid) { - Oid database = (global ? GLOBAL_DATA_TDE_OID : MyDatabaseId); - List *all_providers = GetAllKeyringProviders(database); + List *all_providers = GetAllKeyringProviders(dbOid); ListCell *lc; Tuplestorestate *tupstore; TupleDesc tupdesc; From afdab0e517eed6809c5c12825f80c239b66aead9 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 21:08:38 +0200 Subject: [PATCH 185/796] Use palloc0_object() It works the same as palloc_object() which we already use but intializes the memory to zero. --- contrib/pg_tde/src/catalog/tde_keyring.c | 6 +++--- contrib/pg_tde/src/keyring/keyring_api.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 3a4d6885f453b..dec2aeb4cfc8a 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -831,7 +831,7 @@ load_keyring_provider_options(ProviderType provider_type, char *keyring_options) static FileKeyring * load_file_keyring_provider_options(char *keyring_options) { - FileKeyring *file_keyring = palloc0(sizeof(FileKeyring)); + FileKeyring *file_keyring = palloc0_object(FileKeyring); file_keyring->keyring.type = FILE_KEY_PROVIDER; @@ -855,7 +855,7 @@ load_file_keyring_provider_options(char *keyring_options) static VaultV2Keyring * load_vaultV2_keyring_provider_options(char *keyring_options) { - VaultV2Keyring *vaultV2_keyring = palloc0(sizeof(VaultV2Keyring)); + VaultV2Keyring *vaultV2_keyring = palloc0_object(VaultV2Keyring); vaultV2_keyring->keyring.type = VAULT_V2_KEY_PROVIDER; @@ -884,7 +884,7 @@ load_vaultV2_keyring_provider_options(char *keyring_options) static KmipKeyring * load_kmip_keyring_provider_options(char *keyring_options) { - KmipKeyring *kmip_keyring = palloc0(sizeof(KmipKeyring)); + KmipKeyring *kmip_keyring = palloc0_object(KmipKeyring); kmip_keyring->keyring.type = KMIP_KEY_PROVIDER; diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index f6d9785eef756..3d073ccd59721 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -129,7 +129,7 @@ KeyringGenerateNewKey(const char *key_name, unsigned key_len) Assert(key_len <= 32); /* Struct will be saved to disk so keep clean */ - key = palloc0(sizeof(KeyInfo)); + key = palloc0_object(KeyInfo); key->data.len = key_len; if (!RAND_bytes(key->data.data, key_len)) { From 08fa7bb4764bab0d951eec96bb36287595e3690f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 21:09:30 +0200 Subject: [PATCH 186/796] Clean up debug printing of keyring config --- contrib/pg_tde/src/catalog/tde_keyring.c | 27 +++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index dec2aeb4cfc8a..ac32f9f251d5a 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -915,30 +915,27 @@ load_kmip_keyring_provider_options(char *keyring_options) static void debug_print_kerying(GenericKeyring *keyring) { - int debug_level = DEBUG2; - - elog(debug_level, "Keyring type: %d", keyring->type); - elog(debug_level, "Keyring name: %s", keyring->provider_name); - elog(debug_level, "Keyring id: %d", keyring->keyring_id); + elog(DEBUG2, "Keyring type: %d", keyring->type); + elog(DEBUG2, "Keyring name: %s", keyring->provider_name); + elog(DEBUG2, "Keyring id: %d", keyring->keyring_id); switch (keyring->type) { case FILE_KEY_PROVIDER: - elog(debug_level, "File Keyring Path: %s", ((FileKeyring *) keyring)->file_name); + elog(DEBUG2, "File Keyring Path: %s", ((FileKeyring *) keyring)->file_name); break; case VAULT_V2_KEY_PROVIDER: - elog(debug_level, "Vault Keyring Token: %s", ((VaultV2Keyring *) keyring)->vault_token); - elog(debug_level, "Vault Keyring URL: %s", ((VaultV2Keyring *) keyring)->vault_url); - elog(debug_level, "Vault Keyring Mount Path: %s", ((VaultV2Keyring *) keyring)->vault_mount_path); - elog(debug_level, "Vault Keyring CA Path: %s", ((VaultV2Keyring *) keyring)->vault_ca_path); + elog(DEBUG2, "Vault Keyring Token: %s", ((VaultV2Keyring *) keyring)->vault_token); + elog(DEBUG2, "Vault Keyring URL: %s", ((VaultV2Keyring *) keyring)->vault_url); + elog(DEBUG2, "Vault Keyring Mount Path: %s", ((VaultV2Keyring *) keyring)->vault_mount_path); + elog(DEBUG2, "Vault Keyring CA Path: %s", ((VaultV2Keyring *) keyring)->vault_ca_path); break; case KMIP_KEY_PROVIDER: - elog(debug_level, "KMIP Keyring Host: %s", ((KmipKeyring *) keyring)->kmip_host); - elog(debug_level, "KMIP Keyring Port: %s", ((KmipKeyring *) keyring)->kmip_port); - elog(debug_level, "KMIP Keyring CA Path: %s", ((KmipKeyring *) keyring)->kmip_ca_path); - elog(debug_level, "KMIP Keyring Cert Path: %s", ((KmipKeyring *) keyring)->kmip_cert_path); + elog(DEBUG2, "KMIP Keyring Host: %s", ((KmipKeyring *) keyring)->kmip_host); + elog(DEBUG2, "KMIP Keyring Port: %s", ((KmipKeyring *) keyring)->kmip_port); + elog(DEBUG2, "KMIP Keyring CA Path: %s", ((KmipKeyring *) keyring)->kmip_ca_path); + elog(DEBUG2, "KMIP Keyring Cert Path: %s", ((KmipKeyring *) keyring)->kmip_cert_path); break; case UNKNOWN_KEY_PROVIDER: - elog(debug_level, "Unknown Keyring "); break; } } From 63d20a8f1403ef14491a5bd90732a3f3a59c9c1b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 23:10:37 +0200 Subject: [PATCH 187/796] Remove unnecessary code in TAP tests This code did not test anything which had not already been tested better somewhere else. --- contrib/pg_tde/t/001_basic.pl | 3 -- contrib/pg_tde/t/002_rotate_key.pl | 7 --- contrib/pg_tde/t/007_tde_heap.pl | 49 -------------------- contrib/pg_tde/t/expected/001_basic.out | 1 - contrib/pg_tde/t/expected/002_rotate_key.out | 4 -- contrib/pg_tde/t/expected/007_tde_heap.out | 13 ------ 6 files changed, 77 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index c9619b104e9f2..b68c0f3e80de4 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -24,9 +24,6 @@ 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' ); -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" ); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index f590a81ac4be6..6bcc186430fa1 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -16,13 +16,6 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" ); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 09e72806daa3c..3ca7bd38dc861 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -16,13 +16,6 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" ); @@ -130,48 +123,6 @@ sub verify_table verify_table('test_enc4'); verify_table('test_enc5'); -# Verify that we can't see the data in the file -my $tablefile2 = $node->safe_psql('postgres', 'SHOW data_directory;'); -$tablefile2 .= '/'; -$tablefile2 .= - $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc2\');'); - -my $strings = 'TABLEFILE2 FOUND: '; -$strings .= `(ls $tablefile2 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_result_file($strings); - -$strings = 'CONTAINS FOO (should be empty): '; -$strings .= `strings $tablefile2 | grep foo`; -PGTDE::append_to_result_file($strings); - -# Verify that we can't see the data in the file -my $tablefile3 = $node->safe_psql('postgres', 'SHOW data_directory;'); -$tablefile3 .= '/'; -$tablefile3 .= - $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc3\');'); - -$strings = 'TABLEFILE3 FOUND: '; -$strings .= `(ls $tablefile3 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_result_file($strings); - -$strings = 'CONTAINS FOO (should be empty): '; -$strings .= `strings $tablefile3 | grep foo`; -PGTDE::append_to_result_file($strings); - -# Verify that we can't see the data in the file -my $tablefile4 = $node->safe_psql('postgres', 'SHOW data_directory;'); -$tablefile4 .= '/'; -$tablefile4 .= - $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc4\');'); - -$strings = 'TABLEFILE4 FOUND: '; -$strings .= `(ls $tablefile4 >/dev/null && echo yes) || echo no`; -PGTDE::append_to_result_file($strings); - -$strings = 'CONTAINS FOO (should be empty): '; -$strings .= `strings $tablefile4 | grep foo`; -PGTDE::append_to_result_file($strings); - PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc3;'); diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index c1e385741b99f..9902db68772e2 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -8,7 +8,6 @@ SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables --- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 12129902d2d3d..6eba13f8d32dd 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -1,8 +1,4 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -psql::1: ERROR: principal key not configured -HINT: create one using pg_tde_set_key before using encrypted tables --- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/007_tde_heap.out index 6a5aef32bf731..1a74d9658ace7 100644 --- a/contrib/pg_tde/t/expected/007_tde_heap.out +++ b/contrib/pg_tde/t/expected/007_tde_heap.out @@ -1,8 +1,4 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -psql::1: ERROR: principal key not configured -HINT: create one using pg_tde_set_key before using encrypted tables --- server restart SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- @@ -115,15 +111,6 @@ SELECT * FROM test_enc5 ORDER BY id ASC; (2 rows) TABLEFILE FOR test_enc5 FOUND: yes -CONTAINS FOO (should be empty): -TABLEFILE2 FOUND: yes - -CONTAINS FOO (should be empty): -TABLEFILE3 FOUND: yes - -CONTAINS FOO (should be empty): -TABLEFILE4 FOUND: yes - CONTAINS FOO (should be empty): DROP TABLE test_enc1; DROP TABLE test_enc2; From be5697148e9d9a6e0e8698a9b280653a32c945b3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 23:11:50 +0200 Subject: [PATCH 188/796] Use data_dir function instead of doing a SQL query --- contrib/pg_tde/t/001_basic.pl | 6 ++---- contrib/pg_tde/t/007_tde_heap.pl | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index b68c0f3e80de4..7e5c1065e35f4 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -47,10 +47,8 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); # Verify that we can't see the data in the file -my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); -$tablefile .= '/'; -$tablefile .= - $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'test_enc\');'); +my $tablefile = $node->data_dir . '/' + . $node->safe_psql('postgres', "SELECT pg_relation_filepath('test_enc');"); my $strings = 'TABLEFILE FOUND: '; $strings .= `(ls $tablefile >/dev/null && echo yes) || echo no`; diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 3ca7bd38dc861..2d0daca33e4ab 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -100,9 +100,9 @@ sub verify_table my ($table) = @_; - my $tablefile = $node->safe_psql('postgres', 'SHOW data_directory;'); - $tablefile .= '/'; - $tablefile .= $node->safe_psql('postgres', + my $tablefile = + $node->data_dir . '/' + . $node->safe_psql('postgres', 'SELECT pg_relation_filepath(\'' . $table . '\');'); PGTDE::psql($node, 'postgres', From 3f89df53c82e64ab0115060ee7edbf40581690a7 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 23:12:31 +0200 Subject: [PATCH 189/796] Use -f in Perl instead of ls for checking if a file exists --- contrib/pg_tde/t/001_basic.pl | 7 +++---- contrib/pg_tde/t/007_tde_heap.pl | 10 ++++++---- contrib/pg_tde/t/expected/001_basic.out | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 7e5c1065e35f4..336615047ec0b 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -50,11 +50,10 @@ my $tablefile = $node->data_dir . '/' . $node->safe_psql('postgres', "SELECT pg_relation_filepath('test_enc');"); -my $strings = 'TABLEFILE FOUND: '; -$strings .= `(ls $tablefile >/dev/null && echo yes) || echo no`; -PGTDE::append_to_result_file($strings); +PGTDE::append_to_result_file( + 'TABLEFILE FOUND: ' . (-f $tablefile ? 'yes' : 'no')); -$strings = 'CONTAINS FOO (should be empty): '; +my $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile | grep foo`; PGTDE::append_to_result_file($strings); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 2d0daca33e4ab..646de2b929f6c 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -108,15 +108,17 @@ sub verify_table PGTDE::psql($node, 'postgres', 'SELECT * FROM ' . $table . ' ORDER BY id ASC;'); - my $strings = 'TABLEFILE FOR ' . $table . ' FOUND: '; - $strings .= `(ls $tablefile >/dev/null && echo -n yes) || echo -n no`; - PGTDE::append_to_result_file($strings); + PGTDE::append_to_result_file('TABLEFILE FOR ' + . $table + . ' FOUND: ' + . (-f $tablefile ? 'yes' : 'no')); - $strings = 'CONTAINS FOO (should be empty): '; + my $strings = 'CONTAINS FOO (should be empty): '; $strings .= `strings $tablefile | grep foo`; PGTDE::append_to_result_file($strings); } +# Verify that we can't see the data in the files verify_table('test_enc1'); verify_table('test_enc2'); verify_table('test_enc3'); diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index 9902db68772e2..0b5daa413aab1 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -38,7 +38,6 @@ SELECT * FROM test_enc ORDER BY id ASC; (2 rows) TABLEFILE FOUND: yes - CONTAINS FOO (should be empty): DROP TABLE test_enc; DROP EXTENSION pg_tde; From f926800f7493264ccaf28861cc205a454201ce44 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 3 May 2025 01:59:39 +0200 Subject: [PATCH 190/796] Use Perl function for killing other processes --- contrib/pg_tde/t/003_remote_config.pl | 2 +- contrib/pg_tde/t/006_remote_vault_config.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 1dc06e6f8ec13..7993fe2d7e1a2 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -85,7 +85,7 @@ $node->stop; -system("kill $pid"); +kill('TERM', $pid); # Compare the expected and out file my $compare = PGTDE->compare_results(); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 1012d9168dcce..426e6b40f68e4 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -94,7 +94,7 @@ $node->stop; -system("kill -9 $pid"); +kill('TERM', $pid); # Compare the expected and out file my $compare = PGTDE->compare_results(); From 1a8326e0da606334858e82cabd378b965197b1be Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 3 May 2025 02:00:40 +0200 Subject: [PATCH 191/796] Slightly improve readability of queries in TAP tests Mostly about making the whitespace consistent but also changes some quoting and removes ASC in order by. --- contrib/pg_tde/t/001_basic.pl | 17 ++++--- contrib/pg_tde/t/002_rotate_key.pl | 38 +++++++-------- contrib/pg_tde/t/003_remote_config.pl | 12 ++--- contrib/pg_tde/t/004_file_config.pl | 12 ++--- contrib/pg_tde/t/005_multiple_extensions.pl | 16 +++---- contrib/pg_tde/t/006_remote_vault_config.pl | 12 ++--- contrib/pg_tde/t/007_tde_heap.pl | 40 ++++++++-------- contrib/pg_tde/t/008_key_rotate_tablespace.pl | 10 ++-- contrib/pg_tde/t/009_wal_encrypt.pl | 2 +- contrib/pg_tde/t/014_pg_waldump_basic.pl | 2 +- contrib/pg_tde/t/015_pg_waldump_fullpage.pl | 2 +- contrib/pg_tde/t/expected/001_basic.out | 14 +++--- contrib/pg_tde/t/expected/002_rotate_key.out | 38 +++++++-------- .../pg_tde/t/expected/003_remote_config.out | 12 ++--- contrib/pg_tde/t/expected/004_file_config.out | 12 ++--- .../t/expected/005_multiple_extensions.out | 8 ++-- .../t/expected/006_remote_vault_config.out | 12 ++--- contrib/pg_tde/t/expected/007_tde_heap.out | 46 +++++++++---------- .../t/expected/008_key_rotate_tablespace.out | 10 ++-- contrib/pg_tde/t/expected/009_wal_encrypt.out | 2 +- 20 files changed, 158 insertions(+), 159 deletions(-) diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 336615047ec0b..fc6e495101955 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -17,34 +17,33 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - 'SELECT extname, extversion FROM pg_extension WHERE extname = \'pg_tde\';' -); + "SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde';"); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap;' ); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc (k) VALUES ('foobar'), ('barfoo');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Verify that we can't see the data in the file my $tablefile = $node->data_dir . '/' diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index 6bcc186430fa1..eb188448a999c 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -17,37 +17,37 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');" + "SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per');" + "SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2g.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per');" + "SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3.per');" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' ); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5),(6);'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Rotate key PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; @@ -58,13 +58,13 @@ PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Again rotate key PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');" + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2');" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; @@ -75,13 +75,13 @@ PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Again rotate key PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; @@ -92,7 +92,7 @@ PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # TODO: add method to query current info # And maybe debug tools to show what's in a file keyring? @@ -101,7 +101,7 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; @@ -112,10 +112,10 @@ PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::psql($node, 'postgres', - 'ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF;'); + 'ALTER SYSTEM SET pg_tde.inherit_global_providers = off;'); # Things still work after a restart PGTDE::append_to_result_file("-- server restart"); @@ -133,7 +133,7 @@ ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2');" + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2');" ); PGTDE::psql($node, 'postgres', "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl index 7993fe2d7e1a2..d7d2c70aa25c1 100644 --- a/contrib/pg_tde/t/003_remote_config.pl +++ b/contrib/pg_tde/t/003_remote_config.pl @@ -60,24 +60,24 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ));" + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello'));" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');" ); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' ); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5), (6);'); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl index 948b4d345976d..82a0779e6d196 100644 --- a/contrib/pg_tde/t/004_file_config.pl +++ b/contrib/pg_tde/t/004_file_config.pl @@ -21,24 +21,24 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));" + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location'));" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');" ); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' ); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (5),(6);'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (5), (6);'); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/005_multiple_extensions.pl index 3230ab4a0171d..7750a9945d842 100644 --- a/contrib/pg_tde/t/005_multiple_extensions.pl +++ b/contrib/pg_tde/t/005_multiple_extensions.pl @@ -119,28 +119,28 @@ $node->psql( 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' ));", + "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location'));", extra_params => ['-a']); $node->psql( 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider');", + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');", extra_params => ['-a']); $stdout = $node->safe_psql( 'postgres', - 'CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;', + 'CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;', extra_params => ['-a']); PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql( 'postgres', - 'INSERT INTO test_enc1 (k) VALUES (5),(6);', + 'INSERT INTO test_enc1 (k) VALUES (5), (6);', extra_params => ['-a']); PGTDE::append_to_result_file($stdout); $stdout = $node->safe_psql( 'postgres', - 'SELECT * FROM test_enc1 ORDER BY id ASC;', + 'SELECT * FROM test_enc1 ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_result_file($stdout); @@ -149,7 +149,7 @@ $stdout = $node->safe_psql( 'postgres', - 'SELECT * FROM test_enc1 ORDER BY id ASC;', + 'SELECT * FROM test_enc1 ORDER BY id;', extra_params => ['-a']); PGTDE::append_to_result_file($stdout); @@ -162,7 +162,7 @@ # Print PGSM settings ($cmdret, $stdout, $stderr) = $node->psql( 'postgres', - "SELECT name, setting, unit, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, pending_restart FROM pg_settings WHERE name='pg_stat_monitor.pgsm_query_shared_buffer';", + "SELECT name, setting, unit, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, pending_restart FROM pg_settings WHERE name = 'pg_stat_monitor.pgsm_query_shared_buffer';", extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); ok($cmdret == 0, "Print PGTDE EXTENSION Settings"); PGTDE::append_to_debug_file($stdout); @@ -187,7 +187,7 @@ ($cmdret, $stdout, $stderr) = $node->psql( 'postgres', - 'SELECT datname, substr(query,0,150) AS query, SUM(calls) AS calls FROM pg_stat_monitor GROUP BY datname, query ORDER BY datname, query, calls;', + 'SELECT datname, substr(query, 0, 150) AS query, SUM(calls) AS calls FROM pg_stat_monitor GROUP BY datname, query ORDER BY datname, query, calls;', extra_params => [ '-a', '-Pformat=aligned', '-Ptuples_only=off' ]); ok($cmdret == 0, "SELECT XXX FROM pg_stat_monitor"); PGTDE::append_to_debug_file($stdout); diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl index 426e6b40f68e4..a1d6f6e0664e7 100644 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ b/contrib/pg_tde/t/006_remote_vault_config.pl @@ -69,24 +69,24 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL);" + "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token'), json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url'), to_json('secret'::text), NULL);" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'vault-provider');" ); PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' ); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5),(6);'); +PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5), (6);'); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/007_tde_heap.pl index 646de2b929f6c..8f3deeb2316ff 100644 --- a/contrib/pg_tde/t/007_tde_heap.pl +++ b/contrib/pg_tde/t/007_tde_heap.pl @@ -17,79 +17,79 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); ######################### test_enc1 (simple create table w tde_heap) PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc1 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap;' ); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc1 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc1 (k) VALUES ('foobar'), ('barfoo');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id;'); ######################### test_enc2 (create heap + alter to tde_heap) PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));'); + 'CREATE TABLE test_enc2 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id));'); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc2 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc2 (k) VALUES ('foobar'), ('barfoo');"); PGTDE::psql($node, 'postgres', 'ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap;'); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); ######################### test_enc3 (default_table_access_method) PGTDE::psql($node, 'postgres', - 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id));' + 'SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id));' ); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc3 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc3 (k) VALUES ('foobar'), ('barfoo');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc3 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc3 ORDER BY id;'); ######################### test_enc4 (create heap + alter default) PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap;' + 'CREATE TABLE test_enc4 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING heap;' ); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc4 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc4 (k) VALUES ('foobar'), ('barfoo');"); PGTDE::psql($node, 'postgres', 'SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT;' ); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc4 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc4 ORDER BY id;'); ######################### test_enc5 (create tde_heap + truncate) PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap;' + 'CREATE TABLE test_enc5 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap;' ); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc5 (k) VALUES ('foobar'), ('barfoo');"); PGTDE::psql($node, 'postgres', 'CHECKPOINT;'); PGTDE::psql($node, 'postgres', 'TRUNCATE test_enc5;'); PGTDE::psql($node, 'postgres', - 'INSERT INTO test_enc5 (k) VALUES (\'foobar\'),(\'barfoo\');'); + "INSERT INTO test_enc5 (k) VALUES ('foobar'), ('barfoo');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id ASC;'); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc5 ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); $node->restart; @@ -103,10 +103,10 @@ sub verify_table my $tablefile = $node->data_dir . '/' . $node->safe_psql('postgres', - 'SELECT pg_relation_filepath(\'' . $table . '\');'); + "SELECT pg_relation_filepath('" . $table . "');"); PGTDE::psql($node, 'postgres', - 'SELECT * FROM ' . $table . ' ORDER BY id ASC;'); + 'SELECT * FROM ' . $table . ' ORDER BY id;'); PGTDE::append_to_result_file('TABLEFILE FOR ' . $table diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/008_key_rotate_tablespace.pl index 9369940d9e8a9..310ecba3dbf22 100644 --- a/contrib/pg_tde/t/008_key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/008_key_rotate_tablespace.pl @@ -22,18 +22,18 @@ PGTDE::psql($node, 'tbc', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'tbc', - "SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" ); PGTDE::psql($node, 'tbc', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault');" + "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); PGTDE::psql( $node, 'tbc', " CREATE TABLE country_table ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ) USING tde_heap; "); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index e4ec0b7dc888d..f116853fd87a8 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -20,7 +20,7 @@ PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per');" ); PGTDE::psql($node, 'postgres', diff --git a/contrib/pg_tde/t/014_pg_waldump_basic.pl b/contrib/pg_tde/t/014_pg_waldump_basic.pl index 09315cf39d421..ec798cd765bb1 100644 --- a/contrib/pg_tde/t/014_pg_waldump_basic.pl +++ b/contrib/pg_tde/t/014_pg_waldump_basic.pl @@ -28,7 +28,7 @@ $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_tde_test_keyring-wal.per');" ); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" diff --git a/contrib/pg_tde/t/015_pg_waldump_fullpage.pl b/contrib/pg_tde/t/015_pg_waldump_fullpage.pl index 877ff2d34599b..782d248f1b09d 100644 --- a/contrib/pg_tde/t/015_pg_waldump_fullpage.pl +++ b/contrib/pg_tde/t/015_pg_waldump_fullpage.pl @@ -42,7 +42,7 @@ sub get_block_lsn $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','/tmp/pg_tde_test_keyring-wal.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_tde_test_keyring-wal.per');" ); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index 0b5daa413aab1..ff9315540cd75 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -5,24 +5,24 @@ SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; pg_tde | 1.0-rc (1 row) -CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; +CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; psql::1: ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc (k) VALUES ('foobar'),('barfoo'); -SELECT * FROM test_enc ORDER BY id ASC; +CREATE TABLE test_enc (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc (k) VALUES ('foobar'), ('barfoo'); +SELECT * FROM test_enc ORDER BY id; id | k ----+-------- 1 | foobar @@ -30,7 +30,7 @@ SELECT * FROM test_enc ORDER BY id ASC; (2 rows) -- server restart -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+-------- 1 | foobar diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 6eba13f8d32dd..15345de30ad13 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -1,23 +1,23 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_add_database_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per'); +SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); pg_tde_add_database_key_provider_file --------------------------------------- 2 (1 row) -SELECT pg_tde_add_global_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2g.per'); +SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2g.per'); pg_tde_add_global_key_provider_file ------------------------------------- -1 (1 row) -SELECT pg_tde_add_global_key_provider_file('file-3','/tmp/pg_tde_test_keyring_3.per'); +SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3.per'); pg_tde_add_global_key_provider_file ------------------------------------- -2 @@ -30,15 +30,15 @@ SELECT pg_tde_list_all_database_key_providers(); (2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") (2 rows) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc (k) VALUES (5),(6); -SELECT * FROM test_enc ORDER BY id ASC; +CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc (k) VALUES (5), (6); +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -51,7 +51,7 @@ SELECT pg_tde_set_key_using_database_key_provider('rotated-key1'); (1 row) -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -68,20 +68,20 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 2 | 6 (2 rows) -SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); +SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -98,7 +98,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -111,7 +111,7 @@ SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false); (1 row) -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -128,7 +128,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -141,7 +141,7 @@ SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false) (1 row) -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 @@ -158,14 +158,14 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key -SELECT * FROM test_enc ORDER BY id ASC; +SELECT * FROM test_enc ORDER BY id; id | k ----+--- 1 | 5 2 | 6 (2 rows) -ALTER SYSTEM SET pg_tde.inherit_global_providers = OFF; +ALTER SYSTEM SET pg_tde.inherit_global_providers = off; -- server restart SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON @@ -178,7 +178,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); psql::1: ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key -SELECT pg_tde_set_key_using_database_key_provider('rotated-key2','file-2'); +SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/003_remote_config.out b/contrib/pg_tde/t/expected/003_remote_config.out index fc4208ba360ed..d547046d7d501 100644 --- a/contrib/pg_tde/t/expected/003_remote_config.out +++ b/contrib/pg_tde/t/expected/003_remote_config.out @@ -1,19 +1,19 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' )); +SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello')); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc2 (k) VALUES (5),(6); -SELECT * FROM test_enc2 ORDER BY id ASC; +CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc2 (k) VALUES (5), (6); +SELECT * FROM test_enc2 ORDER BY id; id | k ----+--- 1 | 5 @@ -21,7 +21,7 @@ SELECT * FROM test_enc2 ORDER BY id ASC; (2 rows) -- server restart -SELECT * FROM test_enc2 ORDER BY id ASC; +SELECT * FROM test_enc2 ORDER BY id; id | k ----+--- 1 | 5 diff --git a/contrib/pg_tde/t/expected/004_file_config.out b/contrib/pg_tde/t/expected/004_file_config.out index 878e2d3621ea0..c2910206332fe 100644 --- a/contrib/pg_tde/t/expected/004_file_config.out +++ b/contrib/pg_tde/t/expected/004_file_config.out @@ -1,19 +1,19 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-provider', json_object( 'type' VALUE 'file', 'path' VALUE '/tmp/datafile-location' )); +SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location')); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc1 (k) VALUES (5),(6); -SELECT * FROM test_enc1 ORDER BY id ASC; +CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc1 (k) VALUES (5), (6); +SELECT * FROM test_enc1 ORDER BY id; id | k ----+--- 1 | 5 @@ -21,7 +21,7 @@ SELECT * FROM test_enc1 ORDER BY id ASC; (2 rows) -- server restart -SELECT * FROM test_enc1 ORDER BY id ASC; +SELECT * FROM test_enc1 ORDER BY id; id | k ----+--- 1 | 5 diff --git a/contrib/pg_tde/t/expected/005_multiple_extensions.out b/contrib/pg_tde/t/expected/005_multiple_extensions.out index e5f705d3137c3..a27fc6c8113ff 100644 --- a/contrib/pg_tde/t/expected/005_multiple_extensions.out +++ b/contrib/pg_tde/t/expected/005_multiple_extensions.out @@ -1,11 +1,11 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE TABLE test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc1 (k) VALUES (5),(6); -SELECT * FROM test_enc1 ORDER BY id ASC; +CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc1 (k) VALUES (5), (6); +SELECT * FROM test_enc1 ORDER BY id; 1|5 2|6 -- server restart -SELECT * FROM test_enc1 ORDER BY id ASC; +SELECT * FROM test_enc1 ORDER BY id; 1|5 2|6 DROP TABLE test_enc1; diff --git a/contrib/pg_tde/t/expected/006_remote_vault_config.out b/contrib/pg_tde/t/expected/006_remote_vault_config.out index 8fe7b12360bc0..3afa1d253d5f8 100644 --- a/contrib/pg_tde/t/expected/006_remote_vault_config.out +++ b/contrib/pg_tde/t/expected/006_remote_vault_config.out @@ -1,19 +1,19 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token' ), json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url' ), to_json('secret'::text), NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token'), json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url'), to_json('secret'::text), NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','vault-provider'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'vault-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc2(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc2 (k) VALUES (5),(6); -SELECT * FROM test_enc2 ORDER BY id ASC; +CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc2 (k) VALUES (5), (6); +SELECT * FROM test_enc2 ORDER BY id; id | k ----+--- 1 | 5 @@ -21,7 +21,7 @@ SELECT * FROM test_enc2 ORDER BY id ASC; (2 rows) -- server restart -SELECT * FROM test_enc2 ORDER BY id ASC; +SELECT * FROM test_enc2 ORDER BY id; id | k ----+--- 1 | 5 diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/007_tde_heap.out index 1a74d9658ace7..c3ea0e80ac6e7 100644 --- a/contrib/pg_tde/t/expected/007_tde_heap.out +++ b/contrib/pg_tde/t/expected/007_tde_heap.out @@ -1,60 +1,60 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) -CREATE TABLE test_enc1(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc1 (k) VALUES ('foobar'),('barfoo'); -SELECT * FROM test_enc1 ORDER BY id ASC; +CREATE TABLE test_enc1 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc1 (k) VALUES ('foobar'), ('barfoo'); +SELECT * FROM test_enc1 ORDER BY id; id | k ----+-------- 1 | foobar 2 | barfoo (2 rows) -CREATE TABLE test_enc2(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)); -INSERT INTO test_enc2 (k) VALUES ('foobar'),('barfoo'); +CREATE TABLE test_enc2 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)); +INSERT INTO test_enc2 (k) VALUES ('foobar'), ('barfoo'); ALTER TABLE test_enc2 SET ACCESS METHOD tde_heap; -SELECT * FROM test_enc2 ORDER BY id ASC; +SELECT * FROM test_enc2 ORDER BY id; id | k ----+-------- 1 | foobar 2 | barfoo (2 rows) -SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)); -INSERT INTO test_enc3 (k) VALUES ('foobar'),('barfoo'); -SELECT * FROM test_enc3 ORDER BY id ASC; +SET default_table_access_method = "tde_heap"; CREATE TABLE test_enc3 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)); +INSERT INTO test_enc3 (k) VALUES ('foobar'), ('barfoo'); +SELECT * FROM test_enc3 ORDER BY id; id | k ----+-------- 1 | foobar 2 | barfoo (2 rows) -CREATE TABLE test_enc4(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING heap; -INSERT INTO test_enc4 (k) VALUES ('foobar'),('barfoo'); +CREATE TABLE test_enc4 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING heap; +INSERT INTO test_enc4 (k) VALUES ('foobar'), ('barfoo'); SET default_table_access_method = "tde_heap"; ALTER TABLE test_enc4 SET ACCESS METHOD DEFAULT; -SELECT * FROM test_enc4 ORDER BY id ASC; +SELECT * FROM test_enc4 ORDER BY id; id | k ----+-------- 1 | foobar 2 | barfoo (2 rows) -CREATE TABLE test_enc5(id SERIAL,k VARCHAR(32),PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc5 (k) VALUES ('foobar'),('barfoo'); +CREATE TABLE test_enc5 (id SERIAL, k VARCHAR(32), PRIMARY KEY (id)) USING tde_heap; +INSERT INTO test_enc5 (k) VALUES ('foobar'), ('barfoo'); CHECKPOINT; TRUNCATE test_enc5; -INSERT INTO test_enc5 (k) VALUES ('foobar'),('barfoo'); -SELECT * FROM test_enc5 ORDER BY id ASC; +INSERT INTO test_enc5 (k) VALUES ('foobar'), ('barfoo'); +SELECT * FROM test_enc5 ORDER BY id; id | k ----+-------- 3 | foobar @@ -63,7 +63,7 @@ SELECT * FROM test_enc5 ORDER BY id ASC; -- server restart ########################### -SELECT * FROM test_enc1 ORDER BY id ASC; +SELECT * FROM test_enc1 ORDER BY id; id | k ----+-------- 1 | foobar @@ -73,7 +73,7 @@ SELECT * FROM test_enc1 ORDER BY id ASC; TABLEFILE FOR test_enc1 FOUND: yes CONTAINS FOO (should be empty): ########################### -SELECT * FROM test_enc2 ORDER BY id ASC; +SELECT * FROM test_enc2 ORDER BY id; id | k ----+-------- 1 | foobar @@ -83,7 +83,7 @@ SELECT * FROM test_enc2 ORDER BY id ASC; TABLEFILE FOR test_enc2 FOUND: yes CONTAINS FOO (should be empty): ########################### -SELECT * FROM test_enc3 ORDER BY id ASC; +SELECT * FROM test_enc3 ORDER BY id; id | k ----+-------- 1 | foobar @@ -93,7 +93,7 @@ SELECT * FROM test_enc3 ORDER BY id ASC; TABLEFILE FOR test_enc3 FOUND: yes CONTAINS FOO (should be empty): ########################### -SELECT * FROM test_enc4 ORDER BY id ASC; +SELECT * FROM test_enc4 ORDER BY id; id | k ----+-------- 1 | foobar @@ -103,7 +103,7 @@ SELECT * FROM test_enc4 ORDER BY id ASC; TABLEFILE FOR test_enc4 FOUND: yes CONTAINS FOO (should be empty): ########################### -SELECT * FROM test_enc5 ORDER BY id ASC; +SELECT * FROM test_enc5 ORDER BY id; id | k ----+-------- 3 | foobar diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index 7d88a74e1c47d..b329fc7ac610f 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -1,22 +1,22 @@ SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION ''; CREATE DATABASE tbc TABLESPACE = test_tblspace; CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- 1 (1 row) -SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); +SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- (1 row) CREATE TABLE country_table ( - country_id serial primary key, - country_name text unique not null, - continent text not null + country_id serial primary key, + country_name text unique not null, + continent text not null ) USING tde_heap; INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index c0df1e2a03110..3838f611476f3 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -1,5 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_global_key_provider_file('file-keyring-010','/tmp/pg_tde_test_keyring010.per'); +SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per'); pg_tde_add_global_key_provider_file ------------------------------------- -1 From fc72bbfd48aef8e2c7ad1c19a4ed023fd807c936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 5 May 2025 12:52:08 +0200 Subject: [PATCH 192/796] Rename misnamed enum Keyring was misspelled as Kering. --- contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index e811040d82976..cc70ba3fc34a6 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -43,11 +43,11 @@ * JSON parser state */ -typedef enum JsonKeringSemState +typedef enum JsonKeyringSemState { JK_EXPECT_TOP_FIELD, JK_EXPECT_EXTERN_VAL, -} JsonKeringSemState; +} JsonKeyringSemState; #define KEYRING_REMOTE_FIELD_TYPE "remote" #define KEYRING_FILE_FIELD_TYPE "file" @@ -118,7 +118,7 @@ typedef struct JsonKeyringState * fields because of the external values */ JsonKeyringField field[MAX_JSON_DEPTH]; - JsonKeringSemState state; + JsonKeyringSemState state; int level; /* From 551a28095431a874adc2d98d69832ca8c99af19b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 22:20:26 +0200 Subject: [PATCH 193/796] USe boolean instead of integer for boolean variable --- contrib/pg_tde/src/encryption/enc_aes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index cb6f265f7da8f..6aa610e2a7388 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -48,7 +48,7 @@ static const EVP_CIPHER *cipher_ctr_ecb; void AesInit(void) { - static int initialized = 0; + static bool initialized = false; if (!initialized) { @@ -59,7 +59,7 @@ AesInit(void) cipher_gcm = EVP_aes_128_gcm(); cipher_ctr_ecb = EVP_aes_128_ecb(); - initialized = 1; + initialized = true; } } From 908999757a6af6691bfb42380714271748526067 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 22:20:57 +0200 Subject: [PATCH 194/796] Only call AesInit() once An alternative would be to always call it before we use AES but having the SMGR call initialize when the WAL SMGR and the tdemap code do not is confusing for the user. --- contrib/pg_tde/src/encryption/enc_aes.c | 17 ++++++++--------- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 6 ------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index 6aa610e2a7388..5f87220c50abf 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -50,17 +50,16 @@ AesInit(void) { static bool initialized = false; - if (!initialized) - { - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); + Assert(!initialized); - cipher_cbc = EVP_aes_128_cbc(); - cipher_gcm = EVP_aes_128_gcm(); - cipher_ctr_ecb = EVP_aes_128_ecb(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); - initialized = true; - } + cipher_cbc = EVP_aes_128_cbc(); + cipher_gcm = EVP_aes_128_gcm(); + cipher_ctr_ecb = EVP_aes_128_ecb(); + + initialized = true; } static void diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index d863a497c803e..de28eb1cb7f48 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -83,8 +83,6 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); void **local_buffers = palloc_array(void *, nblocks); - AesInit(); - for (int i = 0; i < nblocks; ++i) { BlockNumber bn = blocknum + i; @@ -144,8 +142,6 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); unsigned char iv[16]; - AesInit(); - CalcBlockIv(forknum, blocknum, int_key->base_iv, iv); AesEncrypt(int_key->key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); @@ -168,8 +164,6 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (!tdereln->encrypted_relation) return; - AesInit(); - for (int i = 0; i < nblocks; ++i) { bool allZero = true; From c7c1c64007fe902cfece50992a17362d8b3e76b4 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 5 May 2025 13:08:06 +0200 Subject: [PATCH 195/796] Assert that we intiialized ciphers before we use them --- contrib/pg_tde/src/encryption/enc_aes.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index 5f87220c50abf..f555c65ec2230 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -41,9 +41,9 @@ * 16 byte blocks. */ -static const EVP_CIPHER *cipher_cbc; -static const EVP_CIPHER *cipher_gcm; -static const EVP_CIPHER *cipher_ctr_ecb; +static const EVP_CIPHER *cipher_cbc = NULL; +static const EVP_CIPHER *cipher_gcm = NULL; +static const EVP_CIPHER *cipher_ctr_ecb = NULL; void AesInit(void) @@ -69,6 +69,8 @@ AesRunCtr(EVP_CIPHER_CTX **ctxPtr, int enc, const unsigned char *key, const unsi if (*ctxPtr == NULL) { + Assert(cipher_ctr_ecb != NULL); + *ctxPtr = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(*ctxPtr); @@ -93,6 +95,7 @@ AesRunCbc(int enc, const unsigned char *key, const unsigned char *iv, const unsi int out_len_final; EVP_CIPHER_CTX *ctx = NULL; + Assert(cipher_cbc != NULL); Assert(in_len % EVP_CIPHER_block_size(cipher_cbc) == 0); ctx = EVP_CIPHER_CTX_new(); @@ -142,6 +145,7 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned int out_len_final; EVP_CIPHER_CTX *ctx; + Assert(cipher_gcm != NULL); Assert(in_len % EVP_CIPHER_block_size(cipher_gcm) == 0); ctx = EVP_CIPHER_CTX_new(); From b8338686eff63e393373e0686b179b9ffcc22c86 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 5 May 2025 13:10:28 +0200 Subject: [PATCH 196/796] Move AesInit() call to _PG_init() Also clean up the order of the function slightly. --- contrib/pg_tde/src/pg_tde.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index ffc90e7d49e1c..92cf0d9892d73 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -73,8 +73,6 @@ tde_shmem_startup(void) prev_shmem_startup_hook(); TdeShmemInit(); - AesInit(); - TDEXLogShmemInit(); TDEXLogSmgrInit(); } @@ -96,23 +94,21 @@ _PG_init(void) check_percona_api_version(); + AesInit(); TdeGucInit(); TdeEventCaptureInit(); - InitializePrincipalKeyInfo(); InitializeKeyProviderInfo(); - - prev_shmem_request_hook = shmem_request_hook; - shmem_request_hook = tde_shmem_request; - prev_shmem_startup_hook = shmem_startup_hook; - shmem_startup_hook = tde_shmem_startup; - InstallFileKeyring(); InstallVaultV2Keyring(); InstallKmipKeyring(); RegisterTdeRmgr(); - RegisterStorageMgr(); + + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = tde_shmem_request; + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = tde_shmem_startup; } static void From 5586521e2175052843f45698bb3e5ac00c045a95 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 5 May 2025 10:20:17 +0200 Subject: [PATCH 197/796] PG-1543 Always cache server principal key This fixes a bug when pg_tde_verify_server_key() was broken if the server key was rotated in the same server lifetime as it was originally created since we wrote to the cache when we created to key the first time but not when we rotated it. An alternative would be to fix that bug and never cache the server key but by always caching it like we do with all other keys, including the default key, we simplify the code paths and make this kind of bug less likely in the future. This also discovered a previously harmless mistake where we grabbed the wrong lock level when trying to write a new WAL key. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 +-- .../pg_tde/src/catalog/tde_principal_key.c | 35 ++++++------------- .../src/include/catalog/tde_global_space.h | 2 -- contrib/pg_tde/t/009_wal_encrypt.pl | 4 +++ contrib/pg_tde/t/expected/009_wal_encrypt.out | 8 +++++ 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 8971ca13cb1ed..35bc7229d29cb 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -261,9 +261,9 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat { TDEPrincipalKey *principal_key; - LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_SHARED); + principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); if (principal_key == NULL) { ereport(ERROR, diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 687e1a2cca74c..b38ac426fabc3 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -328,11 +328,8 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, /* key rotation */ pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, true); - if (!TDEisInGlobalSpace(curr_principal_key->keyInfo.databaseId)) - { - clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); - push_principal_key_to_cache(new_principal_key); - } + clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); + push_principal_key_to_cache(new_principal_key); } LWLockRelease(lock_files); @@ -390,11 +387,8 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, false); - if (!TDEisInGlobalSpace(curr_principal_key->keyInfo.databaseId)) - { - clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); - push_principal_key_to_cache(new_principal_key); - } + clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); + push_principal_key_to_cache(new_principal_key); LWLockRelease(tde_lwlock_enc_keys()); @@ -800,14 +794,11 @@ GetPrincipalKeyNoDefault(Oid dbOid, LWLockMode lockMode) #ifndef FRONTEND Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), lockMode)); - /* We don't store global space key in cache */ - if (!TDEisInGlobalSpace(dbOid)) - { - principalKey = get_principal_key_from_cache(dbOid); - if (likely(principalKey)) - return principalKey; - } + principalKey = get_principal_key_from_cache(dbOid); + + if (likely(principalKey)) + return principalKey; if (lockMode != LW_EXCLUSIVE) { @@ -819,8 +810,7 @@ GetPrincipalKeyNoDefault(Oid dbOid, LWLockMode lockMode) principalKey = get_principal_key_from_keyring(dbOid); #ifndef FRONTEND - /* We don't store global space key in cache */ - if (principalKey && !TDEisInGlobalSpace(dbOid)) + if (principalKey) { push_principal_key_to_cache(principalKey); @@ -986,11 +976,8 @@ pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey /* key rotation */ pg_tde_perform_rotate_key(oldKey, newKey, true); - if (!TDEisInGlobalSpace(newKey->keyInfo.databaseId)) - { - clear_principal_key_cache(oldKey->keyInfo.databaseId); - push_principal_key_to_cache(newKey); - } + clear_principal_key_cache(oldKey->keyInfo.databaseId); + push_principal_key_to_cache(newKey); pfree(newKey); } diff --git a/contrib/pg_tde/src/include/catalog/tde_global_space.h b/contrib/pg_tde/src/include/catalog/tde_global_space.h index a1f297ffcd503..0510ab9a65cb7 100644 --- a/contrib/pg_tde/src/include/catalog/tde_global_space.h +++ b/contrib/pg_tde/src/include/catalog/tde_global_space.h @@ -33,6 +33,4 @@ _obj_oid \ } -#define TDEisInGlobalSpace(dbOid) (dbOid == GLOBAL_DATA_TDE_OID) - #endif /* TDE_GLOBAL_CATALOG_H */ diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/009_wal_encrypt.pl index f116853fd87a8..c554fb0f3bf58 100644 --- a/contrib/pg_tde/t/009_wal_encrypt.pl +++ b/contrib/pg_tde/t/009_wal_encrypt.pl @@ -23,10 +23,14 @@ "SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per');" ); +PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); + PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');" ); +PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); + PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_result_file("-- server restart with wal encryption"); diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index 3838f611476f3..cdd8f87771e6e 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -5,12 +5,20 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test -1 (1 row) +SELECT pg_tde_verify_server_key(); +psql::1: ERROR: principal key not configured for current database SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- (1 row) +SELECT pg_tde_verify_server_key(); + pg_tde_verify_server_key +-------------------------- + +(1 row) + ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption SHOW pg_tde.wal_encrypt; From e36331e7ff57eaa310baa1f6ee8cc9a914b6bc99 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 16:39:50 +0200 Subject: [PATCH 198/796] Improve function names and signatures for encryption The naming was quite inconsistent and, in the case of ECB vs CTR, misleading. So let's make it more consistent plus remove the legacy macros for the WAL encryption which were only used to slightly improve debug logging. Also do not supply any IV to ECB since it does not use any IV. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 4 ++-- contrib/pg_tde/src/encryption/enc_aes.c | 12 ++++++------ contrib/pg_tde/src/encryption/enc_tde.c | 9 +++++---- contrib/pg_tde/src/include/encryption/enc_aes.h | 3 +-- contrib/pg_tde/src/include/encryption/enc_tde.h | 10 +--------- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 1bc0348c2eca7..187788a25409a 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -176,7 +176,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #endif CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); - PG_TDE_ENCRYPT_DATA(iv_prefix, offset, + pg_tde_stream_crypt(iv_prefix, offset, (char *) buf, count, enc_buff, key, &EncryptionCryptCtx); @@ -348,7 +348,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %X/%X", dec_off, dec_off - offset, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); #endif - PG_TDE_DECRYPT_DATA(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, + pg_tde_stream_crypt(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, curr_key->key, &curr_key->crypt_ctx); if (dec_off + dec_sz == offset) diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index f555c65ec2230..84a95a1a323ec 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -63,7 +63,7 @@ AesInit(void) } static void -AesRunCtr(EVP_CIPHER_CTX **ctxPtr, int enc, const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out) +AesEcbEncrypt(EVP_CIPHER_CTX **ctxPtr, const unsigned char *key, const unsigned char *in, int in_len, unsigned char *out) { int out_len; @@ -74,7 +74,7 @@ AesRunCtr(EVP_CIPHER_CTX **ctxPtr, int enc, const unsigned char *key, const unsi *ctxPtr = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(*ctxPtr); - if (EVP_CipherInit_ex(*ctxPtr, cipher_ctr_ecb, NULL, key, iv, enc) == 0) + if (EVP_CipherInit_ex(*ctxPtr, cipher_ctr_ecb, NULL, key, NULL, 1) == 0) ereport(ERROR, errmsg("EVP_CipherInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -254,12 +254,12 @@ AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned return true; } -/* This function assumes that the out buffer is big enough: at least (blockNumber2 - blockNumber1) * 16 bytes +/* + * This function assumes that the out buffer is big enough: at least (blockNumber2 - blockNumber1) * 16 bytes */ void -Aes128EncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out) +AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out) { - const unsigned char iv[16] = {0,}; unsigned char *p; Assert(blockNumber2 >= blockNumber1); @@ -280,5 +280,5 @@ Aes128EncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv p += sizeof(j); } - AesRunCtr(ctxPtr, 1, key, iv, out, p - out, out); + AesEcbEncrypt(ctxPtr, key, out, p - out, out); } diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index 3cdc7398e994a..59e08dc1e9cc1 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -29,7 +29,7 @@ iv_prefix_debug(const char *iv_prefix, char *out_hex) * start_offset: is the absolute location of start of data in the file. */ void -pg_tde_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context) +pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr) { const uint64 aes_start_block = start_offset / AES_BLOCK_SIZE; const uint64 aes_end_block = (start_offset + data_len + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE; @@ -44,15 +44,16 @@ pg_tde_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint3 uint32 current_batch_bytes; uint64 batch_end_block = Min(batch_start_block + NUM_AES_BLOCKS_IN_BATCH, aes_end_block); - Aes128EncryptedZeroBlocks(ctxPtr, key->key, iv_prefix, batch_start_block, batch_end_block, enc_key); + AesCtrEncryptedZeroBlocks(ctxPtr, key->key, iv_prefix, batch_start_block, batch_end_block, enc_key); + #ifdef ENCRYPTION_DEBUG { char ivp_debug[33]; iv_prefix_debug(iv_prefix, ivp_debug); ereport(LOG, - errmsg("%s: Batch-No:%d Start offset: %lu Data_Len: %u, batch_start_block: %lu, batch_end_block: %lu, IV prefix: %s", - context ? context : "", batch_no, start_offset, data_len, batch_start_block, batch_end_block, ivp_debug)); + errmsg("pg_tde_stream_crypt batch_no: %d start_offset: %lu data_len: %u, batch_start_block: %lu, batch_end_block: %lu, iv_prefix: %s", + batch_no, start_offset, data_len, batch_start_block, batch_end_block, ivp_debug)); } #endif diff --git a/contrib/pg_tde/src/include/encryption/enc_aes.h b/contrib/pg_tde/src/include/encryption/enc_aes.h index 1cbece5768d55..c545ae7aeebda 100644 --- a/contrib/pg_tde/src/include/encryption/enc_aes.h +++ b/contrib/pg_tde/src/include/encryption/enc_aes.h @@ -13,11 +13,10 @@ #include extern void AesInit(void); -extern void Aes128EncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out); - extern void AesEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); extern void AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); extern void AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag); extern bool AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag); +extern void AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out); #endif /* ENC_AES_H */ diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index 092cb5df4f5b0..ac417c5254ff9 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -12,14 +12,6 @@ #include "access/pg_tde_tdemap.h" -extern void pg_tde_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr, const char *context); - -/* Function Macros over crypt */ - -#define PG_TDE_ENCRYPT_DATA(_iv_prefix, _start_offset, _data, _data_len, _out, _key, _ctxptr) \ - pg_tde_crypt(_iv_prefix, _start_offset, _data, _data_len, _out, _key, _ctxptr, "ENCRYPT") - -#define PG_TDE_DECRYPT_DATA(_iv_prefix, _start_offset, _data, _data_len, _out, _key, _ctxptr) \ - pg_tde_crypt(_iv_prefix, _start_offset, _data, _data_len, _out, _key, _ctxptr, "DECRYPT") +extern void pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr); #endif /* ENC_TDE_H */ From 43653e456bab1142cfadd63568deeec311b22a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 10:00:59 +0200 Subject: [PATCH 199/796] Remove type field from key provider configuration This field in the JSON was not used by anything, it wasn't even verified to match the actual key provider type so the risk of weird data was unnecessary. --- contrib/pg_tde/expected/access_control.out | 6 +- contrib/pg_tde/expected/key_provider.out | 38 +++++------ contrib/pg_tde/pg_tde--1.0-rc.sql | 66 +++++++------------ .../src/catalog/tde_keyring_parse_opts.c | 26 +------- contrib/pg_tde/t/expected/002_rotate_key.out | 8 +-- .../t/expected/010_change_key_provider.out | 18 ++--- 6 files changed, 63 insertions(+), 99 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 53083b232ce06..75d58486431e4 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -48,9 +48,9 @@ SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-pro (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------------+---------------+------------------------------------------------------------ - 1 | local-file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} + id | provider_name | provider_type | options +----+---------------------+---------------+------------------------------------------- + 1 | local-file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} (1 row) SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index f641eb3809bc6..d7c05c8e3534e 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -16,9 +16,9 @@ SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_k (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} + id | provider_name | provider_type | options +----+---------------+---------------+------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} (1 row) SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); @@ -28,10 +28,10 @@ SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_ (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------- - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} + id | provider_name | provider_type | options +----+----------------+---------------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_verify_key(); @@ -52,10 +52,10 @@ SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg ERROR: key provider "not-existent-provider" does not exists HINT: Create the key provider SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------- - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} + id | provider_name | provider_type | options +----+----------------+---------------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); @@ -65,10 +65,10 @@ SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_tes (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} + id | provider_name | provider_type | options +----+----------------+---------------+------------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_verify_key(); @@ -76,10 +76,10 @@ ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: parse json keyring config: unexpected field foo SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------------------------ - 1 | file-provider | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring_other.per"} - 2 | file-provider2 | file | {"type" : "file", "path" : "/tmp/pg_tde_test_keyring2.per"} + id | provider_name | provider_type | options +----+----------------+---------------+------------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index a8cf95b7c0564..19668c072cedf 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -16,7 +16,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE COALESCE(file_path, ''))); END; CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path JSON) @@ -26,7 +26,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE file_path)); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, @@ -40,8 +40,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE COALESCE(vault_url, ''), + json_object('url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); @@ -58,8 +57,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE vault_url, + json_object('url' VALUE vault_url, 'token' VALUE vault_token, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); @@ -76,8 +74,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, 'caPath' VALUE COALESCE(kmip_ca_path, ''), 'certPath' VALUE COALESCE(kmip_cert_path, ''))); @@ -94,8 +91,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE kmip_host, + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path)); @@ -133,7 +129,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE COALESCE(file_path, ''))); END; CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path JSON) @@ -143,7 +139,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE file_path)); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, @@ -157,8 +153,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE COALESCE(vault_url, ''), + json_object('url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); @@ -175,8 +170,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE vault_url, + json_object('url' VALUE vault_url, 'token' VALUE vault_token, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); @@ -193,8 +187,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, 'caPath' VALUE COALESCE(kmip_ca_path, ''), 'certPath' VALUE COALESCE(kmip_cert_path, ''))); @@ -211,8 +204,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'host' VALUE kmip_host, + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path)); @@ -231,7 +223,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE COALESCE(file_path, ''))); END; CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path JSON) @@ -241,7 +233,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE file_path)); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, @@ -255,8 +247,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE COALESCE(vault_url, ''), + json_object('url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); @@ -273,8 +264,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE vault_url, + json_object('url' VALUE vault_url, 'token' VALUE vault_token, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); @@ -291,8 +281,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, 'caPath' VALUE COALESCE(kmip_ca_path, ''), 'certPath' VALUE COALESCE(kmip_cert_path, ''))); @@ -309,8 +298,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE kmip_host, + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path)); @@ -329,7 +317,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE COALESCE(file_path, ''))); END; CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path JSON) @@ -339,7 +327,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('file', provider_name, - json_object('type' VALUE 'file', 'path' VALUE file_path)); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, @@ -353,8 +341,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE COALESCE(vault_url, ''), + json_object('url' VALUE COALESCE(vault_url, ''), 'token' VALUE COALESCE(vault_token, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); @@ -371,8 +358,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'url' VALUE vault_url, + json_object('url' VALUE vault_url, 'token' VALUE vault_token, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); @@ -389,8 +375,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('kmip', provider_name, - json_object('type' VALUE 'kmip', - 'host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE COALESCE(kmip_host, ''), 'port' VALUE kmip_port, 'caPath' VALUE COALESCE(kmip_ca_path, ''), 'certPath' VALUE COALESCE(kmip_cert_path, ''))); @@ -407,8 +392,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, - json_object('type' VALUE 'vault-v2', - 'host' VALUE kmip_host, + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path)); @@ -473,7 +457,7 @@ AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_default_key_info() RETURNS TABLE ( key_name text, - key_provider_name text, + key_provider_name text, key_provider_id integer, key_creation_time timestamp with time zone) LANGUAGE C diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index cc70ba3fc34a6..5d50ae0e4e953 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -4,12 +4,11 @@ * Parser routines for the keyring JSON options * * Each value in the JSON document can be either scalar (string) - a value itself - * or a reference to the external object that contains the value. Though the top - * level field "type" can be only scalar. + * or a reference to the external object that contains the value. * * Examples: - * {"type" : "file", "path" : "/tmp/keyring_data_file"} - * {"type" : "file", "path" : {"type" : "file", "path" : "/tmp/datafile-location"}} + * {"path" : "/tmp/keyring_data_file"} + * {"path" : {"type" : "file", "path" : "/tmp/datafile-location"}} * in the latter one, /tmp/datafile-location contains not keyring data but the * location of such. * @@ -56,8 +55,6 @@ typedef enum JsonKeyringField { JK_FIELD_UNKNOWN, - JK_KRING_TYPE, - JK_FIELD_TYPE, JK_REMOTE_URL, JK_FIELD_PATH, @@ -80,7 +77,6 @@ typedef enum JsonKeyringField static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { [JK_FIELD_UNKNOWN] = "unknownField", - [JK_KRING_TYPE] = "type", [JK_FIELD_TYPE] = "type", [JK_REMOTE_URL] = "url", [JK_FIELD_PATH] = "path", @@ -126,7 +122,6 @@ typedef struct JsonKeyringState * direct value for the caller. Although we need them for the values * extraction or state tracking. */ - char *kring_type; char *field_type; char *extern_url; char *extern_path; @@ -314,17 +309,6 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) switch (parse->state) { case JK_EXPECT_TOP_FIELD: - - /* - * On the top level, "type" stores a keyring type and this field - * is common for all keyrings. The rest of the fields depend on - * the keyring type. - */ - if (strcmp(fname, JK_FIELD_NAMES[JK_KRING_TYPE]) == 0) - { - *field = JK_KRING_TYPE; - break; - } switch (parse->provider_type) { case FILE_KEY_PROVIDER: @@ -415,10 +399,6 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * switch (field) { - case JK_KRING_TYPE: - parse->kring_type = value; - break; - case JK_FIELD_TYPE: parse->field_type = value; break; diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 15345de30ad13..a0e4cae0ef20c 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -24,10 +24,10 @@ SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3 (1 row) SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------------------------- - (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}") - (2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") + pg_tde_list_all_database_key_providers +--------------------------------------------------------------------- + (1,file-vault,file,"{""path"" : ""/tmp/pg_tde_test_keyring.per""}") + (2,file-2,file,"{""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") (2 rows) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index ca988c6259919..b162e7703de3a 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -6,9 +6,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov (1 row) SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers --------------------------------------------------------------------------------------------- - (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_1.per""}") + pg_tde_list_all_database_key_providers +----------------------------------------------------------------------- + (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_1.per""}") (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); @@ -46,9 +46,9 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers --------------------------------------------------------------------------------------------- - (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_2.per""}") + pg_tde_list_all_database_key_providers +----------------------------------------------------------------------- + (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_2.per""}") (1 row) SELECT pg_tde_verify_key(); @@ -97,9 +97,9 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers --------------------------------------------------------------------------------------------- - (1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/change_key_provider_3.per""}") + pg_tde_list_all_database_key_providers +----------------------------------------------------------------------- + (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_3.per""}") (1 row) SELECT pg_tde_verify_key(); From aaa80c4f61d7dccf7e550dd47cb1c09e5cc8eb8b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 5 May 2025 18:12:46 +0200 Subject: [PATCH 200/796] Remove unhelpful hint on key provider not found The hint added nothing and while we could hint about what function they should use to create a key provider this error happens also when alternating or deleting an existing key provider making any hint pretty much useless. --- contrib/pg_tde/expected/key_provider.out | 1 - contrib/pg_tde/src/catalog/tde_keyring.c | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index d7c05c8e3534e..8b8c45c15c071 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -50,7 +50,6 @@ SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); ERROR: key provider "not-existent-provider" does not exists -HINT: Create the key provider SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+-------------------------------------------- diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index ac32f9f251d5a..8be9c7548adbb 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -1030,8 +1030,7 @@ GetKeyProviderByName(const char *provider_name, Oid dbOid) { ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("key provider \"%s\" does not exists", provider_name), - errhint("Create the key provider")); + errmsg("key provider \"%s\" does not exists", provider_name)); } return keyring; } From da52b54cd5c31fbb97bc1e9d1a111ac33af4bf59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 5 May 2025 19:30:33 +0200 Subject: [PATCH 201/796] Make ParseKeyringJSONOptions return void This always returned true or did a longjump using ereport(ERROR, ...). --- contrib/pg_tde/src/catalog/tde_keyring.c | 18 +++--------------- .../src/catalog/tde_keyring_parse_opts.c | 7 ++----- .../pg_tde/src/include/catalog/tde_keyring.h | 2 +- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 8be9c7548adbb..b20cbc20f4a85 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -835,11 +835,7 @@ load_file_keyring_provider_options(char *keyring_options) file_keyring->keyring.type = FILE_KEY_PROVIDER; - if (!ParseKeyringJSONOptions(FILE_KEY_PROVIDER, file_keyring, - keyring_options, strlen(keyring_options))) - { - return NULL; - } + ParseKeyringJSONOptions(FILE_KEY_PROVIDER, file_keyring, keyring_options, strlen(keyring_options)); if (file_keyring->file_name == NULL || file_keyring->file_name[0] == '\0') { @@ -859,11 +855,7 @@ load_vaultV2_keyring_provider_options(char *keyring_options) vaultV2_keyring->keyring.type = VAULT_V2_KEY_PROVIDER; - if (!ParseKeyringJSONOptions(VAULT_V2_KEY_PROVIDER, vaultV2_keyring, - keyring_options, strlen(keyring_options))) - { - return NULL; - } + ParseKeyringJSONOptions(VAULT_V2_KEY_PROVIDER, vaultV2_keyring, keyring_options, strlen(keyring_options)); if (vaultV2_keyring->vault_token == NULL || vaultV2_keyring->vault_token[0] == '\0' || vaultV2_keyring->vault_url == NULL || vaultV2_keyring->vault_url[0] == '\0' || @@ -888,11 +880,7 @@ load_kmip_keyring_provider_options(char *keyring_options) kmip_keyring->keyring.type = KMIP_KEY_PROVIDER; - if (!ParseKeyringJSONOptions(KMIP_KEY_PROVIDER, kmip_keyring, - keyring_options, strlen(keyring_options))) - { - return NULL; - } + ParseKeyringJSONOptions(KMIP_KEY_PROVIDER, kmip_keyring, keyring_options, strlen(keyring_options)); if (strlen(kmip_keyring->kmip_host) == 0 || strlen(kmip_keyring->kmip_port) == 0 || diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 5d50ae0e4e953..46525cf788ba2 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -141,9 +141,8 @@ static char *get_file_kring_value(const char *path, const char *field_name); * Parses json input for the given provider type and sets the provided options * out_opts should be a palloc'd `VaultV2Keyring` or `FileKeyring` struct as the * respective option values will be mem copied into it. - * Returns `true` if parsing succeded and `false` otherwise. -*/ -bool + */ +void ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf, int buf_len) { JsonLexContext *jlex; @@ -191,8 +190,6 @@ ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf #if PG_VERSION_NUM >= 170000 freeJsonLexContext(jlex); #endif - - return jerr == JSON_SUCCESS; } /* diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index c40b5a54925c2..e4f00e9ae65a7 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -43,7 +43,7 @@ extern void delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog); extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); -extern bool ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, +extern void ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf, int buf_len); #endif /* TDE_KEYRING_H */ From 7631e12cdf8537f40c9b59b5d2e2f050c2df72d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 5 May 2025 19:38:27 +0200 Subject: [PATCH 202/796] Use GenericKeyring* instead of void* As the parameter to ParseKeyringJSONOptions and in the state held while parsing the options. --- contrib/pg_tde/src/catalog/tde_keyring.c | 10 +++++++--- .../src/catalog/tde_keyring_parse_opts.c | 20 ++++++++----------- .../pg_tde/src/include/catalog/tde_keyring.h | 3 ++- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b20cbc20f4a85..d8c0bb0c897f5 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -835,7 +835,8 @@ load_file_keyring_provider_options(char *keyring_options) file_keyring->keyring.type = FILE_KEY_PROVIDER; - ParseKeyringJSONOptions(FILE_KEY_PROVIDER, file_keyring, keyring_options, strlen(keyring_options)); + ParseKeyringJSONOptions(FILE_KEY_PROVIDER, (GenericKeyring *) file_keyring, + keyring_options, strlen(keyring_options)); if (file_keyring->file_name == NULL || file_keyring->file_name[0] == '\0') { @@ -855,7 +856,9 @@ load_vaultV2_keyring_provider_options(char *keyring_options) vaultV2_keyring->keyring.type = VAULT_V2_KEY_PROVIDER; - ParseKeyringJSONOptions(VAULT_V2_KEY_PROVIDER, vaultV2_keyring, keyring_options, strlen(keyring_options)); + ParseKeyringJSONOptions(VAULT_V2_KEY_PROVIDER, + (GenericKeyring *) vaultV2_keyring, + keyring_options, strlen(keyring_options)); if (vaultV2_keyring->vault_token == NULL || vaultV2_keyring->vault_token[0] == '\0' || vaultV2_keyring->vault_url == NULL || vaultV2_keyring->vault_url[0] == '\0' || @@ -880,7 +883,8 @@ load_kmip_keyring_provider_options(char *keyring_options) kmip_keyring->keyring.type = KMIP_KEY_PROVIDER; - ParseKeyringJSONOptions(KMIP_KEY_PROVIDER, kmip_keyring, keyring_options, strlen(keyring_options)); + ParseKeyringJSONOptions(KMIP_KEY_PROVIDER, (GenericKeyring *) kmip_keyring, + keyring_options, strlen(keyring_options)); if (strlen(kmip_keyring->kmip_host) == 0 || strlen(kmip_keyring->kmip_port) == 0 || diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 46525cf788ba2..c8b23b6a6d309 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -102,11 +102,8 @@ typedef struct JsonKeyringState { ProviderType provider_type; - /* - * Caller's options to be set from JSON values. Expected either - * `VaultV2Keyring` or `FileKeyring` - */ - void *provider_opts; + /* Caller's options to be set from JSON values. */ + GenericKeyring *provider_opts; /* * A field hierarchy of the current branch, field[level] is the current @@ -138,12 +135,11 @@ static char *get_file_kring_value(const char *path, const char *field_name); /* - * Parses json input for the given provider type and sets the provided options - * out_opts should be a palloc'd `VaultV2Keyring` or `FileKeyring` struct as the - * respective option values will be mem copied into it. + * Parses json input for the given provider type and sets the provided options. + * out_opts should be a palloc'd keyring object matching the provider_type. */ void -ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, char *in_buf, int buf_len) +ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, char *in_buf, int buf_len) { JsonLexContext *jlex; JsonKeyringState parse = {0}; @@ -390,9 +386,9 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) static JsonParseErrorType json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char *value) { - VaultV2Keyring *vault = parse->provider_opts; - FileKeyring *file = parse->provider_opts; - KmipKeyring *kmip = parse->provider_opts; + VaultV2Keyring *vault = (VaultV2Keyring *) parse->provider_opts; + FileKeyring *file = (FileKeyring *) parse->provider_opts; + KmipKeyring *kmip = (KmipKeyring *) parse->provider_opts; switch (field) { diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index e4f00e9ae65a7..42b708cb8f9cf 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -43,7 +43,8 @@ extern void delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog); extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); -extern void ParseKeyringJSONOptions(ProviderType provider_type, void *out_opts, +extern void ParseKeyringJSONOptions(ProviderType provider_type, + GenericKeyring *out_opts, char *in_buf, int buf_len); #endif /* TDE_KEYRING_H */ From 200b06f9072473b54f35383d6e9a2e1a7a0cbc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 5 May 2025 19:49:13 +0200 Subject: [PATCH 203/796] Rename some enum values in ParseKeyringJSONOptions Rename JK_REMOTE_URL to be clearer that it belongs with JK_FIELD_TYPE and JK_FIELD_PATH. Rename JF_FILE_PATH which I assume was named "JF" instead of "JK" as a typo. Also add a couple of helpful comments. --- .../src/catalog/tde_keyring_parse_opts.c | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index c8b23b6a6d309..c8a1f2721ded2 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -55,11 +55,13 @@ typedef enum JsonKeyringField { JK_FIELD_UNKNOWN, + /* These are for the objects that can point to a file or a remote url. */ JK_FIELD_TYPE, - JK_REMOTE_URL, + JK_FIELD_URL, JK_FIELD_PATH, - JF_FILE_PATH, + /* Settings specific for the individual key provider types. */ + JK_FILE_PATH, JK_VAULT_TOKEN, JK_VAULT_URL, @@ -77,15 +79,18 @@ typedef enum JsonKeyringField static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { [JK_FIELD_UNKNOWN] = "unknownField", + [JK_FIELD_TYPE] = "type", - [JK_REMOTE_URL] = "url", + [JK_FIELD_URL] = "url", [JK_FIELD_PATH] = "path", /* - * These values should match pg_tde_add_database_key_provider_vault_v2 and - * pg_tde_add_database_key_provider_file SQL interfaces + * These values should match pg_tde_add_database_key_provider_vault_v2, + * pg_tde_add_database_key_provider_file and + * pg_tde_add_database_key_provider_kmip SQL interfaces */ - [JF_FILE_PATH] = "path", + [JK_FILE_PATH] = "path", + [JK_VAULT_TOKEN] = "token", [JK_VAULT_URL] = "url", [JK_VAULT_MOUNT_PATH] = "mountPath", @@ -305,8 +310,8 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) switch (parse->provider_type) { case FILE_KEY_PROVIDER: - if (strcmp(fname, JK_FIELD_NAMES[JF_FILE_PATH]) == 0) - *field = JF_FILE_PATH; + if (strcmp(fname, JK_FIELD_NAMES[JK_FILE_PATH]) == 0) + *field = JK_FILE_PATH; else { *field = JK_FIELD_UNKNOWN; @@ -354,8 +359,8 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) case JK_EXPECT_EXTERN_VAL: if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_TYPE]) == 0) *field = JK_FIELD_TYPE; - else if (strcmp(fname, JK_FIELD_NAMES[JK_REMOTE_URL]) == 0) - *field = JK_REMOTE_URL; + else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_URL]) == 0) + *field = JK_FIELD_URL; else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_PATH]) == 0) *field = JK_FIELD_PATH; else @@ -395,14 +400,14 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * case JK_FIELD_TYPE: parse->field_type = value; break; - case JK_REMOTE_URL: + case JK_FIELD_URL: parse->extern_url = value; break; case JK_FIELD_PATH: parse->extern_path = value; break; - case JF_FILE_PATH: + case JK_FILE_PATH: file->file_name = value; break; From e5e04ee3d3b7a03effbc3754a850bddb2823d983 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 17:08:30 +0200 Subject: [PATCH 204/796] PG-1507 PG-1508 Move variables to the right scope --- contrib/pg_tde/src/pg_tde_event_capture.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 317a00fae6b5b..b05fb1ebfd752 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -260,13 +260,14 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) else if (IsA(parsetree, AlterTableStmt)) { AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree); - ListCell *lcmd; - AlterTableCmd *setAccessMethod = NULL; - TdeDdlEvent event = {.parsetree = parsetree}; Oid relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true); if (relid != InvalidOid) { + AlterTableCmd *setAccessMethod = NULL; + ListCell *lcmd; + TdeDdlEvent event = {.parsetree = parsetree}; + foreach(lcmd, stmt->cmds) { AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd); From effdc95db2267d02e2227c199f86a72617ffca94 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Apr 2025 14:24:33 +0200 Subject: [PATCH 205/796] PG-1507 PG-1508 Disallow ALTER TABLE when there is a mix of encrypted and unencrypted Since ALTER TABLE can modify multiple due to inheritance or typed tables which can for example result in TOAST tables being created for some or all of the modified tables while the event trigger is only fired once we cannot rely on the event stack to make sure we get the correct encryption status. Our solution is to be cautious and only modify tables when all tables with storage are either encrypt or not encrypted. If there is a mix we will throw an error. The result of this is also used to properly inform the SMGR of the current encryption status. We apply the same logic to ALTER TYPE for typed tables. A possibility for future improvement would be to limit this check only to commands which recurse down to child tables and/or can create new relfilenodes. --- contrib/pg_tde/expected/partition_table.out | 7 ++ contrib/pg_tde/expected/recreate_storage.out | 43 +++++++ contrib/pg_tde/sql/partition_table.sql | 7 ++ contrib/pg_tde/sql/recreate_storage.sql | 33 +++++ contrib/pg_tde/src/pg_tde_event_capture.c | 120 +++++++++++++++++-- 5 files changed, 201 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index 22d67a0b2100f..c5c5dd07e83a2 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -52,6 +52,7 @@ SELECT pg_tde_is_encrypted('partition_q4_2024'); (1 row) ALTER TABLE partitioned_table SET ACCESS METHOD heap; +ERROR: Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported ALTER TABLE partition_q1_2024 SET ACCESS METHOD heap; ALTER TABLE partition_q2_2024 SET ACCESS METHOD tde_heap; ALTER TABLE partition_q3_2024 SET ACCESS METHOD heap; @@ -86,6 +87,12 @@ SELECT pg_tde_is_encrypted('partition_q4_2024'); t (1 row) +-- Does not care about parent AM as long as all children with storage use the same +ALTER TABLE partition_q1_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q2_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q3_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q4_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partitioned_table SET ACCESS METHOD heap; DROP TABLE partitioned_table; -- Partition inherits encryption status from parent table if default is heap and parent is tde_heap SET default_table_access_method = "heap"; diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index 0b28c4a72cb76..aa468b7f57092 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -229,6 +229,49 @@ SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); f (1 row) +-- Enforce that we do not mess up encryption status for toast table +CREATE TABLE cities ( + name varchar(8), + population real, + elevation int +) USING tde_heap; +CREATE TABLE state_capitals ( + state char(2) UNIQUE NOT NULL +) INHERITS (cities) USING heap; +CREATE TABLE capitals ( + country char(2) UNIQUE NOT NULL +) INHERITS (cities) USING tde_heap; +ALTER TABLE cities ALTER COLUMN name TYPE TEXT; +ERROR: Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported +-- Enforce the same for typed tables +CREATE TYPE people_type AS (age int, name varchar(8), dob date); +CREATE TABLE sales_staff OF people_type USING tde_heap; +CREATE TABLE other_staff OF people_type USING heap; +ALTER TYPE people_type ALTER ATTRIBUTE name TYPE text CASCADE; +ERROR: Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported +-- If all tpyed tables are encrypted everything should work as usual +ALTER TABLE other_staff SET ACCESS METHOD tde_heap; +ALTER TYPE people_type ALTER ATTRIBUTE name TYPE text CASCADE; +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'sales_staff'::regclass::oid); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'other_staff'::regclass::oid); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TYPE people_type CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table sales_staff +drop cascades to table other_staff +DROP TABLE cities CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table state_capitals +drop cascades to table capitals DROP TABLE plain_table; DROP EXTENSION pg_tde CASCADE; NOTICE: drop cascades to 7 other objects diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index ddaf30033adab..b77b1f4c35436 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -31,6 +31,13 @@ SELECT pg_tde_is_encrypted('partition_q2_2024'); SELECT pg_tde_is_encrypted('partition_q3_2024'); SELECT pg_tde_is_encrypted('partition_q4_2024'); +-- Does not care about parent AM as long as all children with storage use the same +ALTER TABLE partition_q1_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q2_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q3_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partition_q4_2024 SET ACCESS METHOD tde_heap; +ALTER TABLE partitioned_table SET ACCESS METHOD heap; + DROP TABLE partitioned_table; -- Partition inherits encryption status from parent table if default is heap and parent is tde_heap diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 8b3e5565966bb..7a19ad35444ea 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -96,6 +96,39 @@ SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); ALTER SEQUENCE plain_table_id2_seq2 OWNED BY NONE; SELECT pg_tde_is_encrypted('plain_table_id2_seq2'); +-- Enforce that we do not mess up encryption status for toast table +CREATE TABLE cities ( + name varchar(8), + population real, + elevation int +) USING tde_heap; + +CREATE TABLE state_capitals ( + state char(2) UNIQUE NOT NULL +) INHERITS (cities) USING heap; + +CREATE TABLE capitals ( + country char(2) UNIQUE NOT NULL +) INHERITS (cities) USING tde_heap; + +ALTER TABLE cities ALTER COLUMN name TYPE TEXT; + +-- Enforce the same for typed tables +CREATE TYPE people_type AS (age int, name varchar(8), dob date); +CREATE TABLE sales_staff OF people_type USING tde_heap; +CREATE TABLE other_staff OF people_type USING heap; + +ALTER TYPE people_type ALTER ATTRIBUTE name TYPE text CASCADE; + +-- If all tpyed tables are encrypted everything should work as usual +ALTER TABLE other_staff SET ACCESS METHOD tde_heap; +ALTER TYPE people_type ALTER ATTRIBUTE name TYPE text CASCADE; + +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'sales_staff'::regclass::oid); +SELECT pg_tde_is_encrypted('pg_toast.pg_toast_' || 'other_staff'::regclass::oid); + +DROP TYPE people_type CASCADE; +DROP TABLE cities CASCADE; DROP TABLE plain_table; DROP EXTENSION pg_tde CASCADE; RESET default_table_access_method; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index b05fb1ebfd752..74cb36bb88a1c 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -17,8 +17,10 @@ #include "utils/lsyscache.h" #include "catalog/pg_class.h" #include "catalog/pg_database.h" +#include "catalog/pg_inherits.h" #include "commands/defrem.h" #include "commands/sequence.h" +#include "access/heapam.h" #include "access/table.h" #include "access/relation.h" #include "catalog/pg_event_trigger.h" @@ -141,6 +143,97 @@ push_event_stack(const TdeDdlEvent *event) MemoryContextSwitchTo(oldCtx); } +static List * +find_typed_table_dependencies(Oid typeOid) +{ + Relation classRel; + ScanKeyData key[1]; + TableScanDesc scan; + HeapTuple tuple; + List *result = NIL; + + classRel = table_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_class_reloftype, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeOid)); + + scan = table_beginscan_catalog(classRel, 1, key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple); + + LockRelationOid(classform->oid, AccessShareLock); + result = lappend_oid(result, classform->oid); + } + + table_endscan(scan); + table_close(classRel, AccessShareLock); + + return result; +} + +typedef enum +{ + ENC_MIX_UNKNOWN, + ENC_MIX_PLAIN, + ENC_MIX_ENCRYPTED, + ENC_MIX_MIXED, +} EncryptionMix; + +/* + * Since ALTER TABLE can modify multiple tables due to inheritance or typed + * tables which can for example result in TOAST tables being created for some + * or all of the modified tables while the event trigger is only fired once we + * cannot rely on the event stack to make sure we get the correct encryption + * status. + * + * Our solution is to be cautious and only modify tables when all tables with + * storage are either encrypted or not encrypted. If there is a mix we will + * throw and error. The result of this is also used to properly inform the + * SMGR of the current encryption status. + */ +static EncryptionMix +alter_table_encryption_mix(Oid relid) +{ + EncryptionMix enc = ENC_MIX_UNKNOWN; + Relation rel; + List *children; + ListCell *lc; + + rel = relation_open(relid, NoLock); + + if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + enc = rel->rd_rel->relam == get_tde_table_am_oid() ? ENC_MIX_ENCRYPTED : ENC_MIX_PLAIN; + + if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + children = find_typed_table_dependencies(rel->rd_rel->reltype); + else + children = find_inheritance_children(relid, AccessShareLock); + + relation_close(rel, NoLock); + + foreach(lc, children) + { + Oid childid = lfirst_oid(lc); + EncryptionMix childenc; + + childenc = alter_table_encryption_mix(childid); + + if (childenc != ENC_MIX_UNKNOWN) + { + if (enc == ENC_MIX_UNKNOWN) + enc = childenc; + else if (enc != childenc) + return ENC_MIX_MIXED; + } + } + + return enc; +} + /* * pg_tde_ddl_command_start_capture is an event trigger function triggered * at the start of any DDL command execution. @@ -267,6 +360,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) AlterTableCmd *setAccessMethod = NULL; ListCell *lcmd; TdeDdlEvent event = {.parsetree = parsetree}; + EncryptionMix encmix; foreach(lcmd, stmt->cmds) { @@ -276,6 +370,20 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) setAccessMethod = cmd; } + encmix = alter_table_encryption_mix(relid); + + /* + * This check is very braod and could be limited only to commands + * which recurse to child tables or to those which may create new + * relfilenodes, but this restrictive code is good enough for now. + */ + if (encmix == ENC_MIX_MIXED) + { + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported")); + } + /* * With a SET ACCESS METHOD clause, use that as the basis for * decisions. But if it's not present, look up encryption status @@ -292,19 +400,13 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) } else { - Relation rel = relation_open(relid, AccessShareLock); - - if (rel->rd_rel->relam == get_tde_table_am_oid()) + if (encmix == ENC_MIX_ENCRYPTED) { - /* - * We are altering an encrypted table and ALTER TABLE can - * possibly create new files so set the global state. - */ event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; checkPrincipalKeyConfigured(); } - - relation_close(rel, NoLock); + else if (encmix == ENC_MIX_PLAIN) + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } push_event_stack(&event); From 75e55d094ba45fa505e906bac56739ccabe22112 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 3 Mar 2025 19:53:05 +0100 Subject: [PATCH 206/796] Add static annotations to shmem code --- contrib/pg_tde/src/catalog/tde_principal_key.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index b38ac426fabc3..33921d2b43a44 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -81,7 +81,7 @@ static dshash_parameters principal_key_dsh_params = { .hash_function = dshash_memhash, }; -TdePrincipalKeylocalState principalKeyLocalState; +static TdePrincipalKeylocalState principalKeyLocalState; static void principal_key_info_attach_shmem(void); static Size initialize_shared_state(void *start_address); @@ -176,7 +176,7 @@ initialize_shared_state(void *start_address) return sizeof(TdePrincipalKeySharedState); } -void +static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area) { dshash_table *dsh; From 1d1959d5be212f04d2075546641ca13e3d5cb458 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 3 Mar 2025 19:53:33 +0100 Subject: [PATCH 207/796] Remove shmem dead code This code has not been used in a long time. --- contrib/pg_tde/src/common/pg_tde_shmem.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c index 37ad019a5f459..f3bd1e8824a72 100644 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ b/contrib/pg_tde/src/common/pg_tde_shmem.c @@ -19,20 +19,9 @@ typedef struct TdeSharedState { - LWLock *principalKeyLock; - int principalKeyHashTrancheId; void *rawDsaArea; /* DSA area pointer to store cache hashes */ - dshash_table_handle principalKeyHashHandle; } TdeSharedState; -typedef struct TDELocalState -{ - TdeSharedState *sharedTdeState; - dsa_area **dsa; /* local dsa area for backend attached to the - * dsa area created by postmaster at startup. */ - dshash_table *principalKeySharedHash; -} TDELocalState; - static void tde_shmem_shutdown(int code, Datum arg); List *registeredShmemRequests = NIL; From d67ad2ab9041c625e718c12ff4461af04505188d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 3 Mar 2025 20:04:03 +0100 Subject: [PATCH 208/796] Remove pointless struct from shmem code This struct just added extra mental overhead. --- contrib/pg_tde/src/common/pg_tde_shmem.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c index f3bd1e8824a72..6bd4c9578d617 100644 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ b/contrib/pg_tde/src/common/pg_tde_shmem.c @@ -17,11 +17,6 @@ #include "storage/lwlock.h" #include "storage/shmem.h" -typedef struct TdeSharedState -{ - void *rawDsaArea; /* DSA area pointer to store cache hashes */ -} TdeSharedState; - static void tde_shmem_shutdown(int code, Datum arg); List *registeredShmemRequests = NIL; @@ -47,7 +42,6 @@ TdeRequiredSharedMemorySize(void) if (routine->required_shared_mem_size) sz = add_size(sz, routine->required_shared_mem_size()); } - sz = add_size(sz, sizeof(TdeSharedState)); return MAXALIGN(sz); } @@ -61,25 +55,22 @@ void TdeShmemInit(void) { bool found; - TdeSharedState *tdeState; + char *free_start; Size required_shmem_size = TdeRequiredSharedMemorySize(); LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); /* Create or attach to the shared memory state */ ereport(NOTICE, errmsg("TdeShmemInit: requested %ld bytes", required_shmem_size)); - tdeState = ShmemInitStruct("pg_tde", required_shmem_size, &found); + free_start = ShmemInitStruct("pg_tde", required_shmem_size, &found); if (!found) { /* First time through ... */ - char *p = (char *) tdeState; dsa_area *dsa; ListCell *lc; Size used_size = 0; Size dsa_area_size; - p += MAXALIGN(sizeof(TdeSharedState)); - used_size += MAXALIGN(sizeof(TdeSharedState)); /* Now place all shared state structures */ foreach(lc, registeredShmemRequests) { @@ -88,19 +79,18 @@ TdeShmemInit(void) if (routine->init_shared_state) { - sz = routine->init_shared_state(p); + sz = routine->init_shared_state(free_start); used_size += MAXALIGN(sz); - p += MAXALIGN(sz); + free_start += MAXALIGN(sz); Assert(used_size <= required_shmem_size); } } /* Create DSA area */ dsa_area_size = required_shmem_size - used_size; Assert(dsa_area_size > 0); - tdeState->rawDsaArea = p; ereport(LOG, errmsg("creating DSA area of size %lu", dsa_area_size)); - dsa = dsa_create_in_place(tdeState->rawDsaArea, + dsa = dsa_create_in_place(free_start, dsa_area_size, LWLockNewTrancheId(), 0); dsa_pin(dsa); @@ -112,7 +102,7 @@ TdeShmemInit(void) TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc); if (routine->init_dsa_area_objects) - routine->init_dsa_area_objects(dsa, tdeState->rawDsaArea); + routine->init_dsa_area_objects(dsa, free_start); } ereport(LOG, errmsg("setting no limit to DSA area of size %lu", dsa_area_size)); From 79c8372d2df01da71c0407106135fd64c03e879f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 2 Apr 2025 00:55:07 +0200 Subject: [PATCH 209/796] Move fixed value to struct initializer --- contrib/pg_tde/src/catalog/tde_principal_key.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 33921d2b43a44..ff36c629cd6a2 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -79,6 +79,9 @@ static dshash_parameters principal_key_dsh_params = { .entry_size = sizeof(TDEPrincipalKey), .compare_function = dshash_memcmp, .hash_function = dshash_memhash, +#if PG_VERSION_NUM >= 170000 + .copy_function = dshash_memcpy, +#endif }; static TdePrincipalKeylocalState principalKeyLocalState; @@ -189,9 +192,6 @@ initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area) sharedState->rawDsaArea = raw_dsa_area; sharedState->hashTrancheId = LWLockNewTrancheId(); principal_key_dsh_params.tranche_id = sharedState->hashTrancheId; -#if PG_VERSION_NUM >= 170000 - principal_key_dsh_params.copy_function = dshash_memcpy; -#endif dsh = dshash_create(dsa, &principal_key_dsh_params, 0); sharedState->hashHandle = dshash_get_hash_table_handle(dsh); dshash_detach(dsh); From 6478b46a2f09855011a9aecd95e5e85756b31ce1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 2 Apr 2025 11:23:19 +0200 Subject: [PATCH 210/796] Turn fields in global variables into local variables These two fields are only used inside their respective functions. --- .../pg_tde/src/catalog/tde_principal_key.c | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index ff36c629cd6a2..232c2b2da4b62 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -56,7 +56,6 @@ PG_FUNCTION_INFO_V1(pg_tde_verify_default_key); typedef struct TdePrincipalKeySharedState { LWLockPadded *Locks; - int hashTrancheId; dshash_table_handle hashHandle; void *rawDsaArea; /* DSA area pointer */ @@ -65,8 +64,6 @@ typedef struct TdePrincipalKeySharedState typedef struct TdePrincipalKeylocalState { TdePrincipalKeySharedState *sharedPrincipalKeyState; - dsa_area *dsa; /* local dsa area for backend attached to the - * dsa area created by postmaster at startup. */ dshash_table *sharedHash; } TdePrincipalKeylocalState; @@ -170,12 +167,12 @@ initialize_shared_state(void *start_address) TdePrincipalKeySharedState *sharedState = (TdePrincipalKeySharedState *) start_address; ereport(LOG, errmsg("initializing shared state for principal key")); - principalKeyLocalState.dsa = NULL; - principalKeyLocalState.sharedHash = NULL; sharedState->Locks = GetNamedLWLockTranche(TDE_TRANCHE_NAME); principalKeyLocalState.sharedPrincipalKeyState = sharedState; + principalKeyLocalState.sharedHash = NULL; + return sizeof(TdePrincipalKeySharedState); } @@ -190,9 +187,8 @@ initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area) Assert(sharedState != NULL); sharedState->rawDsaArea = raw_dsa_area; - sharedState->hashTrancheId = LWLockNewTrancheId(); - principal_key_dsh_params.tranche_id = sharedState->hashTrancheId; - dsh = dshash_create(dsa, &principal_key_dsh_params, 0); + principal_key_dsh_params.tranche_id = LWLockNewTrancheId(); + dsh = dshash_create(dsa, &principal_key_dsh_params, NULL); sharedState->hashHandle = dshash_get_hash_table_handle(dsh); dshash_detach(dsh); } @@ -204,8 +200,9 @@ static void principal_key_info_attach_shmem(void) { MemoryContext oldcontext; + dsa_area *dsa; - if (principalKeyLocalState.dsa) + if (principalKeyLocalState.sharedHash) return; /* @@ -214,18 +211,12 @@ principal_key_info_attach_shmem(void) */ oldcontext = MemoryContextSwitchTo(TopMemoryContext); - principalKeyLocalState.dsa = dsa_attach_in_place(principalKeyLocalState.sharedPrincipalKeyState->rawDsaArea, - NULL); + dsa = dsa_attach_in_place(principalKeyLocalState.sharedPrincipalKeyState->rawDsaArea, NULL); + dsa_pin_mapping(dsa); - /* - * pin the attached area to keep the area attached until end of session or - * explicit detach. - */ - dsa_pin_mapping(principalKeyLocalState.dsa); - - principal_key_dsh_params.tranche_id = principalKeyLocalState.sharedPrincipalKeyState->hashTrancheId; - principalKeyLocalState.sharedHash = dshash_attach(principalKeyLocalState.dsa, &principal_key_dsh_params, + principalKeyLocalState.sharedHash = dshash_attach(dsa, &principal_key_dsh_params, principalKeyLocalState.sharedPrincipalKeyState->hashHandle, 0); + MemoryContextSwitchTo(oldcontext); } From 01ba91f89139977d503fbe0af49fdcb6418b006d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 08:49:50 +0200 Subject: [PATCH 211/796] Remove level from JSON parsing state When parsing key provider options. This was only ever really -1, 0 or 1 and the parser is easier to understand if these are just modelled with the parser's semantic state instead. --- .../src/catalog/tde_keyring_parse_opts.c | 159 +++++++++--------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index c8a1f2721ded2..be24ffd15332e 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -44,6 +44,7 @@ typedef enum JsonKeyringSemState { + JK_EXPECT_TOP_LEVEL_OBJECT, JK_EXPECT_TOP_FIELD, JK_EXPECT_EXTERN_VAL, } JsonKeyringSemState; @@ -102,7 +103,6 @@ static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { [JK_KMIP_CERT_PATH] = "certPath", }; -#define MAX_JSON_DEPTH 64 typedef struct JsonKeyringState { ProviderType provider_type; @@ -110,14 +110,13 @@ typedef struct JsonKeyringState /* Caller's options to be set from JSON values. */ GenericKeyring *provider_opts; - /* - * A field hierarchy of the current branch, field[level] is the current - * one, field[level-1] is the parent and so on. We need to track parent - * fields because of the external values - */ - JsonKeyringField field[MAX_JSON_DEPTH]; + /* The current field in the top level object */ + JsonKeyringField top_level_field; + + /* Current field in any external field object, if any. */ + JsonKeyringField extern_field; + JsonKeyringSemState state; - int level; /* * The rest of the scalar fields might be in the JSON document but has no @@ -154,9 +153,7 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch /* Set up parsing context and initial semantic state */ parse.provider_type = provider_type; parse.provider_opts = out_opts; - parse.level = -1; - parse.state = JK_EXPECT_TOP_FIELD; - memset(parse.field, 0, MAX_JSON_DEPTH * sizeof(JsonKeyringField)); + parse.state = JK_EXPECT_TOP_LEVEL_OBJECT; #if PG_VERSION_NUM >= 170000 jlex = makeJsonLexContextCstringLen(NULL, in_buf, buf_len, PG_UTF8, true); @@ -200,32 +197,28 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch /* * Invoked at the start of each object in the JSON document. * - * Every new object increases the level of nesting as the whole document is the - * object itself (level 0) and every next one means going deeper into nesting. - * - * On the top level, we expect either scalar (string) values or objects referencing - * the external value of the field. Hence, if we are on level 1, we expect an - * "external field object" e.g. ({"type" : "remote", "url" : "http://localhost:8888/hello"}) + * In the top level object, we expect either scalar (string) values or objects + * referencing the external value of the field. If we are already parsing top + * level fields, we expect an "external field object" e.g. ({"type" : "remote", + * "url" : "http://localhost:8888/hello"}) */ static JsonParseErrorType json_kring_object_start(void *state) { JsonKeyringState *parse = state; - if (MAX_JSON_DEPTH == ++parse->level) - { - elog(WARNING, "reached max depth of JSON nesting"); - return JSON_SEM_ACTION_FAILED; - } - - switch (parse->level) + switch (parse->state) { - case 0: + case JK_EXPECT_TOP_LEVEL_OBJECT: parse->state = JK_EXPECT_TOP_FIELD; break; - case 1: + case JK_EXPECT_TOP_FIELD: parse->state = JK_EXPECT_EXTERN_VAL; break; + case JK_EXPECT_EXTERN_VAL: + ereport(ERROR, + errmsg("invalid semantic state")); + break; } return JSON_SUCCESS; @@ -234,10 +227,8 @@ json_kring_object_start(void *state) /* * Invoked at the end of each object in the JSON document. * - * First, it means we are going back to the higher level. Plus, if it was the - * level 1, we expect only external objects there, which means we have all - * the necessary info to extract the value and assign the result to the - * appropriate (parent) field. + * If we're done parsing an external field object we fetch the value from the + * source and assign it to the top level object field. */ static JsonParseErrorType json_kring_object_end(void *state) @@ -252,38 +243,42 @@ json_kring_object_end(void *state) * "/tmp/datafile-location"} the "field"'s value should be the content of * "path" or "url" respectively */ - if (parse->level == 1) + switch (parse->state) { - if (parse->state == JK_EXPECT_EXTERN_VAL) - { - JsonKeyringField parent_field = parse->field[0]; - JsonParseErrorType ret; + case JK_EXPECT_TOP_LEVEL_OBJECT: + ereport(ERROR, + errmsg("invalid semantic state")); + break; + case JK_EXPECT_TOP_FIELD: + /* We're done parsing the top level object */ + break; + case JK_EXPECT_EXTERN_VAL: + { + JsonParseErrorType ret; + char *value = NULL; - char *value = NULL; + if (strcmp(parse->field_type, KEYRING_REMOTE_FIELD_TYPE) == 0) + value = get_remote_kring_value(parse->extern_url, JK_FIELD_NAMES[parse->top_level_field]); + if (strcmp(parse->field_type, KEYRING_FILE_FIELD_TYPE) == 0) + value = get_file_kring_value(parse->extern_path, JK_FIELD_NAMES[parse->top_level_field]); - if (strcmp(parse->field_type, KEYRING_REMOTE_FIELD_TYPE) == 0) - value = get_remote_kring_value(parse->extern_url, JK_FIELD_NAMES[parent_field]); - if (strcmp(parse->field_type, KEYRING_FILE_FIELD_TYPE) == 0) - value = get_file_kring_value(parse->extern_path, JK_FIELD_NAMES[parent_field]); + if (value == NULL) + { + return JSON_INCOMPLETE; + } - if (value == NULL) - { - return JSON_INCOMPLETE; - } + ret = json_kring_assign_scalar(parse, parse->top_level_field, value); - ret = json_kring_assign_scalar(parse, parent_field, value); + if (ret != JSON_SUCCESS) + { + return ret; + } - if (ret != JSON_SUCCESS) - { - return ret; + parse->state = JK_EXPECT_TOP_FIELD; + break; } - } - - parse->state = JK_EXPECT_TOP_FIELD; } - parse->level--; - return JSON_SUCCESS; } @@ -298,55 +293,54 @@ static JsonParseErrorType json_kring_object_field_start(void *state, char *fname, bool isnull) { JsonKeyringState *parse = state; - JsonKeyringField *field; - - Assert(parse->level >= 0); - - field = &parse->field[parse->level]; switch (parse->state) { + case JK_EXPECT_TOP_LEVEL_OBJECT: + ereport(ERROR, + errmsg("invalid semantic state")); + break; case JK_EXPECT_TOP_FIELD: switch (parse->provider_type) { case FILE_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_FILE_PATH]) == 0) - *field = JK_FILE_PATH; + parse->top_level_field = JK_FILE_PATH; else { - *field = JK_FIELD_UNKNOWN; + parse->top_level_field = JK_FIELD_UNKNOWN; elog(ERROR, "parse file keyring config: unexpected field %s", fname); } break; case VAULT_V2_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_TOKEN]) == 0) - *field = JK_VAULT_TOKEN; + parse->top_level_field = JK_VAULT_TOKEN; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_URL]) == 0) - *field = JK_VAULT_URL; + parse->top_level_field = JK_VAULT_URL; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_MOUNT_PATH]) == 0) - *field = JK_VAULT_MOUNT_PATH; + parse->top_level_field = JK_VAULT_MOUNT_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_CA_PATH]) == 0) - *field = JK_VAULT_CA_PATH; + parse->top_level_field = JK_VAULT_CA_PATH; else { - *field = JK_FIELD_UNKNOWN; + parse->top_level_field = JK_FIELD_UNKNOWN; elog(ERROR, "parse json keyring config: unexpected field %s", fname); } break; case KMIP_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_HOST]) == 0) - *field = JK_KMIP_HOST; + parse->top_level_field = JK_KMIP_HOST; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_PORT]) == 0) - *field = JK_KMIP_PORT; + parse->top_level_field = JK_KMIP_PORT; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_CA_PATH]) == 0) - *field = JK_KMIP_CA_PATH; + parse->top_level_field = JK_KMIP_CA_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_CERT_PATH]) == 0) - *field = JK_KMIP_CERT_PATH; + parse->top_level_field = JK_KMIP_CERT_PATH; else { - *field = JK_FIELD_UNKNOWN; + parse->top_level_field = JK_FIELD_UNKNOWN; elog(ERROR, "parse json keyring config: unexpected field %s", fname); } break; @@ -358,14 +352,14 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) case JK_EXPECT_EXTERN_VAL: if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_TYPE]) == 0) - *field = JK_FIELD_TYPE; + parse->extern_field = JK_FIELD_TYPE; else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_URL]) == 0) - *field = JK_FIELD_URL; + parse->extern_field = JK_FIELD_URL; else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_PATH]) == 0) - *field = JK_FIELD_PATH; + parse->extern_field = JK_FIELD_PATH; else { - *field = JK_FIELD_UNKNOWN; + parse->extern_field = JK_FIELD_UNKNOWN; elog(ERROR, "parse json keyring config: unexpected field %s", fname); } break; @@ -384,8 +378,23 @@ static JsonParseErrorType json_kring_scalar(void *state, char *token, JsonTokenType tokentype) { JsonKeyringState *parse = state; + JsonKeyringField *field = NULL; + + switch (parse->state) + { + case JK_EXPECT_TOP_LEVEL_OBJECT: + ereport(ERROR, + errmsg("invalid semantic state")); + break; + case JK_EXPECT_TOP_FIELD: + field = &parse->top_level_field; + break; + case JK_EXPECT_EXTERN_VAL: + field = &parse->extern_field; + break; + } - return json_kring_assign_scalar(parse, parse->field[parse->level], token); + return json_kring_assign_scalar(parse, *field, token); } static JsonParseErrorType From 030052925a596eb598186aa900bcbc37d2a27d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 09:20:00 +0200 Subject: [PATCH 212/796] Require key provider settings to be an object Reject bare scalars or arrays. In this commit we do not change the behaviour if a value inside the configuration object is an array. --- contrib/pg_tde/expected/key_provider.out | 18 +++++++++++++ contrib/pg_tde/sql/key_provider.sql | 12 +++++++++ .../src/catalog/tde_keyring_parse_opts.c | 25 +++++++++++++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 8b8c45c15c071..0e51669c16b1c 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -192,6 +192,15 @@ SELECT pg_tde_add_database_key_provider('file', 'name', json_build_object('key', ERROR: too large provider options, maximum size is 1023 bytes SELECT pg_tde_add_global_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); ERROR: too large provider options, maximum size is 1023 bytes +-- Creating key providers fails if configuration is not a JSON object +SELECT pg_tde_add_database_key_provider('file', 'provider', '"bare string"'); +ERROR: key provider options must be an object +SELECT pg_tde_add_database_key_provider('file', 'provider', '["array"]'); +ERROR: key provider options must be an object +SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); +ERROR: key provider options must be an object +SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); +ERROR: key provider options must be an object -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -210,6 +219,15 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', json_build_o ERROR: too large provider options, maximum size is 1023 bytes SELECT pg_tde_change_global_key_provider('file', 'file-keyring', json_build_object('key', repeat('V', 1024))); ERROR: too large provider options, maximum size is 1023 bytes +-- Modifying key providers fails if configuration is not a JSON object +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '"bare string"'); +ERROR: key provider options must be an object +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '["array"]'); +ERROR: key provider options must be an object +SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); +ERROR: key provider options must be an object +SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); +ERROR: key provider options must be an object -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index e18834de35a92..6a83eeb8f92ff 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -75,6 +75,12 @@ SELECT pg_tde_add_global_key_provider('file', repeat('K', 128), '{}'); SELECT pg_tde_add_database_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); SELECT pg_tde_add_global_key_provider('file', 'name', json_build_object('key', repeat('K', 1024))); +-- Creating key providers fails if configuration is not a JSON object +SELECT pg_tde_add_database_key_provider('file', 'provider', '"bare string"'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '["array"]'); +SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); +SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); @@ -87,6 +93,12 @@ SELECT pg_tde_change_global_key_provider('file', 'file-keyring', NULL); SELECT pg_tde_change_database_key_provider('file', 'file-provider', json_build_object('key', repeat('V', 1024))); SELECT pg_tde_change_global_key_provider('file', 'file-keyring', json_build_object('key', repeat('V', 1024))); +-- Modifying key providers fails if configuration is not a JSON object +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '"bare string"'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '["array"]'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); + -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index be24ffd15332e..3dfb7be06a0ba 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -129,6 +129,7 @@ typedef struct JsonKeyringState } JsonKeyringState; static JsonParseErrorType json_kring_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType json_kring_array_start(void *state); static JsonParseErrorType json_kring_object_field_start(void *state, char *fname, bool isnull); static JsonParseErrorType json_kring_object_start(void *state); static JsonParseErrorType json_kring_object_end(void *state); @@ -168,7 +169,7 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch sem.semstate = &parse; sem.object_start = json_kring_object_start; sem.object_end = json_kring_object_end; - sem.array_start = NULL; + sem.array_start = json_kring_array_start; sem.array_end = NULL; sem.object_field_start = json_kring_object_field_start; sem.object_field_end = NULL; @@ -194,6 +195,25 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch * JSON parser semantic actions */ +static JsonParseErrorType +json_kring_array_start(void *state) +{ + JsonKeyringState *parse = state; + + switch (parse->state) + { + case JK_EXPECT_TOP_LEVEL_OBJECT: + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key provider options must be an object")); + break; + case JK_EXPECT_TOP_FIELD: + case JK_EXPECT_EXTERN_VAL: + } + + return JSON_SUCCESS; +} + /* * Invoked at the start of each object in the JSON document. * @@ -384,7 +404,8 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) { case JK_EXPECT_TOP_LEVEL_OBJECT: ereport(ERROR, - errmsg("invalid semantic state")); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key provider options must be an object")); break; case JK_EXPECT_TOP_FIELD: field = &parse->top_level_field; From dc909626dc95bd4eee2cca119e50ca98510fec06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 14:47:12 +0200 Subject: [PATCH 213/796] Validate required fields for external JSON values Also pfree() them when they are used. We're probably leaking a lot of memory in the parser anyway, but since we want to NULL these for the next round around this seems like the right thing to do. --- contrib/pg_tde/expected/key_provider.out | 14 ++++++++++ contrib/pg_tde/sql/key_provider.sql | 9 +++++++ .../src/catalog/tde_keyring_parse_opts.c | 26 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 0e51669c16b1c..aff2d07bea3f7 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -201,6 +201,13 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); ERROR: key provider options must be an object SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); ERROR: key provider options must be an object +-- Creating key providers fails if an external value object doesn't have all required keys +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); +ERROR: external value must contain "type" in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); +ERROR: external remote value must contain "url" in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); +ERROR: external file value must contain "path" in field "path" -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -228,6 +235,13 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); ERROR: key provider options must be an object SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); ERROR: key provider options must be an object +-- Modifying key providers fails if an external value object doesn't have all required keys +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); +ERROR: external value must contain "type" in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); +ERROR: external remote value must contain "url" in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); +ERROR: external file value must contain "path" in field "path" -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 6a83eeb8f92ff..06462e7e2cd2c 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -81,6 +81,11 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', '["array"]'); SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); +-- Creating key providers fails if an external value object doesn't have all required keys +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); @@ -99,6 +104,10 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '["array"]') SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); +-- Modifying key providers fails if an external value object doesn't have all required keys +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 3dfb7be06a0ba..91f4e3ccc317c 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -277,10 +277,36 @@ json_kring_object_end(void *state) JsonParseErrorType ret; char *value = NULL; + if (!parse->field_type) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("external value must contain \"type\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + if (strcmp(parse->field_type, KEYRING_REMOTE_FIELD_TYPE) == 0) + { + if (!parse->extern_url) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("external remote value must contain \"url\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + value = get_remote_kring_value(parse->extern_url, JK_FIELD_NAMES[parse->top_level_field]); + pfree(parse->extern_url); + parse->extern_url = NULL; + } if (strcmp(parse->field_type, KEYRING_FILE_FIELD_TYPE) == 0) + { + if (!parse->extern_path) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("external file value must contain \"path\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + value = get_file_kring_value(parse->extern_path, JK_FIELD_NAMES[parse->top_level_field]); + pfree(parse->extern_path); + parse->extern_path = NULL; + } + + pfree(parse->field_type); + parse->field_type = NULL; if (value == NULL) { From fe50a21b31c4f6ad1e3ef8829cac77755da85f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 14:48:30 +0200 Subject: [PATCH 214/796] Disallow arrays in key provider options These are never valid values anyways, so just disallow them completely. --- contrib/pg_tde/expected/key_provider.out | 14 ++++++++++++++ contrib/pg_tde/sql/key_provider.sql | 11 +++++++++++ .../pg_tde/src/catalog/tde_keyring_parse_opts.c | 8 +++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index aff2d07bea3f7..427004c23b615 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -208,6 +208,13 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": " ERROR: external remote value must contain "url" in field "path" SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); ERROR: external file value must contain "path" in field "path" +-- Creating key providers fails if values are array instead of scalar +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); +ERROR: unexpected array in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": ["array"]}}'); +ERROR: unexpected array in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); +ERROR: unexpected array in field "path" -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -242,6 +249,13 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {" ERROR: external remote value must contain "url" in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); ERROR: external file value must contain "path" in field "path" +-- Modifying key providers fails if values are array instead of scalar +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); +ERROR: unexpected array in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": ["array"]}}'); +ERROR: unexpected array in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); +ERROR: unexpected array in field "path" -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 06462e7e2cd2c..2957cc9923f03 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -86,6 +86,11 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); +-- Creating key providers fails if values are array instead of scalar +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": ["array"]}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); @@ -108,6 +113,12 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); + +-- Modifying key providers fails if values are array instead of scalar +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": ["array"]}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); + -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 91f4e3ccc317c..2a20750d0c214 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -209,9 +209,15 @@ json_kring_array_start(void *state) break; case JK_EXPECT_TOP_FIELD: case JK_EXPECT_EXTERN_VAL: + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected array in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + break; } - return JSON_SUCCESS; + /* Never reached */ + Assert(0); + return JSON_SEM_ACTION_FAILED; } /* From 4807e46b0b0cf6f72ceeb60ca621dce2e4561d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 15:48:13 +0200 Subject: [PATCH 215/796] Handle JSON scalar types in key provider options Treat null as missing value, error out on boolean and make no change for number or string. --- contrib/pg_tde/expected/key_provider.out | 26 +++++++++++++++++++ contrib/pg_tde/sql/key_provider.sql | 16 ++++++++++++ .../src/catalog/tde_keyring_parse_opts.c | 25 +++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 427004c23b615..cc7b30be22d2b 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -204,10 +204,16 @@ ERROR: key provider options must be an object -- Creating key providers fails if an external value object doesn't have all required keys SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); ERROR: external value must contain "type" in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": null}}'); +ERROR: external value must contain "type" in field "path" SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); ERROR: external remote value must contain "url" in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); +ERROR: external remote value must contain "url" in field "path" SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); ERROR: external file value must contain "path" in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); +ERROR: external file value must contain "path" in field "path" -- Creating key providers fails if values are array instead of scalar SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); ERROR: unexpected array in field "path" @@ -215,6 +221,13 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": [ ERROR: unexpected array in field "path" SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); ERROR: unexpected array in field "path" +-- Creating key providers fails if values are boolean +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": true}'); +ERROR: unexpected boolean in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": true}}'); +ERROR: unexpected boolean in field "path" +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); +ERROR: unexpected boolean in field "path" -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -245,10 +258,16 @@ ERROR: key provider options must be an object -- Modifying key providers fails if an external value object doesn't have all required keys SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); ERROR: external value must contain "type" in field "path" +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": null}}'); +ERROR: key provider "provider" does not exists SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); ERROR: external remote value must contain "url" in field "path" +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); +ERROR: key provider "provider" does not exists SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); ERROR: external file value must contain "path" in field "path" +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); +ERROR: key provider "provider" does not exists -- Modifying key providers fails if values are array instead of scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); ERROR: unexpected array in field "path" @@ -256,6 +275,13 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {" ERROR: unexpected array in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); ERROR: unexpected array in field "path" +-- Modifying key providers fails if values are boolean +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": true}'); +ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": true}}'); +ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); +ERROR: key provider "provider" does not exists -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 2957cc9923f03..f3d594a3b9bce 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -83,14 +83,22 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); -- Creating key providers fails if an external value object doesn't have all required keys SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": null}}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); -- Creating key providers fails if values are array instead of scalar SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": ["array"]}}'); SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); +-- Creating key providers fails if values are boolean +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": true}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": true}}'); +SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); + -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); SELECT pg_tde_change_database_key_provider('file', NULL, '{}'); @@ -111,14 +119,22 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); -- Modifying key providers fails if an external value object doesn't have all required keys SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": null}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); -- Modifying key providers fails if values are array instead of scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": ["array"]}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); +-- Modifying key providers fails if values are boolean +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": true}'); +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": true}}'); +SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); + -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 2a20750d0c214..6fbbb9d1132ec 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -431,6 +431,7 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) { JsonKeyringState *parse = state; JsonKeyringField *field = NULL; + char *value; switch (parse->state) { @@ -447,7 +448,29 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) break; } - return json_kring_assign_scalar(parse, *field, token); + switch (tokentype) + { + case JSON_TOKEN_STRING: + case JSON_TOKEN_NUMBER: + value = token; + break; + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected boolean in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + break; + case JSON_TOKEN_NULL: + value = NULL; + pfree(token); + break; + default: + ereport(ERROR, + errmsg("invalid token type")); + break; + } + + return json_kring_assign_scalar(parse, *field, value); } static JsonParseErrorType From aba7ce6508f4e3d4bc42ff04f18c789858a900dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 6 May 2025 15:00:04 +0200 Subject: [PATCH 216/796] Don't crash on null input to kmip key provider Also use the same way to compare to the empty string as everything else around this. --- contrib/pg_tde/src/catalog/tde_keyring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index d8c0bb0c897f5..0fefa6e9573c2 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -886,10 +886,10 @@ load_kmip_keyring_provider_options(char *keyring_options) ParseKeyringJSONOptions(KMIP_KEY_PROVIDER, (GenericKeyring *) kmip_keyring, keyring_options, strlen(keyring_options)); - if (strlen(kmip_keyring->kmip_host) == 0 || - strlen(kmip_keyring->kmip_port) == 0 || - strlen(kmip_keyring->kmip_ca_path) == 0 || - strlen(kmip_keyring->kmip_cert_path) == 0) + if (kmip_keyring->kmip_host == NULL || kmip_keyring->kmip_host[0] == '\0' || + kmip_keyring->kmip_port == NULL || kmip_keyring->kmip_port[0] == '\0' || + kmip_keyring->kmip_ca_path == NULL || kmip_keyring->kmip_ca_path[0] == '\0' || + kmip_keyring->kmip_cert_path == NULL || kmip_keyring->kmip_cert_path[0] == '\0') { ereport(WARNING, errcode(ERRCODE_INVALID_PARAMETER_VALUE), From eae05e0741cee58a6a423f8abb2a2294468502b9 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:49:43 +0200 Subject: [PATCH 217/796] Fix whitespace at end of file Every text file (with the exception of test output) should end in exactly one newline. --- .github/workflows/coverage.yml | 2 -- README.md | 1 - contrib/pg_tde/documentation/_resource/overrides/main.html | 3 --- .../documentation/_resource/overrides/partials/banner.html | 2 +- .../documentation/_resource/overrides/partials/copyright.html | 2 +- .../documentation/_resource/overrides/partials/header.html | 2 +- contrib/pg_tde/documentation/_resource/templates/styles.scss | 2 +- contrib/pg_tde/documentation/docs/css/design.css | 2 +- contrib/pg_tde/documentation/docs/css/extra.css | 2 +- contrib/pg_tde/documentation/docs/css/landing.css | 2 +- contrib/pg_tde/documentation/docs/css/osano.css | 2 +- contrib/pg_tde/documentation/docs/css/postgresql.css | 2 +- contrib/pg_tde/documentation/docs/js/consent.js | 2 +- contrib/pg_tde/documentation/docs/js/promptremover.js | 4 ---- contrib/pg_tde/documentation/mkdocs-pdf.yml | 1 - contrib/pg_tde/documentation/mkdocs.yml | 2 -- contrib/pg_tde/documentation/requirements.txt | 2 +- src/libtde/Makefile | 2 +- 18 files changed, 12 insertions(+), 25 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 805be41ba7ac0..10b58784bdc7e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -61,5 +61,3 @@ jobs: src/test/*/results src/test/*/tmp_check retention-days: 3 - - diff --git a/README.md b/README.md index 4501f53904c77..afb02c0f108b2 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,3 @@ Specific - include as much detail as possible, such as which version, which envi Unique - do not duplicate existing tickets Scoped to a single issue - only one issue per report - diff --git a/contrib/pg_tde/documentation/_resource/overrides/main.html b/contrib/pg_tde/documentation/_resource/overrides/main.html index 6aef80e6c1bd3..7e90c9be7724c 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/main.html +++ b/contrib/pg_tde/documentation/_resource/overrides/main.html @@ -64,6 +64,3 @@ posthog.init('phc_7unoI9J6Fm0SMrfDp35xNOpCRTkOAibbffQwdGWbHnL',{api_host:'https://eu.posthog.com'}) {% endblock %} - - - diff --git a/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html b/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html index fb26631493f1a..1553c0301cc1f 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html +++ b/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html @@ -6,4 +6,4 @@ Get help from Percona - \ No newline at end of file + diff --git a/contrib/pg_tde/documentation/_resource/overrides/partials/copyright.html b/contrib/pg_tde/documentation/_resource/overrides/partials/copyright.html index dd0f101fad6df..95c8eaf6cf73d 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/partials/copyright.html +++ b/contrib/pg_tde/documentation/_resource/overrides/partials/copyright.html @@ -11,4 +11,4 @@ Material for MkDocs {% endif %} - \ No newline at end of file + diff --git a/contrib/pg_tde/documentation/_resource/overrides/partials/header.html b/contrib/pg_tde/documentation/_resource/overrides/partials/header.html index 2d0d6e7401a04..1983ab1baef50 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/partials/header.html +++ b/contrib/pg_tde/documentation/_resource/overrides/partials/header.html @@ -132,4 +132,4 @@ {% include "partials/tabs.html" %} {% endif %} {% endif %} - \ No newline at end of file + diff --git a/contrib/pg_tde/documentation/_resource/templates/styles.scss b/contrib/pg_tde/documentation/_resource/templates/styles.scss index fc9bc9fce52e9..ccbbe96de6e37 100644 --- a/contrib/pg_tde/documentation/_resource/templates/styles.scss +++ b/contrib/pg_tde/documentation/_resource/templates/styles.scss @@ -115,4 +115,4 @@ article div.tabbed-content--wrap * { .md-typeset details.note { color: #00162b; border-color: #fff; -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/css/design.css b/contrib/pg_tde/documentation/docs/css/design.css index 2e14d20e5853a..f4861d6db7a7d 100644 --- a/contrib/pg_tde/documentation/docs/css/design.css +++ b/contrib/pg_tde/documentation/docs/css/design.css @@ -732,4 +732,4 @@ i[warning] [class*="moji"] { padding: 1em; } } -/**/ \ No newline at end of file +/**/ diff --git a/contrib/pg_tde/documentation/docs/css/extra.css b/contrib/pg_tde/documentation/docs/css/extra.css index c857532bb8797..f5a28052fc9b9 100644 --- a/contrib/pg_tde/documentation/docs/css/extra.css +++ b/contrib/pg_tde/documentation/docs/css/extra.css @@ -10,4 +10,4 @@ .md-sidebar__inner { font-size: 0.65rem; /* Font size */ line-height: 1.6; -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/css/landing.css b/contrib/pg_tde/documentation/docs/css/landing.css index df69386e85e9a..1e6eddc72aedf 100644 --- a/contrib/pg_tde/documentation/docs/css/landing.css +++ b/contrib/pg_tde/documentation/docs/css/landing.css @@ -298,4 +298,4 @@ a.splash-card img { [data-news] [data-article] { flex: 1 1 100%; } -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/css/osano.css b/contrib/pg_tde/documentation/docs/css/osano.css index b89fa6ac21069..79565a16100b9 100644 --- a/contrib/pg_tde/documentation/docs/css/osano.css +++ b/contrib/pg_tde/documentation/docs/css/osano.css @@ -203,4 +203,4 @@ .osano-cm-dialog--type_bar .osano-cm-dialog__buttons { max-width: 20em; } -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/css/postgresql.css b/contrib/pg_tde/documentation/docs/css/postgresql.css index e5d70d97d7e6b..f0931b48906e5 100644 --- a/contrib/pg_tde/documentation/docs/css/postgresql.css +++ b/contrib/pg_tde/documentation/docs/css/postgresql.css @@ -58,4 +58,4 @@ .superNav, .md-nav__source { background-color: var(--night500); -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/js/consent.js b/contrib/pg_tde/documentation/docs/js/consent.js index b6f8a8ac0a3ac..4ff680d8a79e0 100644 --- a/contrib/pg_tde/documentation/docs/js/consent.js +++ b/contrib/pg_tde/documentation/docs/js/consent.js @@ -3,4 +3,4 @@ if (consent && consent.custom) { /* The user accepted the cookie */ } else { /* The user rejected the cookie */ -} \ No newline at end of file +} diff --git a/contrib/pg_tde/documentation/docs/js/promptremover.js b/contrib/pg_tde/documentation/docs/js/promptremover.js index aef117323fbe0..a6241fd80d221 100644 --- a/contrib/pg_tde/documentation/docs/js/promptremover.js +++ b/contrib/pg_tde/documentation/docs/js/promptremover.js @@ -38,7 +38,3 @@ document.addEventListener("DOMContentLoaded", function(){ commandButtonElement.item(0).setAttribute("data-clipboard-text", trueCommand); } }); - - - - diff --git a/contrib/pg_tde/documentation/mkdocs-pdf.yml b/contrib/pg_tde/documentation/mkdocs-pdf.yml index fc87289bae446..990732fb25072 100644 --- a/contrib/pg_tde/documentation/mkdocs-pdf.yml +++ b/contrib/pg_tde/documentation/mkdocs-pdf.yml @@ -13,4 +13,3 @@ extra_css: markdown_extensions: pymdownx.tabbed: {} admonition: {} - diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 91ac69b6bfbe7..d5bd75513b4eb 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -185,5 +185,3 @@ nav: - release-notes/mvp.md - uninstall.md - contribute.md - - diff --git a/contrib/pg_tde/documentation/requirements.txt b/contrib/pg_tde/documentation/requirements.txt index b3ee7d78e7043..6e1ce1300850c 100644 --- a/contrib/pg_tde/documentation/requirements.txt +++ b/contrib/pg_tde/documentation/requirements.txt @@ -16,4 +16,4 @@ mkdocs-meta-descriptions-plugin mike mkdocs-glightbox Pillow > 10.1.0 -mkdocs-open-in-new-tab \ No newline at end of file +mkdocs-open-in-new-tab diff --git a/src/libtde/Makefile b/src/libtde/Makefile index 57c72e1d6bb86..fbe8d7db04960 100644 --- a/src/libtde/Makefile +++ b/src/libtde/Makefile @@ -7,4 +7,4 @@ include $(top_srcdir)/contrib/pg_tde/Makefile.tools all: tdelibs -install: all \ No newline at end of file +install: all From 40ce62f9cc1e50608e75d659876d2daf0da600f2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:50:39 +0200 Subject: [PATCH 218/796] Remove newlines at beginning of files No text file should begin with a newline. --- contrib/pg_tde/documentation/docs/css/landing.css | 1 - contrib/pg_tde/documentation/requirements.txt | 1 - contrib/pg_tde/src/include/smgr/pg_tde_smgr.h | 1 - contrib/pg_tde/src/pg_tde_change_key_provider.c | 1 - contrib/pg_tde/src/smgr/pg_tde_smgr.c | 1 - src/libtde/Makefile | 1 - 6 files changed, 6 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/css/landing.css b/contrib/pg_tde/documentation/docs/css/landing.css index 1e6eddc72aedf..d80a0fea33229 100644 --- a/contrib/pg_tde/documentation/docs/css/landing.css +++ b/contrib/pg_tde/documentation/docs/css/landing.css @@ -1,4 +1,3 @@ - /* Type */ .landing h1, diff --git a/contrib/pg_tde/documentation/requirements.txt b/contrib/pg_tde/documentation/requirements.txt index 6e1ce1300850c..5c5cb4d1bea6e 100644 --- a/contrib/pg_tde/documentation/requirements.txt +++ b/contrib/pg_tde/documentation/requirements.txt @@ -1,4 +1,3 @@ - Markdown mkdocs mkdocs-versioning diff --git a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h index d56c9ac04e138..65b54e5a5e9fd 100644 --- a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h +++ b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h @@ -1,4 +1,3 @@ - /*------------------------------------------------------------------------- * * pg_tde_smgr.h diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 87db2c5d819b9..fa4c1d69171c6 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -1,4 +1,3 @@ - #include "postgres_fe.h" #include "pg_tde.h" diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index de28eb1cb7f48..5e312c1912dc8 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -1,4 +1,3 @@ - #include "smgr/pg_tde_smgr.h" #include "postgres.h" #include "storage/smgr.h" diff --git a/src/libtde/Makefile b/src/libtde/Makefile index fbe8d7db04960..4ed386e20ee58 100644 --- a/src/libtde/Makefile +++ b/src/libtde/Makefile @@ -1,4 +1,3 @@ - subdir = src/libtde top_builddir = ../.. From 5c2b7276cf147de850a228372c3afe0e3a6f7671 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:54:23 +0200 Subject: [PATCH 219/796] Only ever have one empty line as separator While in theory it could be useful to separate blocks with double empty lines nobody does that consistently so we might as well only ever use one empty line. --- contrib/pg_tde/documentation/mkdocs.yml | 4 ---- contrib/pg_tde/pg_tde--1.0-rc.sql | 1 - contrib/pg_tde/src/access/pg_tde_tdemap.c | 5 ----- contrib/pg_tde/src/catalog/tde_keyring.c | 1 - contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 3 --- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 1 - contrib/pg_tde/src/include/catalog/tde_principal_key.h | 1 - contrib/pg_tde/src/include/pg_tde_guc.h | 1 - contrib/pg_tde/src/pg_tde.c | 1 - contrib/pg_tde/src/pg_tde_change_key_provider.c | 2 -- contrib/pg_tde/t/014_pg_waldump_basic.pl | 1 - 11 files changed, 21 deletions(-) diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index d5bd75513b4eb..bbc8b604fc6a8 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -6,7 +6,6 @@ site_author: Percona LLC copyright: > Percona LLC and/or its affiliates © 2025 — Cookie Consent - repo_name: percona/postgres repo_url: https://github.com/percona/postgres edit_uri: edit/TDE_REL_17_STABLE/contrib/pg_tde/documentation/docs @@ -54,8 +53,6 @@ theme: - navigation.tracking - navigation.indexes - - extra_css: - https://unicons.iconscout.com/release/v3.0.3/css/line.css - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css @@ -100,7 +97,6 @@ markdown_extensions: custom_icons: - _resource/.icons - plugins: - search: separator: '[\s\-,:!=\[\]()"/]+|(?!\b)(?=[A-Z][a-z])|\.(?!\d)|&[lg]t;' diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 19668c072cedf..8584090541690 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -97,7 +97,6 @@ BEGIN ATOMIC 'certPath' VALUE kmip_cert_path)); END; - CREATE FUNCTION pg_tde_list_all_database_key_providers (OUT id INT, OUT provider_name TEXT, diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 35bc7229d29cb..b870683618436 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -55,7 +55,6 @@ #define PG_TDE_FILEMAGIC 0x02454454 /* version ID value = TDE 02 */ - #define MAP_ENTRY_SIZE sizeof(TDEMapEntry) #define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) @@ -100,7 +99,6 @@ RelKeyCache tde_rel_key_cache = { .cap = 0, }; - /* * TODO: WAL should have its own RelKeyCache */ @@ -911,7 +909,6 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) ereport(ERROR, errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); - return rel_key_data; } @@ -963,7 +960,6 @@ pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_miss return fd; } - /* * Read TDE file header from a TDE file and fill in the fheader data structure. */ @@ -989,7 +985,6 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader } } - /* * Returns true if a map entry if found or false if we have reached the end of * the file. diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 0fefa6e9573c2..6eff577e9af07 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -811,7 +811,6 @@ load_keyring_provider_from_record(KeyringProviderRecord *provider) return keyring; } - static GenericKeyring * load_keyring_provider_options(ProviderType provider_type, char *keyring_options) { diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 6fbbb9d1132ec..a41234701805f 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -41,7 +41,6 @@ /* * JSON parser state */ - typedef enum JsonKeyringSemState { JK_EXPECT_TOP_LEVEL_OBJECT, @@ -138,7 +137,6 @@ static JsonParseErrorType json_kring_assign_scalar(JsonKeyringState *parse, Json static char *get_remote_kring_value(const char *url, const char *field_name); static char *get_file_kring_value(const char *path, const char *field_name); - /* * Parses json input for the given provider type and sets the provided options. * out_opts should be a palloc'd keyring object matching the provider_type. @@ -184,7 +182,6 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch ereport(ERROR, errmsg("parsing of keyring options failed: %s", json_errdetail(jerr, jlex))); - } #if PG_VERSION_NUM >= 170000 freeJsonLexContext(jlex); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 72a1ea4ad62a7..0955569b00cdc 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -89,7 +89,6 @@ typedef struct WALKeyCacheRec struct WALKeyCacheRec *next; } WALKeyCacheRec; - extern InternalKey *pg_tde_read_last_wal_key(void); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index d83c8bdaeaeee..ee5870ccf7b3c 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -10,7 +10,6 @@ #ifndef PG_TDE_PRINCIPAL_KEY_H #define PG_TDE_PRINCIPAL_KEY_H - #include "postgres.h" #include "catalog/tde_keyring.h" #ifndef FRONTEND diff --git a/contrib/pg_tde/src/include/pg_tde_guc.h b/contrib/pg_tde/src/include/pg_tde_guc.h index 472a00bb2791e..c3f95f3313c77 100644 --- a/contrib/pg_tde/src/include/pg_tde_guc.h +++ b/contrib/pg_tde/src/include/pg_tde_guc.h @@ -11,7 +11,6 @@ #ifndef TDE_GUC_H #define TDE_GUC_H - #include "postgres.h" #ifndef FRONTEND diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 92cf0d9892d73..3127c2c4cff09 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -39,7 +39,6 @@ #include - PG_MODULE_MAGIC; static void pg_tde_init_data_dir(void); diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index fa4c1d69171c6..489dbcdad339f 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -63,7 +63,6 @@ build_json(char *buffer, int count,...) ptr = strcat(ptr, ","); } - ptr = strcat(ptr, "\""); ptr = strcat(ptr, key); ptr = strcat(ptr, "\":"); @@ -146,7 +145,6 @@ main(int argc, char *argv[]) argstart += 2; } - if (datadir == NULL || strlen(datadir) == 0) { help(); diff --git a/contrib/pg_tde/t/014_pg_waldump_basic.pl b/contrib/pg_tde/t/014_pg_waldump_basic.pl index ec798cd765bb1..7b37b909dee41 100644 --- a/contrib/pg_tde/t/014_pg_waldump_basic.pl +++ b/contrib/pg_tde/t/014_pg_waldump_basic.pl @@ -40,7 +40,6 @@ }); $node->restart; - my ($start_lsn, $start_walfile) = split /\|/, $node->safe_psql('postgres', q{SELECT pg_current_wal_insert_lsn(), pg_walfile_name(pg_current_wal_insert_lsn())} From fd6ed3a680e9d9ca8a14923b8801df0a0b357fb8 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:55:24 +0200 Subject: [PATCH 220/796] Remove trailing whitespace at end of line With the exception of test output files no text file should have extra whitesapce and the end of line. --- .../pg_tde/documentation/_resource/overrides/main.html | 8 ++++---- contrib/pg_tde/documentation/docs/css/design.css | 2 +- contrib/pg_tde/documentation/docs/css/extra.css | 2 +- contrib/pg_tde/documentation/docs/css/landing.css | 2 +- contrib/pg_tde/documentation/docs/css/osano.css | 2 +- contrib/pg_tde/documentation/mkdocs.yml | 8 ++++---- contrib/pg_tde/expected/change_access_method.out | 4 +--- contrib/pg_tde/expected/default_principal_key.out | 5 ++--- contrib/pg_tde/sql/change_access_method.sql | 6 +++--- contrib/pg_tde/sql/default_principal_key.sql | 6 +++--- 10 files changed, 21 insertions(+), 24 deletions(-) diff --git a/contrib/pg_tde/documentation/_resource/overrides/main.html b/contrib/pg_tde/documentation/_resource/overrides/main.html index 7e90c9be7724c..8e523a7d493bc 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/main.html +++ b/contrib/pg_tde/documentation/_resource/overrides/main.html @@ -4,7 +4,7 @@ {% extends "base.html" %} {% block announce %} - This is a Release Candidate of Percona Transparent Data Encryption extension and it is + This is a Release Candidate of Percona Transparent Data Encryption extension and it is not recommended for production environments yet. We encourage you to test it and give your feedback. This will help us improve the product and make it production-ready faster. {% endblock %} @@ -13,7 +13,7 @@ {{ super() }} {% endblock %} - + {% block extrahead %} {{ super() }} {% set title = config.site_name %} @@ -27,7 +27,7 @@ {% endblock %} - + {% block site_nav %} {% if nav %} {% if page.meta and page.meta.hide %} @@ -39,7 +39,7 @@ {% include "partials/nav.html" %}
+ diff --git a/contrib/pg_tde/documentation/docs/css/design.css b/contrib/pg_tde/documentation/docs/css/design.css index f4861d6db7a7d..d2a6c3cb59bf6 100644 --- a/contrib/pg_tde/documentation/docs/css/design.css +++ b/contrib/pg_tde/documentation/docs/css/design.css @@ -93,7 +93,7 @@ /* Accent */ --md-accent-fg-color: var(--sky500); - + /* Footer */ --md-footer-fg-color: var(--stone900); --md-footer-fg-color--light: rgba(44,50,62,0.72); diff --git a/contrib/pg_tde/documentation/docs/css/extra.css b/contrib/pg_tde/documentation/docs/css/extra.css index f5a28052fc9b9..38b682c08a883 100644 --- a/contrib/pg_tde/documentation/docs/css/extra.css +++ b/contrib/pg_tde/documentation/docs/css/extra.css @@ -5,7 +5,7 @@ left: 0.6rem; } -} +} .md-sidebar__inner { font-size: 0.65rem; /* Font size */ diff --git a/contrib/pg_tde/documentation/docs/css/landing.css b/contrib/pg_tde/documentation/docs/css/landing.css index d80a0fea33229..f8a16c5e62519 100644 --- a/contrib/pg_tde/documentation/docs/css/landing.css +++ b/contrib/pg_tde/documentation/docs/css/landing.css @@ -249,7 +249,7 @@ a.splash-card img { line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 3; - -webkit-box-orient: vertical; + -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; max-height: 2.8em; diff --git a/contrib/pg_tde/documentation/docs/css/osano.css b/contrib/pg_tde/documentation/docs/css/osano.css index 79565a16100b9..c937cb503e8e5 100644 --- a/contrib/pg_tde/documentation/docs/css/osano.css +++ b/contrib/pg_tde/documentation/docs/css/osano.css @@ -14,7 +14,7 @@ .osano-cm-dialog { font-size: 0.75em; padding: 2em 1em; - color: var(--md-typeset-color); + color: var(--md-typeset-color); background: var(--md-footer-bg-color--dark); } .osano-cm-header, diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index bbc8b604fc6a8..bdac5f4823963 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -125,7 +125,7 @@ plugins: debug_html: false # two_columns_level: 3 custom_template_path: _resource/templates - enabled_if_env: ENABLE_PDF_EXPORT + enabled_if_env: ENABLE_PDF_EXPORT extra: version: @@ -144,8 +144,8 @@ extra: - icon: material/emoticon-sad-outline name: This page could be improved data: 0 - note: >- - Thank you for your feedback! Help us improve by using our + note: >- + Thank you for your feedback! Help us improve by using our feedback form. @@ -156,7 +156,7 @@ nav: - Get started: - "1. Install": "install.md" - "Via apt": apt.md - - "Via yum": yum.md + - "Via yum": yum.md - "2. Set up": "setup.md" - "3. Configure WAL encryption (tech preview)": wal-encryption.md - "4. Test TDE": "test.md" diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index ce8f54af7067d..d4841ee890c83 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -16,7 +16,6 @@ CREATE TABLE country_table ( country_name varchar(32) unique not null, continent varchar(32) not null ) USING tde_heap; - INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), ('UK', 'Europe'), @@ -49,7 +48,7 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); -- Try changing the encrypted table to an unencrypted table ALTER TABLE country_table SET ACCESS METHOD heap; --- Insert some more data +-- Insert some more data INSERT INTO country_table (country_name, continent) VALUES ('France', 'Europe'), ('Germany', 'Europe'), @@ -158,7 +157,6 @@ CREATE TABLE country_table3 ( continent text not null ) USING heap; ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. - ALTER TABLE country_table SET ACCESS METHOD heap; ERROR: pg_tde.enforce_encryption is ON, only the tde_heap access method is allowed. ALTER TABLE country_table2 SET ACCESS METHOD tde_heap; diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 8569aab5b42a0..00ccd625194a5 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -10,7 +10,7 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi SELECT pg_tde_verify_default_key(); ERROR: principal key not configured for current database -- Should fail: no default principal key for the server yet -SELECT key_provider_id, key_provider_name, key_name +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key @@ -26,7 +26,7 @@ SELECT pg_tde_verify_default_key(); (1 row) -SELECT key_provider_id, key_provider_name, key_name +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- @@ -48,7 +48,6 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key - -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index dc01fbed8955a..cc3f2eb153f5d 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -8,7 +8,7 @@ CREATE TABLE country_table ( country_name varchar(32) unique not null, continent varchar(32) not null ) USING tde_heap; - + INSERT INTO country_table (country_name, continent) VALUES ('Japan', 'Asia'), ('UK', 'Europe'), @@ -22,7 +22,7 @@ SELECT pg_tde_is_encrypted('country_table_pkey'); -- Try changing the encrypted table to an unencrypted table ALTER TABLE country_table SET ACCESS METHOD heap; --- Insert some more data +-- Insert some more data INSERT INTO country_table (country_name, continent) VALUES ('France', 'Europe'), ('Germany', 'Europe'), @@ -78,7 +78,7 @@ CREATE TABLE country_table3 ( country_name text unique not null, continent text not null ) USING heap; - + ALTER TABLE country_table SET ACCESS METHOD heap; ALTER TABLE country_table2 SET ACCESS METHOD tde_heap; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 9a100a2a0d3ab..be4183566c7fa 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -7,13 +7,13 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi SELECT pg_tde_verify_default_key(); -- Should fail: no default principal key for the server yet -SELECT key_provider_id, key_provider_name, key_name +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); SELECT pg_tde_verify_default_key(); -SELECT key_provider_id, key_provider_name, key_name +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); -- fails @@ -23,7 +23,7 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - + -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, From 784e45d414c9cb85783519c6f84e4e90b7f5c8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 7 May 2025 10:54:50 +0200 Subject: [PATCH 221/796] Consistently refer to SQL types We had a mix here so I choose some rules that made the smallest diff. - Use uppercase for types - Refer to integer as INT --- contrib/pg_tde/pg_tde--1.0-rc.sql | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 8584090541690..b8d3d4e5c0e3e 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -102,7 +102,7 @@ CREATE FUNCTION pg_tde_list_all_database_key_providers OUT provider_name TEXT, OUT provider_type TEXT, OUT options JSON) -RETURNS SETOF record +RETURNS SETOF RECORD LANGUAGE C STRICT AS 'MODULE_PATHNAME'; @@ -111,7 +111,7 @@ CREATE FUNCTION pg_tde_list_all_global_key_providers OUT provider_name TEXT, OUT provider_type TEXT, OUT options JSON) -RETURNS SETOF record +RETURNS SETOF RECORD LANGUAGE C STRICT AS 'MODULE_PATHNAME'; @@ -397,8 +397,8 @@ BEGIN ATOMIC 'certPath' VALUE kmip_cert_path)); END; -CREATE FUNCTION pg_tde_is_encrypted(relation regclass) -RETURNS boolean +CREATE FUNCTION pg_tde_is_encrypted(relation REGCLASS) +RETURNS BOOLEAN STRICT LANGUAGE C AS 'MODULE_PATHNAME'; @@ -439,26 +439,26 @@ LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_key_info() -RETURNS TABLE ( key_name text, - key_provider_name text, - key_provider_id integer, - key_creation_time timestamp with time zone) +RETURNS TABLE ( key_name TEXT, + key_provider_name TEXT, + key_provider_id INT, + key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_server_key_info() -RETURNS TABLE ( key_name text, - key_provider_name text, - key_provider_id integer, - key_creation_time timestamp with time zone) +RETURNS TABLE ( key_name TEXT, + key_provider_name TEXT, + key_provider_id INT, + key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_default_key_info() -RETURNS TABLE ( key_name text, - key_provider_name text, - key_provider_id integer, - key_creation_time timestamp with time zone) +RETURNS TABLE ( key_name TEXT, + key_provider_name TEXT, + key_provider_id INT, + key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; @@ -476,7 +476,7 @@ CREATE FUNCTION pg_tde_version() RETURNS TEXT LANGUAGE C AS 'MODULE_PATHNAME'; -- Table access method CREATE FUNCTION pg_tdeam_handler(internal) -RETURNS table_am_handler +RETURNS TABLE_AM_HANDLER LANGUAGE C AS 'MODULE_PATHNAME'; @@ -484,12 +484,12 @@ CREATE ACCESS METHOD tde_heap TYPE TABLE HANDLER pg_tdeam_handler; COMMENT ON ACCESS METHOD tde_heap IS 'tde_heap table access method'; CREATE FUNCTION pg_tde_ddl_command_start_capture() -RETURNS event_trigger +RETURNS EVENT_TRIGGER LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_ddl_command_end_capture() -RETURNS event_trigger +RETURNS EVENT_TRIGGER LANGUAGE C AS 'MODULE_PATHNAME'; @@ -518,7 +518,7 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) TO %I', target_role); + EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) TO %I', target_role); END; $$; @@ -549,7 +549,7 @@ LANGUAGE plpgsql SET search_path = @extschema@ AS $$ BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(text, text, BOOLEAN) FROM %I', target_role); + EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) FROM %I', target_role); END; $$; From 34438f5229c0bc8dbb937b0288c765e8436de936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 7 May 2025 13:32:47 +0200 Subject: [PATCH 222/796] Fix some broken key provider test These tests did not assert what they claimed to assert. --- contrib/pg_tde/expected/key_provider.out | 24 ++++++++++++------------ contrib/pg_tde/sql/key_provider.sql | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index cc7b30be22d2b..29a00cd0568c9 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -258,16 +258,16 @@ ERROR: key provider options must be an object -- Modifying key providers fails if an external value object doesn't have all required keys SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); ERROR: external value must contain "type" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": null}}'); -ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": null}}'); +ERROR: external value must contain "type" in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); ERROR: external remote value must contain "url" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); -ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote", "url": null}}'); +ERROR: external remote value must contain "url" in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); ERROR: external file value must contain "path" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); -ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": null}}'); +ERROR: external file value must contain "path" in field "path" -- Modifying key providers fails if values are array instead of scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); ERROR: unexpected array in field "path" @@ -276,12 +276,12 @@ ERROR: unexpected array in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); ERROR: unexpected array in field "path" -- Modifying key providers fails if values are boolean -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": true}'); -ERROR: key provider "provider" does not exists -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": true}}'); -ERROR: key provider "provider" does not exists -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); -ERROR: key provider "provider" does not exists +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": true}'); +ERROR: unexpected boolean in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}'); +ERROR: unexpected boolean in field "path" +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); +ERROR: unexpected boolean in field "path" -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index f3d594a3b9bce..c777d8aaa266a 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -119,11 +119,11 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); -- Modifying key providers fails if an external value object doesn't have all required keys SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": null}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": null}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote", "url": null}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": null}}'); -- Modifying key providers fails if values are array instead of scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); @@ -131,9 +131,9 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); -- Modifying key providers fails if values are boolean -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": true}'); -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": true}}'); -SELECT pg_tde_change_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": true}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}'); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); From 03106ed2ede0ad4da00b165290b2cab3b198686e Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 8 May 2025 13:06:29 +0300 Subject: [PATCH 223/796] Tde documentation updates for ga (#251) Modified the structure and overall presentation for the users to make it more accessible and future-proof as we improve pg_tde moving forward. Split big chapters into smaller chunks to improve SEO, renamed a couple of files for better visibility online and cleaned a bit of text. Integrated changes from: - https://github.com/percona/postgres/pull/314 - https://github.com/percona/postgres/pull/306 - Commit f636e82 --- .../docs/advanced-topics/index.md | 7 + contrib/pg_tde/documentation/docs/apt.md | 39 +++-- .../index.md} | 26 ++-- .../documentation/docs/command-line-tools.md | 60 -------- .../docs/command-line-tools/index.md | 7 + .../pg-tde-change-key-provider.md | 36 +++++ .../command-line-tools/pg-tde-checksums.md | 5 + .../docs/command-line-tools/pg-waldump.md | 14 ++ .../pg_tde/documentation/docs/contribute.md | 2 +- contrib/pg_tde/documentation/docs/faq.md | 42 +++--- contrib/pg_tde/documentation/docs/features.md | 30 ++-- .../pg_tde/documentation/docs/functions.md | 69 ++++----- contrib/pg_tde/documentation/docs/get-help.md | 5 +- .../index.md | 16 ++ .../keyring.md | 27 ++++ .../kmip-server.md | 55 +++++++ .../set-principal-key.md | 37 +++++ .../vault.md | 47 ++++++ .../docs/{ => how-to}/decrypt.md | 30 ++-- .../docs/{ => how-to}/external-parameters.md | 11 +- .../docs/{ => how-to}/multi-tenant-setup.md | 28 ++-- .../docs/{ => how-to}/uninstall.md | 14 +- contrib/pg_tde/documentation/docs/index.md | 50 +----- .../docs/index/how-does-tde-work.md | 27 ++++ .../documentation/docs/index/how-tde-helps.md | 20 +++ .../pg_tde/documentation/docs/index/index.md | 7 + .../docs/index/supported-versions.md | 21 +++ .../docs/{ => index}/table-access-method.md | 32 ++-- .../documentation/docs/index/tde-encrypts.md | 11 ++ .../docs/index/tde-limitations.md | 9 ++ contrib/pg_tde/documentation/docs/install.md | 17 ++- .../docs/release-notes/alpha1.md | 11 +- .../documentation/docs/release-notes/beta.md | 3 +- .../documentation/docs/release-notes/beta2.md | 10 +- .../documentation/docs/release-notes/mvp.md | 4 +- .../documentation/docs/release-notes/rc.md | 77 +++++----- .../documentation/docs/release-notes/rc2.md | 119 +++++++++++++++ .../docs/release-notes/release-notes.md | 6 +- .../pg_tde/documentation/docs/replication.md | 4 + contrib/pg_tde/documentation/docs/setup.md | 142 +++++------------- contrib/pg_tde/documentation/docs/tde.md | 43 ------ contrib/pg_tde/documentation/docs/test.md | 58 ++++--- .../documentation/docs/wal-encryption.md | 44 +++--- contrib/pg_tde/documentation/docs/yum.md | 37 +++-- contrib/pg_tde/documentation/mkdocs.yml | 80 ++++++---- contrib/pg_tde/documentation/requirements.txt | 4 +- .../snippets/kms-considerations.md | 24 +-- contrib/pg_tde/documentation/variables.yml | 1 + 48 files changed, 855 insertions(+), 613 deletions(-) create mode 100644 contrib/pg_tde/documentation/docs/advanced-topics/index.md rename contrib/pg_tde/documentation/docs/{architecture.md => architecture/index.md} (95%) delete mode 100644 contrib/pg_tde/documentation/docs/command-line-tools.md create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/index.md create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-checksums.md create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md rename contrib/pg_tde/documentation/docs/{ => how-to}/decrypt.md (75%) rename contrib/pg_tde/documentation/docs/{ => how-to}/external-parameters.md (82%) rename contrib/pg_tde/documentation/docs/{ => how-to}/multi-tenant-setup.md (93%) rename contrib/pg_tde/documentation/docs/{ => how-to}/uninstall.md (91%) create mode 100644 contrib/pg_tde/documentation/docs/index/how-does-tde-work.md create mode 100644 contrib/pg_tde/documentation/docs/index/how-tde-helps.md create mode 100644 contrib/pg_tde/documentation/docs/index/index.md create mode 100644 contrib/pg_tde/documentation/docs/index/supported-versions.md rename contrib/pg_tde/documentation/docs/{ => index}/table-access-method.md (86%) create mode 100644 contrib/pg_tde/documentation/docs/index/tde-encrypts.md create mode 100644 contrib/pg_tde/documentation/docs/index/tde-limitations.md create mode 100644 contrib/pg_tde/documentation/docs/release-notes/rc2.md create mode 100644 contrib/pg_tde/documentation/docs/replication.md delete mode 100644 contrib/pg_tde/documentation/docs/tde.md diff --git a/contrib/pg_tde/documentation/docs/advanced-topics/index.md b/contrib/pg_tde/documentation/docs/advanced-topics/index.md new file mode 100644 index 0000000000000..9df169821bcd3 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/advanced-topics/index.md @@ -0,0 +1,7 @@ +# Technical Reference + +This section covers the internal components and tools that power `pg_tde`. + +Use it to understand how encryption is implemented, fine-tune a configuration, leverage advanced CLI tools and functions for diagnostics and customization. + +[Architecture](../architecture/index.md){.md-button} [GUC Variables](../variables.md){.md-button} [Functions](../functions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/apt.md b/contrib/pg_tde/documentation/docs/apt.md index 2ecd9eb232f85..0b2414c23569b 100644 --- a/contrib/pg_tde/documentation/docs/apt.md +++ b/contrib/pg_tde/documentation/docs/apt.md @@ -1,55 +1,54 @@ -# Install `pg_tde` on Debian or Ubuntu +# Install pg_tde on Debian or Ubuntu -This tutorial shows how to install `pg_tde` with [Percona Distribution for PostgreSQL :octicons-link-external-16:](https://docs.percona.com/postgresql/latest/index.html). +This page explains how to install `pg_tde` with [Percona Distribution for PostgreSQL :octicons-link-external-16:](https://docs.percona.com/postgresql/latest/index.html). -Check the [list of supported platforms](install.md#__tabbed_1_1). +Check the [list of supported platforms](install.md#__tabbed_1_1) before continuing. ## Preconditions 1. Debian and other systems that use the `apt` package manager include the upstream PostgreSQL server package (`postgresql-{{pgversion17}}`) by default. You need to uninstall this package before you install Percona Server for PostgreSQL and `pg_tde` to avoid conflicts. 2. You need the `percona-release` repository management tool that enables the desired Percona repository for you. - -### Install `percona-release` {.power-number} +### Install percona-release {.power-number} 1. You need the following dependencies to install `percona-release`: - + - `wget` - `gnupg2` - `curl` - `lsb-release` - + Install them with the following command: - + ```{.bash data-prompt="$"} - $ sudo apt-get install -y wget gnupg2 curl lsb-release + $ sudo apt-get install -y wget gnupg2 curl lsb-release ``` - + 2. Fetch the `percona-release` package ```{.bash data-prompt="$"} - $ sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb + $ sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb ``` 3. Install `percona-release` ```{.bash data-prompt="$"} - $ sudo dpkg -i percona-release_latest.generic_all.deb + $ sudo dpkg -i percona-release_latest.generic_all.deb ``` 4. Enable the Percona Distribution for PostgreSQL repository ```{.bash data-prompt="$"} - $ sudo percona-release enable-only ppg-{{pgversion17}} + $ sudo percona-release enable-only ppg-{{pgversion17}} ``` 6. Update the local cache ```bash - sudo apt-get update + sudo apt-get update ``` -## Install `pg_tde` {.power-number} +## Install pg_tde {.power-number} !!! important @@ -60,19 +59,17 @@ Check the [list of supported platforms](install.md#__tabbed_1_1). The use of the `CASCADE` parameter deletes all tables that were created in the database with `pg_tde` enabled and also all dependencies upon the encrypted table (e.g. foreign keys in a non-encrypted table used in the encrypted one). ```sql - DROP EXTENSION pg_tde CASCADE + DROP EXTENSION pg_tde CASCADE; ``` 2. Uninstall the `percona-postgresql-17-pg-tde` package. After all [preconditions](#preconditions) are met, run the following command to install `pg_tde`: - ```{.bash data-prompt="$"} -$ sudo apt-get install -y percona-postgresql-17 + $ sudo apt-get install -y percona-postgresql-17 ``` +## Next steps -## Next step - -[Setup :material-arrow-right:](setup.md){.md-button} +[Configure pg_tde :material-arrow-right:](setup.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/architecture.md b/contrib/pg_tde/documentation/docs/architecture/index.md similarity index 95% rename from contrib/pg_tde/documentation/docs/architecture.md rename to contrib/pg_tde/documentation/docs/architecture/index.md index f7cd70e4805a9..d5ece3f2a8d17 100644 --- a/contrib/pg_tde/documentation/docs/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -12,7 +12,7 @@ Let's break down what it means. * Storing every key on the same key storage, or using different storages for different databases * Handling permissions: who can manage database specific or global permissions, who can create encrypted or not encrypted tables -**Complete** means that `pg_tde` aims to encrypt data at rest. +**Complete** means that `pg_tde` aims to encrypt data at rest. **Data at rest** means everything written to the disk. This includes the following: @@ -35,10 +35,10 @@ The main components of `pg_tde` are the following: [Percona Server for PostgreSQL location](https://github.com/percona/postgres/tree/{{tdebranch}}) * The **`pg_tde` extension itself** implements the encryption code by hooking into the extension points introduced in the core changes, and the already existing extension points in the PostgreSQL server. - + Everything is controllable with GUC variables and SQL statements, similar to other extensions. -* The **keyring API / libraries** implement the key storage logic with different key providers. The API is internal only, the keyring libraries are part of the main library for simplicity. +* The **keyring API / libraries** implement the key storage logic with different key providers. The API is internal only, the keyring libraries are part of the main library for simplicity. In the future these could be extracted into separate shared libraries with an open API, allowing the use of third-party providers. ## Encryption architecture @@ -48,7 +48,7 @@ In the future these could be extracted into separate shared libraries with an op `pg_tde` uses two kinds of keys for encryption: * Internal keys to encrypt the data. They are stored in PostgreSQL's data directory under `$PGDATA/pg_tde``. -* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in a Key Management System (KMS) using the key provider API. +* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in a Key Management System (KMS) using the key provider API. `pg_tde` uses one principal key per database. Every internal key for the given database is encrypted using this principal key. @@ -74,7 +74,7 @@ Support for other cipher lengths / algorithms is planned in the future. `pg_tde` makes it possible to encrypt everything or only some tables in some databases. -To support this without metadata changes, encrypted tables are labeled with a `tde_heap` access method marker. +To support this without metadata changes, encrypted tables are labeled with a `tde_heap` access method marker. The `tde_heap` access method is the same as the `heap` one. It uses the same functions internally without any changes, but with the different name and ID. In such a way `pg_tde` knows that `tde_heap` tables are encrypted and `heap` tables are not. @@ -162,11 +162,11 @@ Retreival of the principal key is cached so it only happens when necessary. ### Key provider management -Key provider configuration or location may change. For example, a service is moved to a new address or the principal key must be moved to a different key provider type. `pg_tde` supports both these scenarios enabling you to manage principal keys using simple [SQL functions](functions.md#key-provider-management). +Key provider configuration or location may change. For example, a service is moved to a new address or the principal key must be moved to a different key provider type. `pg_tde` supports both these scenarios enabling you to manage principal keys using simple [SQL functions](../functions.md#key-provider-management). -In certain cases you can't use SQL functions to manage key providers. For example, if the key provider changed while the server wasn't running and is therefore unaware of these changes. The startup can fail if it needs to access the encryption keys. +In certain cases you can't use SQL functions to manage key providers. For example, if the key provider changed while the server wasn't running and is therefore unaware of these changes. The startup can fail if it needs to access the encryption keys. -For such situations, `pg_tde` also provides [command line tools](command-line-tools.md) to recover the database. +For such situations, `pg_tde` also provides [command line tools](../command-line-tools/index.md) to recover the database. ### Sensitive key provider information @@ -242,7 +242,7 @@ This is also the reason why it requires a `dbOid` instead of a name, as it has n ### Deleting providers -Providers can be deleted by the +Providers can be deleted by using the ```sql pg_tde_delete_database_key_provider(provider_name) @@ -255,7 +255,7 @@ For database specific providers, the function first checks if the provider is us For global providers, the function checks if the provider is used anywhere, WAL or any specific database, and returns an error if it is. -This somewhat goes against the principle that `pg_tde` shouldn't interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. Making this check makes more sense than potentially making some databases inaccessible. +This somewhat goes against the principle that `pg_tde` should not interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. Making this check makes more sense than potentially making some databases inaccessible. ### Listing/querying providers @@ -365,7 +365,7 @@ encryption is managed by the admins, normal users only have to create tables wit 2. `CREATE EXTENSION pg_tde;` in `template1` 3. Adding a global key provider 4. Changing the WAL encryption to use the proper global key provider -5. Giving users that are expected to manage database keys permissions for database specific key management, but not database specific key provider management: +5. Giving users that are expected to manage database keys permissions for database specific key management, but not database specific key provider management: specific databases HAVE to use the global key provider Note: setting the `default_table_access_method` to `tde_heap` is possible, but instead of `ALTER SYSTEM` only per database using `ALTER DATABASE`, after a principal key is configured for that specific database. @@ -376,8 +376,8 @@ Alternatively `ALTER SYSTEM` is possible, but table creation in the database wil 1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` (that's not multi tenant currently) 2. `CREATE EXTENSION pg_tde;` in any database -2. Adding a global key provider for WAL -3. Changing the WAL encryption to use the proper global key provider +3. Adding a global key provider for WAL +4. Changing the WAL encryption to use the proper global key provider No default configuration: key providers / principal keys are configured as a per database level, permissions are managed per database diff --git a/contrib/pg_tde/documentation/docs/command-line-tools.md b/contrib/pg_tde/documentation/docs/command-line-tools.md deleted file mode 100644 index a737505c4861c..0000000000000 --- a/contrib/pg_tde/documentation/docs/command-line-tools.md +++ /dev/null @@ -1,60 +0,0 @@ -# Command line tools - -The `pg_tde` extension provides new command line tools and modifies some existing tools to work with encrypted WAL and tables. - -## pg_tde_change_key_provider - -A tool for modifying the configuration of a key provider, possibly also changing its type. - -This tool edits the configuration files directly, ignoring permissions or running `postgres` processes. - -Its only intended use is to fix servers that can't start up because of inaccessible key providers. - -For example, you restore from an old backup and the address of the key provider changed in the meantime. You can use this tool to correct the configuration, allowing the server to start up. - -Use this tool **only when the server is offline.** To modify the key provider configuration when the server is up and running, use the [`pg_tde_change_(global/database)_key_provider_`](functions.md#change-an-existing-provider) SQL functions. - -### Usage - -To modify the key provider configuration, specify all parameters depending on the provider type in the same way as you do when using the [`pg_tde_change_(global/database)_key_provider_`](functions.md#change-an-existing-provider) SQL functions. - -The general syntax is as follows: - -``` -pg_tde_change_key_provider [-D ] -``` - -where : - -* [optional] `` is the data directory. When not specified, `pg_tde` uses the `$PGDATA` value. -* `` is the name you assigned to the key provider -* `` can be a `file`, `vault` or `kmip`. -* `` - - -Depending on the provider type, the additional parameters are: - -``` -pg_tde_change_key_provider [-D ] file -pg_tde_change_key_provider [-D ] vault [] -pg_tde_change_key_provider [-D ] kmip [] -``` - -## pg_waldump - -[`pg_waldump` :octicons-link-external-16:](https://www.postgresql.org/docs/current/pgwaldump.html) is a tool to display a human-readable rendering of the write-ahead log of a PostgreSQL database cluster. - -To read encrypted WAL records, `pg_waldump` supports the following additional arguments: - -* `keyring_path`: the directory where keyring config files for WAL are stored. These files are: - - * `pg_tde.map`, - * `pg_tde.dat`, - * `pg_tde_keyrings` - -`pg_waldump` will not try to decrypt WAL if the `keyring_path` is not set. - -## pg_checksums - -[`pg_checksums` :octicons-link-external-16:](https://www.postgresql.org/docs/current/app-pgchecksums.html) cannot calculate checksums for encrypted files. -It skips encrypted files and reports this in the output. diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/index.md b/contrib/pg_tde/documentation/docs/command-line-tools/index.md new file mode 100644 index 0000000000000..b854eab1b304a --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/index.md @@ -0,0 +1,7 @@ +# pg_tde CLI Tools + +The `pg_tde` extension introduces new command-line utilities and extends some existing PostgreSQL tools to support encrypted WAL and tables. These include: + +* [pg_tde_change_key_provider](../command-line-tools/pg-tde-change-key-provider.md): change encryption key provider for a database +* [pg_waldump](../command-line-tools/pg-waldump.md): inspect and decrypt WAL files +* [pg_checksums](../command-line-tools/pg-tde-checksums.md): verify data checksums (non-encrypted files only) diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md new file mode 100644 index 0000000000000..a897634c41c37 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md @@ -0,0 +1,36 @@ +# pg_tde_change_key_provider + +A tool for modifying the configuration of a key provider, possibly also changing its type. + +This tool edits the configuration files directly, ignoring permissions or running `postgres` processes. + +Its only intended use is to fix servers that can't start up because of inaccessible key providers. + +For example, you restore from an old backup and the address of the key provider changed in the meantime. You can use this tool to correct the configuration, allowing the server to start up. + +:material-information: Warning: Use this tool **only when the server is offline.** To modify the key provider configuration when the server is up and running, use the [`pg_tde_change_(global/database)_key_provider_`](../functions.md#change-an-existing-provider) SQL functions. + +## Example usage + +To modify the key provider configuration, specify all parameters depending on the provider type in the same way as you do when using the [`pg_tde_change_(global/database)_key_provider_`](../functions.md#change-an-existing-provider) SQL functions. + +The general syntax is as follows: + +```bash +pg_tde_change_key_provider [-D ] +``` + +## Parameter description + +* [optional] `` is the data directory.`pg_tde` uses the `$PGDATA` environment variable if this is not specified +* `` is the name you assigned to the key provider +* `` can be a `file`, `vault` or `kmip` +* `` + +Depending on the provider type, the additional parameters are: + +```bash +pg_tde_change_key_provider [-D ] file +pg_tde_change_key_provider [-D ] vault [] +pg_tde_change_key_provider [-D ] kmip [] +``` diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-checksums.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-checksums.md new file mode 100644 index 0000000000000..391182dcd59c6 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-checksums.md @@ -0,0 +1,5 @@ +# pg_checksums + +[`pg_checksums` :octicons-link-external-16:](https://www.postgresql.org/docs/current/app-pgchecksums.html) is a PostgreSQL command-line utility used to enable, disable, or verify data checksums on a PostgreSQL data directory. However, it cannot calculate checksums for encrypted files. + +Encrypted files are skipped, and this is reported in the output. diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md new file mode 100644 index 0000000000000..6396249342840 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md @@ -0,0 +1,14 @@ +# pg_waldump + +[`pg_waldump` :octicons-link-external-16:](https://www.postgresql.org/docs/current/pgwaldump.html) is a tool to display a human-readable rendering of the Write-Ahead Log (WAL) of a PostgreSQL database cluster. + +To read encrypted WAL records, `pg_waldump` supports the following additional arguments: + +* `keyring_path`: the directory where keyring configuration files for WAL are stored. These files include: + * `pg_tde.map` + * `pg_tde.dat` + * `pg_tde_keyrings` + +!!! note + + `pg_waldump` will not decrypt WAL unless the `keyring_path` is set. diff --git a/contrib/pg_tde/documentation/docs/contribute.md b/contrib/pg_tde/documentation/docs/contribute.md index c728f333f49d0..e626488ea1599 100644 --- a/contrib/pg_tde/documentation/docs/contribute.md +++ b/contrib/pg_tde/documentation/docs/contribute.md @@ -1,4 +1,4 @@ -# Contributing guide +# Contributing Guide Welcome to `pg_tde` - the Transparent Data Encryption extension for PostgreSQL! diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index a4842bdd306cd..902e0a18b49fc 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -2,16 +2,18 @@ ## Why do I need TDE? +Using TDE provides the following benefits: + - Compliance to security and legal regulations like General Data Protection Regulation (GDPR), Payment Card Industry Data Security Standard (PCI DSS), California Consumer Privacy Act (CCPA), Data Protection Act 2018 (DPA 2018) and others - Encryption of backups. Even when an authorized person gets physical access to a backup, encryption ensures that the data remains unreadable and secure. -- Granular encryption of specific data sets and reducing the performance overhead that encryption brings. +- Granular encryption of specific data sets and reducing the performance overhead that encryption brings. - Additional layer of security to existing security measures ## When and how should I use TDE? If you are dealing with Personally Identifiable Information (PII), data encryption is crucial. Especially if you are involved in areas with strict regulations like: -* financial services where TDE helps to comply with PCI DSS +* financial services where TDE helps to comply with PCI DSS * healthcare and insurance - compliance with HIPAA, HITECH, CCPA * telecommunications, government and education to ensure data confidentiality. @@ -21,18 +23,17 @@ Using TDE helps you avoid the following risks: * Identity theft that may lead to financial fraud and other crimes * Reputation damage leading to loss of customer trust and business * Legal consequences and financial losses for non-compliance with data protection regulations -* Internal threats by misusing unencrypted sensitive data +* Internal threats by misusing unencrypted sensitive data If to translate sensitive data to files stored in your database, these are user data in tables, temporary files, WAL files. TDE has you covered encrypting all these files. -`pg_tde` does not encrypt system catalogs yet. This means that statistics data and database metadata are not encrypted. The encryption of system catalogs is planned for future releases. - +`pg_tde` does not encrypt system catalogs yet. This means that statistics data and database metadata are not encrypted. The encryption of system catalogs is planned for future releases. ## I use disk-level encryption. Why should I care about TDE? Encrypting a hard drive encrypts all data, including system, application, and temporary files. -Full disk encryption protects your data from people who have physical access to your device and even if it is lost or stolen. However, it doesn't protect the data after system boot-up: the data is automatically decrypted when the system runs or when an authorized user requests it. +Full disk encryption protects your data from people who have physical access to your device and even if it is lost or stolen. However, it doesn't protect the data after system boot-up: the data is automatically decrypted when the system runs or when an authorized user requests it. Another point to consider is PCI DSS compliance for Personal Account Numbers (PAN) encryption. @@ -44,7 +45,7 @@ Another point to consider is PCI DSS compliance for Personal Account Numbers (PA Note that PCI DSS 3.4.1 is retiring on March 31, 2025. Therefore, consider switching to PCI DSS 4.0. -* **PCI DSS 4.0** standards consider using only disk and partition-level encryption not enough to ensure PAN protection. It requires an additional layer of security that `pg_tde` can provide. +* **PCI DSS 4.0** standards consider using only disk and partition-level encryption not enough to ensure PAN protection. It requires an additional layer of security that `pg_tde` can provide. `pg_tde` focuses specifically on data files and offers more granular control over encrypted data. The data remains encrypted on disk during runtime and when you move it to another directory, another system or storage. An example of such data is backups. They remain encrypted when moved to the backup storage. @@ -52,7 +53,7 @@ Thus, to protect your sensitive data, consider using TDE to encrypt it at the ta ## Is TDE enough to ensure data security? -No. TDE is an additional layer to ensure data security. It protects data at rest. Consider introducing also these measures: +**No.** Transparent Data Encryption (TDE) adds an extra layer of security for data at rest. You should also consider implementing the following additional security features: * Access control and authentication * Strong network security like TLS @@ -65,29 +66,29 @@ No. TDE is an additional layer to ensure data security. It protects data at rest `pg_tde` uses two keys to encrypt data: * Internal encryption keys to encrypt the data. These keys are stored internally in an encrypted format, in a single `$PGDATA/pg_tde` directory. -* Principal keys to encrypt internal encryption keys. These keys are stored externally, in the Key Management System (KMS). +* Principal keys to encrypt internal encryption keys. These keys are stored externally, in the Key Management System (KMS). You can use the following KMSs: -* [HashiCorp Vault](https://developer.hashicorp.com/vault/docs/what-is-vault). `pg_tde` supports the KV secrets engine v2 of Vault. +* [HashiCorp Vault](https://developer.hashicorp.com/vault/docs/what-is-vault). `pg_tde` supports the KV secrets engine v2 of Vault. * [OpenBao](https://openbao.org/) implementation of Vault * KMIP-compatible server. KMIP is a standardized protocol for handling cryptographic workloads and secrets management -HashiCorp Vault can also act as the KMIP server, managing cryptographic keys for clients that use the KMIP protocol. +HashiCorp Vault can also act as the KMIP server, managing cryptographic keys for clients that use the KMIP protocol. Let's break the encryption into two parts: -### Encryption of data files +### Encryption of data files -First, data files are encrypted with internal keys. Each file that has a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) has an internal key. For example, a table with 4 indexes will have 5 internal keys - one for the table and one for each index. +First, data files are encrypted with internal keys. Each file that has a different [Object Identifier (OID)](https://www.postgresql.org/docs/current/datatype-oid.html) has an internal key. For example, a table with 4 indexes will have 5 internal keys - one for the table and one for each index. -The initial decision on what file to encrypt is based on the table access method in PostgreSQL. When you run a `CREATE` or `ALTER TABLE` statement with the `USING tde_heap` clause, the newly created data files are marked as encrypted, and then file operations encrypt or decrypt the data. Later, if an initial file is re-created as a result of a `TRUNCATE` or `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. +The initial decision on what file to encrypt is based on the table access method in PostgreSQL. When you run a `CREATE` or `ALTER TABLE` statement with the `USING tde_heap` clause, the newly created data files are marked as encrypted, and then file operations encrypt or decrypt the data. Later, if an initial file is re-created as a result of a `TRUNCATE` or `VACUUM FULL` command, the newly created file inherits the encryption information and is either encrypted or not. The principal key is used to encrypt the internal keys. The principal key is stored in the key management store. When you query the table, the principal key is retrieved from the key store to decrypt the table. Then the internal key for that table is used to decrypt the data. ### WAL encryption -WAL encryption is done globally for the entire database cluster. All modifications to any database within a PostgreSQL cluster are written to the same WAL to maintain data consistency and integrity and ensure that PostgreSQL cluster can be restored to a consistent state. Therefore, WAL is encrypted globally. +WAL encryption is done globally for the entire database cluster. All modifications to any database within a PostgreSQL cluster are written to the same WAL to maintain data consistency and integrity and ensure that PostgreSQL cluster can be restored to a consistent state. Therefore, WAL is encrypted globally. When you turn on WAL encryption, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. @@ -97,12 +98,11 @@ You can turn WAL encryption on and off so WAL can contain both encrypted and une Whenever the WAL is being read (by the recovery process or tools), the decision on what should be decrypted is based solely on the metadata of WAL encryption keys. - ## Should I encrypt all my data? -It depends on your business requirements and the sensitivity of your data. Encrypting all data is a good practice but it can have a performance impact. +It depends on your business requirements and the sensitivity of your data. Encrypting all data is a good practice but it can have a performance impact. -Consider encrypting only tables that store sensitive data. You can decide what tables to encrypt and with what key. The [Set up multi-tenancy](multi-tenant-setup.md) section in the documentation focuses on this approach. +Consider encrypting only tables that store sensitive data. You can decide what tables to encrypt and with what key. The [Set up multi-tenancy](how-to/multi-tenant-setup.md) section in the documentation focuses on this approach. We advise encrypting the whole database only if all your data is sensitive, like PII, or if there is no other way to comply with data safety requirements. @@ -122,7 +122,7 @@ No, it's not yet supported. In our implementation we reply on OpenSSL libraries Yes, you can encrypt an existing table. Run the `ALTER TABLE` command as follows: -``` +```sql ALTER TABLE table_name SET ACCESS METHOD tde_heap; ``` @@ -135,7 +135,7 @@ You must restart the database in the following cases to apply the changes: * after you enabled the `pg_tde` extension * to turn on / off the WAL encryption -After that, no database restart is required. When you create or alter the table using the `tde_heap` access method, the files are marked as those that require encryption. The encryption happens at the storage manager level, before a transaction is written to disk. Read more about [how tde_heap works](table-access-method.md#how-tde_heap-works). +After that, no database restart is required. When you create or alter the table using the `tde_heap` access method, the files are marked as those that require encryption. The encryption happens at the storage manager level, before a transaction is written to disk. Read more about [how tde_heap works](index/table-access-method.md#how-tde_heap-works). ## What happens to my data if I lose a principal key? @@ -143,7 +143,7 @@ If you lose encryption keys, especially, the principal key, the data is lost. Th ## Can I use `pg_tde` in a multi-tenant setup? -Multi-tenancy is the type of architecture where multiple users, or tenants, share the same resource. It can be a database, a schema or an entire cluster. +Multi-tenancy is the type of architecture where multiple users, or tenants, share the same resource. It can be a database, a schema or an entire cluster. In `pg_tde`, multi-tenancy is supported via a separate principal key per database. This means that a database owner can decide what tables to encrypt within a database. The same database can have both encrypted and non-encrypted tables. diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index 3548c6deb22df..8edd99fbc991c 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -1,20 +1,24 @@ # Features -We provide `pg_tde` in two versions for both PostgreSQL Community and [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/). The difference between the versions is in the set of included features which in its turn depends on the Storage Manager API. While PostgreSQL Community uses the default Storage Manager API, Percona Server for PostgreSQL extends the Storage Manager API enabling to integrate custom storage managers. +`pg_tde` is available for [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/) +The Percona Server for PostgreSQL provides an extended Storage Manager API that allows integration with custom storage managers. -The following table provides features available for each version: +The following features are available for the extension: -| Percona Server for PostgreSQL version | PostgreSQL Community version (deprecated) | -|-------------------------------|----------------------| -| Table encryption:
- data tables,
- **Index data for encrypted tables**,
- TOAST tables,
- temporary tables created during the database operation.

Metadata of those tables is not encrypted. | Table encryption:
- data tables,
- TOAST tables
- temporary tables created during the database operation.

Metadata of those tables is not encrypted. | -| **Global** Write-Ahead Log (WAL) encryption: for data in encrypted and non-encrypted tables | Write-Ahead Log (WAL) encryption of data in encrypted tables | -| Single-tenancy support via global keyring provider | | -| Multi-tenancy support | Multi-tenancy support | -| Table-level granularity | Table-level granularity | -| Key management via:
- HashiCorp Vault;
- KMIP server;
- Local keyfile | Key management via:
- HashiCorp Vault;
- Local keyfile | -| Logical replication of encrypted tables | | +* Table encryption, including: + * Data tables + * Index data for encrypted tables + * TOAST tables + * Temporary tables created during database operations +!!! note + Metadata of those tables is not encrypted. +* Global Write-Ahead Log (WAL) encryption for data in both encrypted and non-encrypted tables +* Single-tenancy support via a global keyring provider +* Multi-tenancy support +* Table-level granularity for encryption and access control +* Multiple Key management options +* Logical replication of encrypted tables - -[Get started](install.md){.md-button} \ No newline at end of file +[Overview](index/index.md){.md-button} [Get started](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 45463fb2b12ff..f7bc4647b8990 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -36,16 +36,16 @@ A key provider is a system or service responsible for managing encryption keys. Key provider management includes the following operations: -* creating a new key provider, -* changing an existing key provider, -* deleting a key provider, +* creating a new key provider, +* changing an existing key provider, +* deleting a key provider, * listing key providers. ### Add a provider You can add a new key provider using the provided functions, which are implemented for each provider type. -There are two functions to add a key provider: one function adds it for the current database and another one - for the global scope. +There are two functions to add a key provider: one function adds it for the current database and another one - for the global scope. * `pg_tde_add_database_key_provider_('provider-name', )` * `pg_tde_add_global_key_provider_('provider-name', )` @@ -75,14 +75,14 @@ The Vault provider connects to a HashiCorp Vault or an OpenBao server, and store Use the following functions to add the Vault provider: -``` +```sql SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); SELECT pg_tde_add_global_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` These functions change the Vault provider: -``` +```sql SELECT pg_tde_change_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); SELECT pg_tde_change_global_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); ``` @@ -95,25 +95,24 @@ where: * `secret_token` is an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification -All parameters can be either strings, or JSON objects [referencing remote parameters](external-parameters.md). +All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). **Never specify the secret token directly, use a remote parameter instead.** - #### Adding or modifying KMIP providers The KMIP provider uses a remote KMIP server. -Use these functions to add a KMIP provider: +Use these functions to add a KMIP provider: -``` +```sql SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); SELECT pg_tde_add_global_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` These functions change the KMIP provider: -``` +```sql SELECT pg_tde_change_database_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); SELECT pg_tde_change_global_key_provider_kmip('provider-name','kmip-addr', `port`, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` @@ -129,7 +128,7 @@ where: The specified access parameters require permission to read and write keys at the server. -All parameters can be either strings, or JSON objects [referencing remote parameters](external-parameters.md). +All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). ### Adding or modifying local keyfile providers @@ -141,14 +140,14 @@ This function is intended for development or quick testing, and stores the keys Add a local keyfile provider: -``` +```sql SELECT pg_tde_add_database_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); SELECT pg_tde_add_global_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); ``` Change a local keyfile provider: -``` +```sql SELECT pg_tde_change_database_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); SELECT pg_tde_change_global_key_provider_file('provider-name','/path/to/the/key/provider/data.file'); ``` @@ -158,7 +157,7 @@ where: * `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider. * `/path/to/the/key/provider/data.file` is the path to the key provider file. -All parameters can be either strings, or JSON objects [referencing remote parameters](external-parameters.md). +All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). ### Delete a provider @@ -180,7 +179,6 @@ These functions list the details of all key providers for the current database o **All configuration values include possibly sensitive values, such as passwords. Never specify these directly, use the remote configuration option instead.** - ## Principal key management Use these functions to create a new principal key for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. @@ -191,7 +189,7 @@ Princial keys are stored on key providers by the name specified in this function Creates or rotates the principal key for the current database using the specified database key provider and key name. -``` +```sql SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` @@ -205,30 +203,29 @@ SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key','provider-na Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. -``` -SELECT pg_tde_set_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); +```sql + SELECT pg_tde_set_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: -* If set to `true` (default), a new key must be unique. +* If set to `true`, a new key must be unique. If the provider already stores a key by that name, the function returns an error. -* If set to `false`, an existing principal key may be reused. +* If set to `false` (default), an existing principal key may be reused. ### pg_tde_set_server_key_using_global_key_provider Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. -``` +```sql SELECT pg_tde_set_server_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: -* If set to `true` (default), a new key must be unique. +* If set to `true`, a new key must be unique. If the provider already stores a key by that name, the function returns an error. -* If set to `false`, an existing principal key may be reused. - +* If set to `false` (default), an existing principal key may be reused. ### pg_tde_set_default_key_using_global_key_provider @@ -236,15 +233,15 @@ Creates or rotates the default principal key for the server using the specified The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. -``` +```sql SELECT pg_tde_set_default_key_using_global_key_provider('name-of-the-key','provider-name','ensure_new_key'); ``` The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: -* If set to `true` (default), a new key must be unique. +* If set to `true`, a new key must be unique. If the provider already stores a key by that name, the function returns an error. -* If set to `false`, an existing principal key may be reused. +* If set to `false` (default), an existing principal key may be reused. ## Encryption status check @@ -256,13 +253,13 @@ tables and indexes. To verify that a table is encrypted, run the following statement: -``` +```sql SELECT pg_tde_is_encrypted('table_name'); ``` You can also verify if the table in a custom schema is encrypted. Pass the schema name for the function as follows: -``` +```sql SELECT pg_tde_is_encrypted('schema.table_name'); ``` @@ -272,7 +269,7 @@ This can additionally be used to verify that indexes and sequences are encrypted Displays information about the principal key for the current database, if it exists. -``` +```sql SELECT pg_tde_key_info() ``` @@ -280,7 +277,7 @@ SELECT pg_tde_key_info() Displays information about the principal key for the server scope, if exists. -``` +```sql SELECT pg_tde_server_key_info() ``` @@ -288,7 +285,7 @@ SELECT pg_tde_server_key_info() Displays the information about the default principal key, if it exists. -``` +```sql SELECT pg_tde_default_key_info() ``` @@ -304,7 +301,7 @@ This function checks that the current database has a properly functional encrypt If any of the above checks fail, the function reports an error. -``` +```sql SELECT pg_tde_verify_key() ``` @@ -320,7 +317,7 @@ This function checks that the server scope has a properly functional encryption If any of the above checks fail, the function reports an error. -``` +```sql SELECT pg_tde_verify_server_key() ``` @@ -336,6 +333,6 @@ This function checks that the default key is properly configured, which means: If any of the above checks fail, the function reports an error. -``` +```sql SELECT pg_tde_verify_default_key() ``` diff --git a/contrib/pg_tde/documentation/docs/get-help.md b/contrib/pg_tde/documentation/docs/get-help.md index 81ee0d8b001e9..56bbac165e47a 100644 --- a/contrib/pg_tde/documentation/docs/get-help.md +++ b/contrib/pg_tde/documentation/docs/get-help.md @@ -1,4 +1,4 @@ -# Get help from Percona +# Get Help from Percona Our documentation guides are packed with information, but they can't cover everything you need to know about `pg_tde`. They also won't cover every scenario you might come across. Don't be afraid to try things out and ask questions when you get stuck. @@ -17,7 +17,6 @@ Percona experts bring years of experience in tackling tough database performance
We understand your challenges when managing complex database environments. That's why we offer various services to help you simplify your operations and achieve your goals. - | Service | Description | |----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 24/7 Expert Support | Our dedicated team of database experts is available 24/7 to assist you with any database issues. We provide flexible support plans tailored to your specific needs. | @@ -25,4 +24,4 @@ We understand your challenges when managing complex database environments. That' | Expert Consulting | Our experienced consultants provide guidance on database topics like architecture design, migration planning, performance optimization, and security best practices. | | Comprehensive Training | Our training programs help your team develop skills to manage databases effectively, offering virtual and in-person courses. | -We're here to help you every step of the way. Whether you need a quick fix or a long-term partnership, we're ready to provide your expertise and support. \ No newline at end of file +We're here to help you every step of the way. Whether you need a quick fix or a long-term partnership, we're ready to provide your expertise and support. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md new file mode 100644 index 0000000000000..f8fbf49d5ab29 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md @@ -0,0 +1,16 @@ +# Configure Key Management (KMS) + +In production environments, storing encryption keys locally on the PostgreSQL server can introduce security risks. To enhance security, `pg_tde` supports integration with external Key Management Systems (KMS) through a Global Key Provider interface. + +This section describes how you can configure `pg_tde` to use the local and external key providers. +To use an external KMS with `pg_tde`, follow these two steps: + +1. Configure a Key Provider +2. Set the [Global Principal Key](set-principal-key.md) + +!!! note + While keyfiles may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. + +Select your prefered configuration from the links below: + +[KMIP Configuration :material-arrow-right:](kmip-server.md){.md-button} [Vault Configuration :material-arrow-right:](vault.md){.md-button} [Keyring File Configuration (not recommended) :material-arrow-right:](keyring.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md new file mode 100644 index 0000000000000..d36f915f713fd --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md @@ -0,0 +1,27 @@ +# Keyring File Configuration + +This setup is intended for development and stores the keys unencrypted in the specified data file. See [how to use external reference to parameters](../how-to/external-parameters.md) to add an extra security layer to your setup. + +!!! note + + While keyfiles may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. + +```sql + SELECT pg_tde_add_global_key_provider_file( + 'provider-name', + '/path/to/the/keyring/data.file' + ); +``` + +The following example is used for testing purposes only: + +```sql + SELECT pg_tde_add_global_key_provider_file( + 'file-keyring', + '/tmp/pg_tde_test_local_keyring.per' + ); +``` + +## Next steps + +[Global Principal Key Configuration :material-arrow-right:](set-principal-key.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md new file mode 100644 index 0000000000000..3127f89a29267 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md @@ -0,0 +1,55 @@ +# KMIP Configuration + +To use a Key Management Interoperability Protocol (KMIP) server with `pg_tde`, you must configure it as a global key provider. This setup enables `pg_tde` to securely fetch and manage encryption keys from a centralized key management appliance. + +!!! note + + You need the root certificate of the KMIP server and a client key/certificate pair with permissions to create and read keys on the server. + +It is recommended to review the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine) if you're using Vault. + +For testing purposes, you can use a lightweight PyKMIP server, which enables easy certificate generation and basic KMIP behavior. If you're using a production-grade KMIP server, ensure you obtain valid, trusted certificates from the key management appliance. + +## Example usage + + ```sql + SELECT pg_tde_add_global_key_provider_kmip( + 'provider-name', + 'kmip-IP', + 5696, + '/path_to/server_certificate.pem', + '/path_to/client_cert.pem', + '/path_to/client_key.pem' + ); + ``` + +## Parameter descriptions + +* `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider +* `kmip-IP` is the IP address of a domain name of the KMIP server +* `port` is the port to communicate with the KMIP server. Typically used port is 5696 +* `server-certificate` is the path to the certificate file for the KMIP server +* `client_cert` is the path to the client certificate. +* `client_key` (optional) is the path to the client key. If not specified, the certificate key has to contain both the certifcate and the key. + +:material-information: Warning: `pg_tde_add_global_key_provider_kmip` currently accepts only a combined client key and a client certificate for its final parameter, reffered to as `client key`. + +The following example is for testing purposes only. + + ```sql + SELECT pg_tde_add_global_key_provider_kmip( + 'kmip','127.0.0.1', + 5696, + '/tmp/server_certificate.pem', + '/tmp/client_cert_jane_doe.pem', + '/tmp/client_key_jane_doe.pem' + ); + ``` + +For more information on related functions, see the link below: + +[Percona pg_tde function reference](../functions.md){.md-button} + +## Next steps + +[Global Principal Key Configuration :material-arrow-right:](set-principal-key.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md new file mode 100644 index 0000000000000..8a56645595510 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -0,0 +1,37 @@ +# Global Principal Key Configuration + +You can configure a default principal key using a global key provider. This key will be used by all databases that do not have their own encryption keys configured. + +## Create a default principal key + +Run the following command: + +```sql + SELECT pg_tde_set_default_key_using_global_key_provider( + 'name-of-the-key', + 'provider-name', + 'ensure_new_key' + ); +``` + +## Parameter description + +* `name-of-the-key` is the name of the principal key. You will use this name to identify the key. +* `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. +* `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. + +This example is for testing purposes only. Replace the key name and provider name with your values: + +```sql + SELECT pg_tde_set_key_using_global_key_provider('test-db-master-key','file-vault','ensure_new_key'); +``` + +!!! note + + The key is auto-generated. + +After this, all databases that do not have something else configured will use this newly generated principal key. + +## Next steps + +[Validate Encryption with pg_tde :material-arrow-right:](../test.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md new file mode 100644 index 0000000000000..f6877bf02ed70 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -0,0 +1,47 @@ +# Vault Configuration + +You can configure `pg_tde` to use HashiCorp Vault as a global key provider for managing encryption keys securely. + +!!! note + + This guide assumes that your Vault server is already set up and accessible. Vault configuration is outside the scope of this document, see [Vault's official documentation](https://developer.hashicorp.com/vault/docs) for more information. + +## Example usage + +```sql + SELECT pg_tde_add_global_key_provider_vault_v2( + 'provider-name', + 'secret_token', + 'url', + 'mount', + 'ca_path' + ); +``` + +## Parameter descriptions + +* `provider-name` is the name to identify this key provider +* `secret_token` is an access token with read and write access to the above mount point +* `url` is the URL of the Vault server +* `mount` is the mount point where the keyring should store the keys +* [optional] `ca_path` is the path of the CA file used for SSL verification + +The following example is for testing purposes only. Use secure tokens and proper SSL validation in production environments: + +```sql + SELECT pg_tde_add_global_key_provider_vault_v2( + 'my-vault', + 'hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', + 'http://vault.vault.svc.cluster.local:8200', + 'secret/data', + NULL + ); +``` + +For more information on related functions, see the link below: + +[Percona pg_tde function reference](../functions.md){.md-button} + +## Next steps + +[Global Principal Key Configuration :material-arrow-right:](set-principal-key.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/decrypt.md b/contrib/pg_tde/documentation/docs/how-to/decrypt.md similarity index 75% rename from contrib/pg_tde/documentation/docs/decrypt.md rename to contrib/pg_tde/documentation/docs/how-to/decrypt.md index 1aafcca361e35..21e9d10fd28bb 100644 --- a/contrib/pg_tde/documentation/docs/decrypt.md +++ b/contrib/pg_tde/documentation/docs/how-to/decrypt.md @@ -1,42 +1,40 @@ -# Remove encryption from an encrypted table +# Remove Encryption from an Encrypted Table ## Method 1. Change the access method If you encrypted a table with the `tde_heap` access method and need to remove the encryption from it, run the following command against the desired table (`mytable` in the example below): -``` -ALTER TABLE mytable SET ACCESS METHOD heap; +```sql + ALTER TABLE mytable SET ACCESS METHOD heap; ``` Note that the `SET ACCESS METHOD` command drops hint bits and this may affect performance. Running a plain `SELECT count(*)` or `VACUUM` command on the entire table will check every tuple for visibility and set its hint bits. Therefore, after executing the `ALTER TABLE` command, run a simple `count(*)` on your tables: -``` -SELECT count(*) FROM mytable; +```sql + SELECT count(*) FROM mytable; ``` Check that the table is not encrypted: +```sql + SELECT pg_tde_is_encrypted('mytable'); ``` -SELECT pg_tde_is_encrypted('mytable'); -``` - -The output returns `f` meaning that the table is no longer encrypted. - +The output returns `f` meaning that the table is no longer encrypted. ## Method 2. Create a new not encrypted table on the base of the encrypted one -Alternatively, you can create a new not encrypted table with the same structure and data as the initial table. For example, the original encrypted table is `EncryptedCustomers`. Use the following command to create a new table `Customers`: +Alternatively, you can create a new not encrypted table with the same structure and data as the initial table. For example, the original encrypted table is `EncryptedCustomers`. Use the following command to create a new table `Customers`: -``` -CREATE TABLE Customers AS -SELECT * FROM EncryptedCustomers; +```sql + CREATE TABLE Customers AS + SELECT * FROM EncryptedCustomers; ``` The new table `Customers` inherits the structure and the data from `EncryptedCustomers`. (Optional) If you no longer need the `EncryptedCustomers` table, you can delete it. -``` -DROP TABLE EncryptedCustomers; +```sql + DROP TABLE EncryptedCustomers; ``` diff --git a/contrib/pg_tde/documentation/docs/external-parameters.md b/contrib/pg_tde/documentation/docs/how-to/external-parameters.md similarity index 82% rename from contrib/pg_tde/documentation/docs/external-parameters.md rename to contrib/pg_tde/documentation/docs/how-to/external-parameters.md index f68aee653e011..86334dfb6afcc 100644 --- a/contrib/pg_tde/documentation/docs/external-parameters.md +++ b/contrib/pg_tde/documentation/docs/how-to/external-parameters.md @@ -1,9 +1,9 @@ -# Use external reference to parameters +# Use External Reference to Parameters To allow storing secrets or any other parameters in a more secure, external location, `pg_tde` allows users to specify an external reference instead of hardcoded parameters. -In Alpha1 version, `pg_tde` supports the following external storage methods: +In the Alpha1 version, `pg_tde` supports the following external storage methods: * `file`, which just stores the data in a simple file specified by a `path`. The file should be readable to the postgres process. @@ -14,7 +14,7 @@ readable to the postgres process. To use the file provider with a file location specified by the `remote` method, use the following command: -``` +```sql SELECT pg_tde_add_database_key_provider_file( 'file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ) @@ -23,12 +23,11 @@ SELECT pg_tde_add_database_key_provider_file( Or to use the `file` method, use the following command: -``` +```sql SELECT pg_tde_add_database_key_provider_file( 'file-provider', json_object( 'type' VALUE 'remote', 'path' VALUE '/tmp/datafile-location' ) );" ``` -Any parameter specified to the `add_key_provider` function can be a `json_object` instead of the string, -similar to the above examples. \ No newline at end of file +Any parameter specified to the `add_key_provider` function can be a `json_object` instead of the string, similar to the above examples. diff --git a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md similarity index 93% rename from contrib/pg_tde/documentation/docs/multi-tenant-setup.md rename to contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 5a6558208da43..abcf3c4779144 100644 --- a/contrib/pg_tde/documentation/docs/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -1,10 +1,10 @@ -# Set up multi-tenancy +# Configure Multi-tenancy The steps below describe how to set up multi-tenancy with `pg_tde`. Multi-tenancy allows you to encrypt different databases with different keys. This provides granular control over data and enables you to introduce different security policies and access controls for each database so that only authorized users of specific databases have access to the data. -If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Setup](setup.md) section. +If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Setup](../setup.md) section. -For how to enable WAL encryption, refer to the [WAL encryption](wal-encryption.md) section. +For how to enable WAL encryption, refer to the [WAL encryption](../wal-encryption.md) section. --8<-- "kms-considerations.md" @@ -12,7 +12,7 @@ For how to enable WAL encryption, refer to the [WAL encryption](wal-encryption.m Load the `pg_tde` at startup time. The extension requires additional shared memory; therefore, add the `pg_tde` value for the `shared_preload_libraries` parameter and restart the `postgresql` cluster. -1. Use the [ALTER SYSTEM :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-altersystem.html) command from `psql` terminal to modify the `shared_preload_libraries` parameter. This requires superuser privileges. +1. Use the [ALTER SYSTEM :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-altersystem.html) command from `psql` terminal to modify the `shared_preload_libraries` parameter. This requires superuser privileges. ``` ALTER SYSTEM SET shared_preload_libraries = 'pg_tde'; @@ -20,12 +20,12 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo 2. Start or restart the `postgresql` cluster to apply the changes. - * On Debian and Ubuntu: + * On Debian and Ubuntu: ```sh sudo systemctl restart postgresql-17 ``` - + * On RHEL and derivatives ```sh @@ -37,8 +37,8 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo ``` CREATE EXTENSION pg_tde; ``` - - The `pg_tde` extension is created for the currently used database. To enable data encryption in other databases, you must explicitly run the `CREATE EXTENSION` command against them. + + The `pg_tde` extension is created for the currently used database. To enable data encryption in other databases, you must explicitly run the `CREATE EXTENSION` command against them. !!! tip @@ -60,7 +60,7 @@ You must do these steps for every database where you have created the extension. For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. - ``` + ```sql SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/server_certificate.pem', '/path_to/client_key.pem'); ``` @@ -99,9 +99,9 @@ You must do these steps for every database where you have created the extension. SELECT pg_tde_add_database_key_provider_file_vault_v2('my-vault','http://vault.vault.svc.cluster.local:8200,'secret/data','hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', NULL); ``` - === "With a keyring file" + === "With a keyring file (not recommended)" - This setup is intended for development and stores the keys unencrypted in the specified data file. + This setup is intended for development and stores the keys unencrypted in the specified data file. ```sql SELECT pg_tde_add_database_key_provider_file('provider-name','/path/to/the/keyring/data.file'); @@ -112,8 +112,7 @@ You must do these steps for every database where you have created the extension. ```sql SELECT pg_tde_add_database_key_provider_file('file-keyring','/tmp/pg_tde_test_local_keyring.per'); ``` - - + 2. Add a principal key ```sql @@ -134,5 +133,4 @@ You must do these steps for every database where you have created the extension. The key is auto-generated. - - :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. \ No newline at end of file + :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. diff --git a/contrib/pg_tde/documentation/docs/uninstall.md b/contrib/pg_tde/documentation/docs/how-to/uninstall.md similarity index 91% rename from contrib/pg_tde/documentation/docs/uninstall.md rename to contrib/pg_tde/documentation/docs/how-to/uninstall.md index 3005d249416e2..7901f778fd8f4 100644 --- a/contrib/pg_tde/documentation/docs/uninstall.md +++ b/contrib/pg_tde/documentation/docs/how-to/uninstall.md @@ -1,4 +1,4 @@ -# Uninstall `pg_tde` +# Uninstall pg_tde If you no longer wish to use TDE in your deployment, you can remove the `pg_tde` extension. To do so, your user must have the superuser privileges, or a database owner privileges in case you only want to remove it from a single database. @@ -6,13 +6,13 @@ Here's how to do it: 1. Drop the extension using the `DROP EXTENSION` command: - ``` + ```sql DROP EXTENSION pg_tde; ``` - This command will fail if there are still encrypted tables in the database. + This command will fail if there are still encrypted tables in the database. - In this case, you must drop the dependent objects manually. Alternatively, you can run the `DROP EXTENSION ... CASCADE` command to drop all dependent objects automatically. + In this case, you must drop the dependent objects manually. Alternatively, you can run the `DROP EXTENSION ... CASCADE` command to drop all dependent objects automatically. Note that the `DROP EXTENSION` command does not delete the `pg_tde` data files related to the database. @@ -22,7 +22,7 @@ Here's how to do it: 4. Modify the `shared_preload_libraries` and remove the 'pg_tde' from it. Use the `ALTER SYSTEM` command for this purpose, or edit the configuration file. - !!! warning + !!! warning Once `pg_tde` is removed from the `shared_preload_libraries`, reading any leftover encrypted files will fail. Removing the extension from the `shared_preload_libraries` is also possible if the extension is still installed in some databases. @@ -30,12 +30,12 @@ Here's how to do it: 5. Start or restart the `postgresql` cluster to apply the changes. - * On Debian and Ubuntu: + * On Debian and Ubuntu: ```sh sudo systemctl restart postgresql ``` - + * On RHEL and derivatives ```sh diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 4bf8a3b9e0e7a..b2a0d1a2cdfa3 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -1,51 +1,11 @@ -# `pg_tde` documentation +# `pg_tde` Documentation -`pg_tde` is the open source PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. This ensures that the data stored on disk is encrypted, and no one can read it without the proper encryption keys, even if they gain access to the physical storage media. +`pg_tde` is the open source, community driven and futureproof PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. `pg_tde` ensures that the data stored on disk is encrypted, and that no one can read it without the proper encryption keys, even if they gain access to the physical storage media. -Learn more [what Transparent Data Encryption is](tde.md#how-does-it-work) and [why you need it](tde.md#why-do-you-need-tde). - -!!! important +!!! important This is the {{release}} version of the extension and it is not meant for production use yet. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). - + +[What is Transparent Data Encryption (TDE)?](index/index.md){.md-button} [Get started](install.md){.md-button} [What's new in pg_tde {{release}}](release-notes/release-notes.md){.md-button} - -## What's encrypted: - -* User data in tables, including TOAST tables, that are created using the extension. Metadata of those tables is not encrypted. -* Temporary tables created during the database operation for data tables created using the extension -* Write-Ahead Log (WAL) data for the entire database cluster. This includes WAL data in encrypted and non-encrypted tables -* Indexes on encrypted tables -* Logical replication on encrypted tables - -[Check the full feature list](features.md){.md-button} - -## Known limitations - -* Keys in the local keyfile are stored unencrypted. For better security we recommend using the Key management storage. -* System tables are currently not encrypted. This means that statistics data and database metadata are currently not encrypted. - -* `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. -* `pb_tde` Release candidate is incompatible with `pg_tde`Beta2 due to significant changes in code. There is no direct upgrade flow from one version to another. You must [uninstall](uninstall.md) `pg_tde` Beta2 first and then [install](install.md) and configure the new Release Candidate version. - - - -## Versions and supported PostgreSQL deployments - -The `pg_tde` extension comes in two distinct versions with specific access methods to encrypt the data. These versions are database-specific and differ in terms of what they encrypt and with what access method. Each version is characterized by the database it supports, the access method it provides, and the scope of encryption it offers. - -* **Version for Percona Server for PostgreSQL** - - This `pg_tde` version is based on and supported for [Percona Server for PostgreSQL 17.x :octicons-link-external-16:](https://docs.percona.com/postgresql/17/postgresql-server.html) - an open source binary drop-in replacement for PostgreSQL Community. It provides the `tde_heap` access method and offers [full encryption capabilities](features.md). - -* **Community version** (deprecated) - - This version is available with PostgreSQL Community 16 and 17, and Percona Distribution for PostgreSQL 16. It provides the `tde_heap_basic` access method, offering limited encryption features. The limitations are in encrypting WAL data only for tables created using the extension and no support of index encryption nor logical replication. - -### Which version to choose? - -Enjoy full encryption with the Percona Server for PostgreSQL version and the `tde_heap` access method. The Community version is deprecated and is planned to be removed in future releases. - -Still not sure? [Contact our experts](https://www.percona.com/about/contact) to find the best solution for you. - diff --git a/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md new file mode 100644 index 0000000000000..e21bdffbccfe2 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md @@ -0,0 +1,27 @@ +# How TDE Works + +To encrypt the data, two types of keys are used: + +* **Internal encryption keys** to encrypt user data. They are stored internally, near the data that they encrypt. +* The **principal key** to encrypt database keys. It is kept separately from the database keys and is managed externally in the key management store. + +!!! note + + For more information on managing and storing principal keys externally, see [Configure Global Key Provider](../global-key-provider-configuration/index.md). + +You have the following options to store and manage principal keys externally: + +* Use the HashiCorp Vault server. Only the back end KV Secrets Engine - Version 2 (API) is supported. +* Use the KMIP-compatible server. `pg_tde` has been tested with the [PyKMIP](https://pykmip.readthedocs.io/en/latest/server.html) server and [the HashiCorp Vault Enterprise KMIP Secrets Engine](https://www.vaultproject.io/docs/secrets/kmip). + +The encryption process is the following: + +![image](../_images/tde-flow.png) + +When a user creates an encrypted table using `pg_tde`, a new random key is generated internally for that table and is encrypted using the AES-CBC cipher algorithm. This key is used to encrypt all data the user inserts in that table. Eventually the encrypted data gets stored in the underlying storage. + +The internal key itself is encrypted using the principal key. The principal key is stored externally in the key management store. + +Similarly when the user queries the encrypted table, the principal key is retrieved from the key store to decrypt the internal key. Then the same unique internal key for that table is used to decrypt the data, and unencrypted data gets returned to the user. So, effectively, every TDE table has a unique key, and each table key is encrypted using the principal key. + +[Encrypted Data Scope](tde-encrypts.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/how-tde-helps.md b/contrib/pg_tde/documentation/docs/index/how-tde-helps.md new file mode 100644 index 0000000000000..d4397dc330043 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/how-tde-helps.md @@ -0,0 +1,20 @@ +# TDE Benefits + +## Benefits for organizations + +* **Data safety:** Prevents unauthorized access to stored data, even if backup files or storage devices are stolen or leaked. +* **Enterprise-ready Architecture:** Supports both single and multi-tenancy, giving flexibility for SaaS providers or internal multi-user systems. + +## Benefits for DBAs and engineers + +* **Granular control:** Encrypt specific tables or databases instead of the entire system, reducing performance overhead. +* **Operational simplicity:** Works transparently without requiring major application changes. +* **Defense in depth:** Adds another layer of protection to existing controls like TLS (encryption in transit), access control, and role-based permissions. + +When combined with the external Key Management Systems (KMS), TDE enables centralized control, auditing, and rotation of encryption keys—critical for secure production environments. + +!!! admonition "See also" + + Percona Blog: [Transparent Data Encryption (TDE)](https://www.percona.com/blog/transparent-data-encryption-tde/) + +[How TDE works](how-does-tde-work.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/index.md b/contrib/pg_tde/documentation/docs/index/index.md new file mode 100644 index 0000000000000..94830bd9e32dc --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/index.md @@ -0,0 +1,7 @@ +# What is Transparent Data Encryption (TDE)? + +Transparent Data Encryption (TDE) protects your data at rest by ensuring that even if the underlying storage is compromised, the data remains unreadable without the correct encryption keys. This is especially critical for environments handling sensitive, regulated, or high-value information. + +Encryption happens transparently in the background, with minimal impact on database operations. + +[TDE Benefits](how-tde-helps.md){.md-button} [Check the full feature list](../features.md){.md-button} [Get started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/supported-versions.md b/contrib/pg_tde/documentation/docs/index/supported-versions.md new file mode 100644 index 0000000000000..ed4758d0b671c --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/supported-versions.md @@ -0,0 +1,21 @@ +# Versions and Supported PostgreSQL Deployments + +The `pg_tde` extension is available for [Percona Server for PostgreSQL 17.x](https://docs.percona.com/postgresql/17/postgresql-server.html), an open source, drop-in replacement for PostgreSQL Community. This version provides the `tde_heap` access method and offers [full encryption capabilities](../features.md), including encryption of tables, indexes, WAL data, and support for logical replication. + +The extension is tightly integrated with Percona Server for PostgreSQL to deliver enhanced encryption functionality that is not available in community builds. + +## Why choose Percona Server for PostgreSQL? + +By using our PostgreSQL distribution, you get: + +- **Full encryption support** through the `tde_heap` access method, including tables, indexes, WAL data, and logical replication. +- **Enhanced performance and enterprise-ready features** not available in community builds. +- **Regular updates and security patches** backed by Percona’s expert support team. +- **Professional support** and guidance for secure PostgreSQL deployments. + +!!! note + Support for earlier or limited versions of `pg_tde` (such as `tde_heap_basic`) has been deprecated. + +Still unsure which deployment fits your needs? [Contact our experts](https://www.percona.com/about/contact) to find the best solution for your environment. + +[Get Started: Install pg_tde :material-arrow-right:](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/table-access-method.md b/contrib/pg_tde/documentation/docs/index/table-access-method.md similarity index 86% rename from contrib/pg_tde/documentation/docs/table-access-method.md rename to contrib/pg_tde/documentation/docs/index/table-access-method.md index 818ac7f479f4f..bff2655f0014a 100644 --- a/contrib/pg_tde/documentation/docs/table-access-method.md +++ b/contrib/pg_tde/documentation/docs/index/table-access-method.md @@ -1,8 +1,8 @@ -# Table access method +# Table Access Methods and TDE A table access method is the way how PostgreSQL stores the data in a table. The default table access method is `heap`. PostgreSQL organizes data in a heap structure, meaning there is no particular order to the rows in the table. Each row is stored independently and identified by its unique row identifier (TID). -## How the `heap` access method works +## How the heap access method works **Insertion**: When a new row is inserted, PostgreSQL finds a free space in the tablespace and stores the row there. @@ -16,7 +16,7 @@ Custom access methods allow you to implement and define your own way of organizi Custom access methods are typically available with PostgreSQL extensions. When you install an extension and enable it in PostgreSQL, a custom access method is created. -An example of such an approach is the `tde_heap` access method. It is automatically created **only** for the databases where you [enabled the `pg_tde` extension](setup.md) and configured the key provider, enabling you to encrypt the data. +An example of such an approach is the `tde_heap` access method. It is automatically created **only** for the databases where you [enabled the `pg_tde` extension](../setup.md) and configured the key provider, enabling you to encrypt the data. To use a custom access method, specify the `USING` clause for the `CREATE TABLE` command: @@ -28,30 +28,29 @@ CREATE TABLE table_name ( ) USING tde_heap; ``` -### How `tde_heap` works +### How tde_heap works -The `tde_heap` access method works on top of the default `heap` access method and is a marker to point which tables require encryption. It uses the custom storage manager TDE SMGR, which becomes active only after you installed the `pg_tde` extension. +The `tde_heap` access method works on top of the default `heap` access method and is a marker to point which tables require encryption. It uses the custom storage manager TDE SMGR, which becomes active only after you installed the `pg_tde` extension. -Every data modification operation is first sent to the Buffer Manager, which updates the buffer cache. Then, it is passed to the storage manager, which then writes it to disk. When a table requires encryption, the data is sent to the TDE storage manager, where it is encrypted before written to disk. +Every data modification operation is first sent to the Buffer Manager, which updates the buffer cache. Then, it is passed to the storage manager, which then writes it to disk. When a table requires encryption, the data is sent to the TDE storage manager, where it is encrypted before written to disk. -Similarly, when a client queries the database, the PostgreSQL core sends the request to the Buffer Manager which checks if the requested data is already in the buffer cache. If it’s not there, the Buffer Manager requests the data from the storage manager. The TDE storage manager reads the encrypted data from disk, decrypts it and loads it to the buffer cache. The Buffer Manager sends the requested data to the PostgreSQL core and then to the client. +Similarly, when a client queries the database, the PostgreSQL core sends the request to the Buffer Manager which checks if the requested data is already in the buffer cache. If it’s not there, the Buffer Manager requests the data from the storage manager. The TDE storage manager reads the encrypted data from disk, decrypts it and loads it to the buffer cache. The Buffer Manager sends the requested data to the PostgreSQL core and then to the client. - -Thus, the encryption is done at the storage manager level. +Thus, the encryption is done at the storage manager level. ## Changing the default table access method -You can change the default table access method so that every table in the entire database cluster is created using the custom access method. For example, you can enable data encryption by default by defining the `tde_heap` as the default table access method. +You can change the default table access method so that every table in the entire database cluster is created using the custom access method. For example, you can enable data encryption by default by defining the `tde_heap` as the default table access method. However, consider the following before making this change: -* This is a global setting and applies across the entire database cluster and not just a single database. -We recommend setting it with caution because all tables and materialized views created without an explicit access method in their `CREATE` statement will default to the specified table access method. +* This is a global setting and applies across the entire database cluster and not just a single database. +We recommend setting it with caution because all tables and materialized views created without an explicit access method in their `CREATE` statement will default to the specified table access method. * You must create the `pg_tde` extension and configure the key provider for all databases before you modify the configuration. Otherwise PostgreSQL won't find the specified access method and will throw an error. -Here's how you can set the new default table access method: +Here is how you can set the new default table access method: -1. Add the access method to the `default_table_access_method` parameter. +1. Add the access method to the `default_table_access_method` parameter: === "via the SQL statement" @@ -80,12 +79,13 @@ Here's how you can set the new default table access method: You can run the SET command anytime during the session. ```sql - SET default_table_access_method = tde_heap; + SET default_table_access_method = tde_heap; ``` 2. Reload the configuration to apply the changes: ```sql - SELECT pg_reload_conf(); + SELECT pg_reload_conf(); ``` +[Limitations of TDE](tde-limitations.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md new file mode 100644 index 0000000000000..d7cd64386537a --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md @@ -0,0 +1,11 @@ +# Encrypted Data Scope + +`pg_tde` encrypts the following components: + +* **User data** in tables using the extension, including associated TOAST data. The table metadata (column names, data types, etc.) is not encrypted. +* **Temporary tables** created during the query execution, for data tables created using the extension. +* **Write-Ahead Log (WAL) data** for the entire database cluster. This includes WAL data in encrypted and non-encrypted tables. +* **Indexes** associated encrypted tables. +* **Logical replication data** for encrypted tables (ensures encrypted content is preserved across replicas). + +[Table access methods and TDE](table-access-method.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md new file mode 100644 index 0000000000000..20b760a431026 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -0,0 +1,9 @@ +# Limitations of pg_tde + +* Keys in the local keyfile are stored unencrypted. For better security we recommend using the Key management storage. +* System tables are currently not encrypted. This means that statistics data and database metadata are currently not encrypted. + +* `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. +* `pb_tde` Release candidate is incompatible with `pg_tde`Beta2 due to significant changes in code. There is no direct upgrade flow from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. + +[Versions and supported PostgreSQL deployments](supported-versions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/install.md b/contrib/pg_tde/documentation/docs/install.md index e4a0cfe78ef25..01ada03b178a1 100644 --- a/contrib/pg_tde/documentation/docs/install.md +++ b/contrib/pg_tde/documentation/docs/install.md @@ -1,8 +1,8 @@ -# Installation +# Install pg_tde -Install `pg_tde` using one of the available installation methods: +To install `pg_tde`, use one of the following methods: -=== ":octicons-terminal-16: Package manager" +=== ":octicons-terminal-16: Package manager" The packages are available for the following operating systems: @@ -23,13 +23,16 @@ Install `pg_tde` using one of the available installation methods: [Run in Docker :material-arrow-right:](https://docs.percona.com/postgresql/latest/docker.html){.md-button} -=== ":octicons-download-16: Manual download" +=== ":octicons-download-16: Tar download" - `pg_tde` is included in the Percona Distribution for PostgreSQL tarball. Check below to get access to a detailed step-by-step guide. + `pg_tde` is included in the Percona Distribution for PostgreSQL tarball. Select the below link to access the step-by-step guide. [Install from tarballs :material-arrow-right:](https://docs.percona.com/postgresql/17/tarball.html){.md-button} +Follow the configuration steps below to continue: -## Next steps +[Configure pg_tde :material-arrow-right:](setup.md){.md-button} -[Setup :material-arrow-right:](setup.md){.md-button} +If you’ve already completed these steps, feel free to skip ahead to a later section: + + [Configure Key Management (KMS)](global-key-provider-configuration/index.md){.md-button} [Validate Encryption with pg_tde](test.md){.md-button} [Configure WAL encryption](wal-encryption.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/alpha1.md b/contrib/pg_tde/documentation/docs/release-notes/alpha1.md index aad9d43c5ef47..766c9e28b136e 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/alpha1.md +++ b/contrib/pg_tde/documentation/docs/release-notes/alpha1.md @@ -1,10 +1,9 @@ # pg_tde Alpha 1 (2024-03-28) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} - !!! important This version of Percona Transparent Data Encryption extension **is @@ -12,7 +11,6 @@ This will help us improve the product and make it production-ready faster. - ## Release Highlights The Alpha1 version of the extension introduces the following key features: @@ -28,7 +26,7 @@ The Alpha1 version of the extension introduces the following key features: * The keyring configuration is now stored in a catalog separately for each database, instead of a configuration file * Avoid storing secrets in the unencrypted catalog by configuring keyring parameters to be read from external sources (file, http(s) request) -## Improvements +## Improvements * Renamed the repository and Docker image from `postgres-tde-ext` to `pg_tde`. The extension name remains unchanged * Changed the Initialization Vector (IV) calculation of both the data and internal keys @@ -36,9 +34,8 @@ The Alpha1 version of the extension introduces the following key features: ## Bugs fixed * Fixed toast related crashes -* Fixed a crash with the DELETE statement +* Fixed a crash with the DELETE statement * Fixed performance-related issues * Fixed a bug where `pg_tde` sent many 404 requests to the Vault server * Fixed сompatibility issues with old OpenSSL versions -* Fixed сompatibility with old Curl versions - +* Fixed сompatibility with old Curl versions diff --git a/contrib/pg_tde/documentation/docs/release-notes/beta.md b/contrib/pg_tde/documentation/docs/release-notes/beta.md index c8c540a4f8935..11985962c0b20 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/beta.md +++ b/contrib/pg_tde/documentation/docs/release-notes/beta.md @@ -1,6 +1,6 @@ # pg_tde Beta (2024-06-30) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} @@ -22,4 +22,3 @@ Starting with `pg_tde` Beta, the access method for `pg_tde` extension is renamed * Fixed the issue with adjusting a current position in a file by using raw file descriptor for the `lseek` function. (Thanks to user _rainhard_ for providing the fix) * Enhanced the init script to consider a custom superuser for the POSTGRES_USER parameter when `pg_tde` is running via Docker (Thanks to _Alejandro Paredero_ for reporting the issue) - diff --git a/contrib/pg_tde/documentation/docs/release-notes/beta2.md b/contrib/pg_tde/documentation/docs/release-notes/beta2.md index 8c0e8203d439f..f134f6874b772 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/beta2.md +++ b/contrib/pg_tde/documentation/docs/release-notes/beta2.md @@ -1,10 +1,9 @@ # pg_tde Beta 2 (2024-12-16) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} - !!! important This version of Percona Transparent Data Encryption extension **is @@ -26,8 +25,8 @@ The Beta 2 version introduces the following features and improvements: ### New Features * Added the `tde_heap` access method with which you can now enable index encryption for encrypted tables and global WAL data encryption. To use this access method, you must install Percona Server for PostgreSQL. Check the [installation guide](../install.md) -* Added event triggers to identify index creation operations on encrypted tables and store those in a custom storage. -* Added support for secure transfer of keys using the [OASIS Key Management Interoperability Protocol (KMIP)](https://docs.oasis-open.org/kmip/kmip-spec/v2.0/os/kmip-spec-v2.0-os.html). The KMIP implementation was tested with the PyKMIP server and the HashiCorp Vault Enterprise KMIP Secrets Engine. +* Added event triggers to identify index creation operations on encrypted tables and store those in a custom storage. +* Added support for secure transfer of keys using the [OASIS Key Management Interoperability Protocol (KMIP)](https://docs.oasis-open.org/kmip/kmip-spec/v2.0/os/kmip-spec-v2.0-os.html). The KMIP implementation was tested with the PyKMIP server and the HashiCorp Vault Enterprise KMIP Secrets Engine. ### Improvements @@ -53,6 +52,3 @@ The Beta 2 version introduces the following features and improvements: * Fixed multiple bugs with `tde_heap_basic` and TOAST records * Fixed various memory leaks - - - diff --git a/contrib/pg_tde/documentation/docs/release-notes/mvp.md b/contrib/pg_tde/documentation/docs/release-notes/mvp.md index 9f84d24bf46c6..47e52b3bc019b 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/mvp.md +++ b/contrib/pg_tde/documentation/docs/release-notes/mvp.md @@ -3,6 +3,6 @@ The Minimum Viable Product (MVP) version of `pg_tde` introduces the following functionality: * Encryption of heap tables, including TOAST -* Encryption keys are stored either in Hashicorp Vault server or in local keyring file (for development) +* Encryption keys are stored either in Hashicorp Vault server or in local keyring file (for development) * The key storage is configurable via separate JSON configuration files -* Replication support \ No newline at end of file +* Replication support diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc.md b/contrib/pg_tde/documentation/docs/release-notes/rc.md index 9ee5fc16d73e5..792663a6da00e 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc.md @@ -1,19 +1,18 @@ # pg_tde Release Candidate ({{date.RC}}) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} - ## Release Highlights This release provides the following features and improvements: -* **Improved performance with redesigned WAL encryption**. +* **Improved performance with redesigned WAL encryption**. The approach to WAL encryption has been redesigned. Now, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. The information about what is encrypted is stored in the internal key metadata. This change improves WAL encryption flow with native replication and increases performance for large scale databases. -* **Default encryption key for single-tenancy**. +* **Default encryption key for single-tenancy**. The new functionality allows you to set a default principal key for the entire database cluster. This key is used to encrypt all databases and tables that do not have a custom principal key set. This feature simplifies encryption configuration and management in single-tenant environments where each user has their own database instance. @@ -35,90 +34,86 @@ This release provides the following features and improvements: ## Upgrade considerations -`pg_tde` Release Candidate is not backward compatible with `pg_tde` Beta2 due to significant changes in code. This means you cannot directly upgrade from one version to another. You must [uninstall](../uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. +`pg_tde` Release Candidate is not backward compatible with `pg_tde` Beta2 due to significant changes in code. This means you cannot directly upgrade from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. ## Known issues -* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. +* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. - To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: + To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: - * temporarily for the current session using the `ulimit -l ` command. - * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + * temporarily for the current session using the `ulimit -l ` command. + * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. Adjust the limits with caution since it affects other processes running in your system. * You can now delete global key providers even when their associated principal key is still in use. This known issue will be fixed in the next release. For now, avoid deleting global key providers. - - ## Changelog ### New Features -* [PG-1234](https://perconadev.atlassian.net/browse/PG-1234) - Added functions for separate global and database key management permissions +* [PG-1234](https://perconadev.atlassian.net/browse/PG-1234) - Added functions for separate global and database key management permissions. -* [PG-1255](https://perconadev.atlassian.net/browse/PG-1255) - Added functionality to delete key providers +* [PG-1255](https://perconadev.atlassian.net/browse/PG-1255) - Added functionality to delete key providers. -* [PG-1256](https://perconadev.atlassian.net/browse/PG-1256) - Added single-tenant support via the default principal key functionality +* [PG-1256](https://perconadev.atlassian.net/browse/PG-1256) - Added single-tenant support via the default principal key functionality. -* [PG-1258](https://perconadev.atlassian.net/browse/PG-1258) - Added functions to display additional information about principal keys / providers +* [PG-1258](https://perconadev.atlassian.net/browse/PG-1258) - Added functions to display additional information about principal keys / providers. -* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) - Redesigned WAL encryption +* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) - Redesigned WAL encryption. -* [PG-1303](https://perconadev.atlassian.net/browse/PG-1303) - Deprecated tde_heap_basic access method +* [PG-1303](https://perconadev.atlassian.net/browse/PG-1303) - Deprecated tde_heap_basic access method. ## Improvements -* [PG-858](https://perconadev.atlassian.net/browse/PG-858) - Refactored internal/principal key LWLocks to make local databases inherit a global key provider +* [PG-858](https://perconadev.atlassian.net/browse/PG-858) - Refactored internal/principal key LWLocks to make local databases inherit a global key provider. -* [PG-1243](https://perconadev.atlassian.net/browse/PG-1243) - Investigated performance issues at a specific threshold and large databases and updated documentation about handling hint bits +* [PG-1243](https://perconadev.atlassian.net/browse/PG-1243) - Investigated performance issues at a specific threshold and large databases and updated documentation about handling hint bits. -* [PG-1310](https://perconadev.atlassian.net/browse/PG-1310) - Added access method enforcement via the GUC variable +* [PG-1310](https://perconadev.atlassian.net/browse/PG-1310) - Added access method enforcement via the GUC variable. -* [PG-1361](https://perconadev.atlassian.net/browse/PG-1361) - Fixed pg_tde relocatability +* [PG-1361](https://perconadev.atlassian.net/browse/PG-1361) - Fixed pg_tde relocatability. -* [PG-1380](https://perconadev.atlassian.net/browse/PG-1380) - Added support for `pg_tde_is_encrypted()` function on indexes and sequences +* [PG-1380](https://perconadev.atlassian.net/browse/PG-1380) - Added support for `pg_tde_is_encrypted()` function on indexes and sequences. ### Bugs Fixed +* [PG-821](https://perconadev.atlassian.net/browse/PG-821) - Fixed the issue with `pg_basebackup` failing when configuring replication. -* [PG-821](https://perconadev.atlassian.net/browse/PG-821) - Fixed the issue with `pg_basebackup` failing when configuring replication +* [PG-847](https://perconadev.atlassian.net/browse/PG-847) - Fixed the issue with `pg_basebackup` and `pg_checksum` throwing an error on files created by `pg_tde` when the checksum is enabled on the database cluster. -* [PG-847](https://perconadev.atlassian.net/browse/PG-847) - Fixed the issue with `pg_basebackup` and `pg_checksum` throwing an error on files created by `pg_tde` when the checksum is enabled on the database cluster +* [PG-1004](https://perconadev.atlassian.net/browse/PG-1004) - Fixed the issue with `pg_checksums` utility failing during checksum verification on `pg_tde` tables. Now `pg_checksum` skips encrypted relations by looking if the relation has a custom storage manager (SMGR) key. -* [PG-1004](https://perconadev.atlassian.net/browse/PG-1004) - Fixed the issue with `pg_checksums` utility failing during checksum verification on `pg_tde` tables. Now `pg_checksum` skips encrypted relations by looking if the relation has a custom storage manager (SMGR) key +* [PG-1373](https://perconadev.atlassian.net/browse/PG-1373) - Fixed the issue with potential unterminated strings by using the `memcpy()` or `strlcpy()` instead of the `strncpy()` function. -* [PG-1373](https://perconadev.atlassian.net/browse/PG-1373) - Fixed the issue with potential unterminated strings by using the `memcpy()` or `strlcpy()` instead of the `strncpy()` function +* [PG-1378](https://perconadev.atlassian.net/browse/PG-1378) - Fixed the issue with toast tables created by the `ALTER TABLE` command not being encrypted. -* [PG-1378](https://perconadev.atlassian.net/browse/PG-1378) - Fixed the issue with toast tables created by the `ALTER TABLE` command not being encrypted +* [PG-1379](https://perconadev.atlassian.net/browse/PG-1379) - Fixed sequence and alter table handling in the event trigger. -* [PG-1379](https://perconadev.atlassian.net/browse/PG-1379) - Fixed sequence and alter table handling in the event trigger +* [PG-1222](https://perconadev.atlassian.net/browse/PG-1222) - Fixed the bug with confused relations with the same `RelFileNumber` in different databases. -* [PG-1222](https://perconadev.atlassian.net/browse/PG-1222) - Fixed the bug with confused relations with the same `RelFileNumber` in different databases +* [PG-1400](https://perconadev.atlassian.net/browse/PG-1400) - Corrected the pg_tde_change_key_provider naming in help. -* [PG-1400](https://perconadev.atlassian.net/browse/PG-1400) - Corrected the pg_tde_change_key_provider naming in help +* [PG-1401](https://perconadev.atlassian.net/browse/PG-1401) - Fixed the issue with inheriting an encryption status during the ALTER TABLE SET access method command execution by basing a new encryption status only on the new encryption setting. -* [PG-1401](https://perconadev.atlassian.net/browse/PG-1401) - Fixed the issue with inheriting an encryption status during the ALTER TABLE SET access method command execution by basing a new encryption status only on the new encryption setting - -* [PG-1414](https://perconadev.atlassian.net/browse/PG-1414) - Fixed the error message wording when configuring WAL encryption by referencing to a correct function +* [PG-1414](https://perconadev.atlassian.net/browse/PG-1414) - Fixed the error message wording when configuring WAL encryption by referencing to a correct function. * [PG-1450](https://perconadev.atlassian.net/browse/PG-1450) - Fixed the `pg_tde_delete_key_provider()` function behavior when called multiple times by ignoring already deleted records. * [PG-1451](https://perconadev.atlassian.net/browse/PG-1451) -Fixed the issue with the repeating error message about inability to retrieve a principal key even when a user creates non-encrypted tables by checking the current transaction ID in both the event trigger start function and during a file creation. If the transaction changed during the setup of the current event trigger data, the event trigger is reset. - -* [PG-1473](https://perconadev.atlassian.net/browse/PG-1473) - Allowed only users with key viewer privileges to execute `pg_tde_verify_principal_key()` and `pg_tde_verify_global_principal_key()` functions -* [PG-1474](https://perconadev.atlassian.net/browse/PG-1474) - Fixed the issue with the principal key reference corruption when reassigning it to a key provider with the same name by setting the key name in vault/kmip getters +* [PG-1473](https://perconadev.atlassian.net/browse/PG-1473) - Allowed only users with key viewer privileges to execute `pg_tde_verify_principal_key()` and `pg_tde_verify_global_principal_key()` functions. + +* [PG-1474](https://perconadev.atlassian.net/browse/PG-1474) - Fixed the issue with the principal key reference corruption when reassigning it to a key provider with the same name by setting the key name in vault/kmip getters. -* [PG-1476](https://perconadev.atlassian.net/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database +* [PG-1476](https://perconadev.atlassian.net/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. -* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation +* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. * [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. -/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database +/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. -* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation +* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. * [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. - diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc2.md b/contrib/pg_tde/documentation/docs/release-notes/rc2.md new file mode 100644 index 0000000000000..3de65ba1311fa --- /dev/null +++ b/contrib/pg_tde/documentation/docs/release-notes/rc2.md @@ -0,0 +1,119 @@ +# pg_tde Release Candidate ({{date.RC2}}) + +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. + +[Get started](../install.md){.md-button} + +## Release Highlights + +This release provides the following features and improvements: + +* **Improved performance with redesigned WAL encryption**. + + The approach to WAL encryption has been redesigned. Now, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. The information about what is encrypted is stored in the internal key metadata. This change improves WAL encryption flow with native replication and increases performance for large scale databases. + +* **Default encryption key for single-tenancy**. + + The new functionality allows you to set a default principal key for the entire database cluster. This key is used to encrypt all databases and tables that do not have a custom principal key set. This feature simplifies encryption configuration and management in single-tenant environments where each user has their own database instance. + +* **Ability to change key provider configuration** + + You no longer need to configure a new key provider and set a new principal key if the provider's configuration changed. Now can change the key provider configuration both for the current database and the entire PostgreSQL cluster using [functions](../functions.md#key-provider-management). This enhancement lifts existing limitations and is a native and common way to operate in PostgreSQL. + +* **Key management permissions** + + The new functions allow you to manage permissions for global and database key management separately. This feature provides more granular control over key management operations and allows you to delegate key management tasks to different roles. + +* **Additional information about principal keys and providers** + + The new functions allow you to display additional information about principal keys and providers. This feature helps you to understand the current key configuration and troubleshoot issues related to key management. + +* **`tde_heap_basic` access method deprecation** + + The `tde_heap_basic` access method has limitations in encryption capabilities and affects performance. Also, it poses a potential security risk when used in production environments due to indexes remaining unencrypted. Considering all the above, we decided to deprecate this access method and remove it in future releases. Use the `tde_heap` access method instead that is available with Percona Server for PostgreSQL 17 - a drop-in replacement for PostgreSQL Community. + +## Upgrade considerations + +`pg_tde` Release Candidate is not backward compatible with `pg_tde` Beta2 due to significant changes in code. This means you cannot directly upgrade from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. + +## Known issues + +* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. + + To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: + + * temporarily for the current session using the `ulimit -l ` command. + * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + + Adjust the limits with caution since it affects other processes running in your system. + +* You can now delete global key providers even when their associated principal key is still in use. This known issue will be fixed in the next release. For now, avoid deleting global key providers. + +## Changelog + +### New Features + +* [PG-1234](https://perconadev.atlassian.net/browse/PG-1234) - Added functions for separate global and database key management permissions. + +* [PG-1255](https://perconadev.atlassian.net/browse/PG-1255) - Added functionality to delete key providers. + +* [PG-1256](https://perconadev.atlassian.net/browse/PG-1256) - Added single-tenant support via the default principal key functionality. + +* [PG-1258](https://perconadev.atlassian.net/browse/PG-1258) - Added functions to display additional information about principal keys / providers. + +* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) - Redesigned WAL encryption. + +* [PG-1303](https://perconadev.atlassian.net/browse/PG-1303) - Deprecated tde_heap_basic access method. + +## Improvements + +* [PG-858](https://perconadev.atlassian.net/browse/PG-858) - Refactored internal/principal key LWLocks to make local databases inherit a global key provider. + +* [PG-1243](https://perconadev.atlassian.net/browse/PG-1243) - Investigated performance issues at a specific threshold and large databases and updated documentation about handling hint bits. + +* [PG-1310](https://perconadev.atlassian.net/browse/PG-1310) - Added access method enforcement via the GUC variable. + +* [PG-1361](https://perconadev.atlassian.net/browse/PG-1361) - Fixed pg_tde relocatability. + +* [PG-1380](https://perconadev.atlassian.net/browse/PG-1380) - Added support for `pg_tde_is_encrypted()` function on indexes and sequences. + +### Bugs Fixed + +* [PG-821](https://perconadev.atlassian.net/browse/PG-821) - Fixed the issue with `pg_basebackup` failing when configuring replication. + +* [PG-847](https://perconadev.atlassian.net/browse/PG-847) - Fixed the issue with `pg_basebackup` and `pg_checksum` throwing an error on files created by `pg_tde` when the checksum is enabled on the database cluster. + +* [PG-1004](https://perconadev.atlassian.net/browse/PG-1004) - Fixed the issue with `pg_checksums` utility failing during checksum verification on `pg_tde` tables. Now `pg_checksum` skips encrypted relations by looking if the relation has a custom storage manager (SMGR) key. + +* [PG-1373](https://perconadev.atlassian.net/browse/PG-1373) - Fixed the issue with potential unterminated strings by using the `memcpy()` or `strlcpy()` instead of the `strncpy()` function. + +* [PG-1378](https://perconadev.atlassian.net/browse/PG-1378) - Fixed the issue with toast tables created by the `ALTER TABLE` command not being encrypted. + +* [PG-1379](https://perconadev.atlassian.net/browse/PG-1379) - Fixed sequence and alter table handling in the event trigger. + +* [PG-1222](https://perconadev.atlassian.net/browse/PG-1222) - Fixed the bug with confused relations with the same `RelFileNumber` in different databases. + +* [PG-1400](https://perconadev.atlassian.net/browse/PG-1400) - Corrected the pg_tde_change_key_provider naming in help. + +* [PG-1401](https://perconadev.atlassian.net/browse/PG-1401) - Fixed the issue with inheriting an encryption status during the ALTER TABLE SET access method command execution by basing a new encryption status only on the new encryption setting. + +* [PG-1414](https://perconadev.atlassian.net/browse/PG-1414) - Fixed the error message wording when configuring WAL encryption by referencing to a correct function. + +* [PG-1450](https://perconadev.atlassian.net/browse/PG-1450) - Fixed the `pg_tde_delete_key_provider()` function behavior when called multiple times by ignoring already deleted records. + +* [PG-1451](https://perconadev.atlassian.net/browse/PG-1451) -Fixed the issue with the repeating error message about inability to retrieve a principal key even when a user creates non-encrypted tables by checking the current transaction ID in both the event trigger start function and during a file creation. If the transaction changed during the setup of the current event trigger data, the event trigger is reset. + +* [PG-1473](https://perconadev.atlassian.net/browse/PG-1473) - Allowed only users with key viewer privileges to execute `pg_tde_verify_principal_key()` and `pg_tde_verify_global_principal_key()` functions. + +* [PG-1474](https://perconadev.atlassian.net/browse/PG-1474) - Fixed the issue with the principal key reference corruption when reassigning it to a key provider with the same name by setting the key name in vault/kmip getters. + +* [PG-1476](https://perconadev.atlassian.net/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. + +* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. + +* [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. +/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. + +* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. + +* [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index d5228f15d3e77..e383f05c3aab9 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -1,11 +1,9 @@ # `pg_tde` release notes index -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. * [pg_tde Release Candidate {{date.RC}}](rc.md) * [pg_tde Beta2 (2024-12-16)](beta2.md) * [pg_tde Beta (2024-06-30)](beta.md) * [pg_tde Alpha1 (2024-03-28)](alpha1.md) -* [pg_tde MVP (2023-12-12)](mvp.md) - - +* [pg_tde MVP (2023-12-12)](mvp.md) diff --git a/contrib/pg_tde/documentation/docs/replication.md b/contrib/pg_tde/documentation/docs/replication.md new file mode 100644 index 0000000000000..b745107a2f222 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/replication.md @@ -0,0 +1,4 @@ +# Replication + + + # pg_tde Release Candidate ({{date.RC2}}) `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. From 593b3ac3d118a9929edc2a5c3b115946a8795d3f Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 19 Feb 2025 09:45:54 +0900 Subject: [PATCH 225/796] test_escape: Fix handling of short options in getopt_long() This addresses two errors in the module, based on the set of options supported: - '-c', for --conninfo, was not listed. - '-f', for --force-unsupported, was not listed. While on it, these are now listed in an alphabetical order. Author: Japin Li Discussion: https://postgr.es/m/ME0P300MB04451FB20CE0346A59C25CADB6FA2@ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM Backpatch-through: 13 --- src/test/modules/test_escape/test_escape.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/modules/test_escape/test_escape.c b/src/test/modules/test_escape/test_escape.c index 09303a00a20a8..a8e9c3cb518c3 100644 --- a/src/test/modules/test_escape/test_escape.c +++ b/src/test/modules/test_escape/test_escape.c @@ -824,7 +824,7 @@ main(int argc, char *argv[]) {NULL, 0, NULL, 0}, }; - while ((c = getopt_long(argc, argv, "vqh", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "c:fhqv", long_options, &option_index)) != -1) { switch (c) { From fbf8836f414ffd931910e2de45c84c8e0a65b578 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Wed, 19 Feb 2025 10:04:44 +0900 Subject: [PATCH 226/796] Fix freeing a child join's SpecialJoinInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In try_partitionwise_join, we try to break down the join between two partitioned relations into joins between matching partitions. To achieve this, we iterate through each pair of partitions from the two joining relations and create child join relations for them. To reduce memory accumulation during each iteration, one step we take is freeing the SpecialJoinInfos created for the child joins. A child join's SpecialJoinInfo is a copy of the parent join's SpecialJoinInfo, with some members being translated copies of their counterparts in the parent. However, when freeing the bitmapset members in a child join's SpecialJoinInfo, we failed to check whether they were translated copies. As a result, we inadvertently freed the members that were still in use by the parent SpecialJoinInfo, leading to crashes when those freed members were accessed. To fix, check if each member of the child join's SpecialJoinInfo is a translated copy and free it only if that's the case. This requires passing the parent join's SpecialJoinInfo as a parameter to free_child_join_sjinfo. Back-patch to v17 where this bug crept in. Bug: #18806 Reported-by: 孟令彬 Diagnosed-by: Tender Wang Author: Richard Guo Reviewed-by: Amit Langote Reviewed-by: Ashutosh Bapat Discussion: https://postgr.es/m/18806-d70b0c9fdf63dcbf@postgresql.org Backpatch-through: 17 --- src/backend/optimizer/path/joinrels.c | 34 ++++++++++++++----- src/test/regress/expected/partition_join.out | 35 ++++++++++++++++++++ src/test/regress/sql/partition_join.sql | 4 +++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index db475e25b15b6..d4c5738e965a0 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -45,7 +45,8 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, Relids left_relids, Relids right_relids); -static void free_child_join_sjinfo(SpecialJoinInfo *sjinfo); +static void free_child_join_sjinfo(SpecialJoinInfo *child_sjinfo, + SpecialJoinInfo *parent_sjinfo); static void compute_partition_bounds(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, @@ -1677,7 +1678,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_restrictlist); pfree(appinfos); - free_child_join_sjinfo(child_sjinfo); + free_child_join_sjinfo(child_sjinfo, parent_sjinfo); } } @@ -1744,18 +1745,33 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, * SpecialJoinInfo are freed here. */ static void -free_child_join_sjinfo(SpecialJoinInfo *sjinfo) +free_child_join_sjinfo(SpecialJoinInfo *child_sjinfo, + SpecialJoinInfo *parent_sjinfo) { /* * Dummy SpecialJoinInfos of inner joins do not have any translated fields * and hence no fields that to be freed. */ - if (sjinfo->jointype != JOIN_INNER) + if (child_sjinfo->jointype != JOIN_INNER) { - bms_free(sjinfo->min_lefthand); - bms_free(sjinfo->min_righthand); - bms_free(sjinfo->syn_lefthand); - bms_free(sjinfo->syn_righthand); + if (child_sjinfo->min_lefthand != parent_sjinfo->min_lefthand) + bms_free(child_sjinfo->min_lefthand); + + if (child_sjinfo->min_righthand != parent_sjinfo->min_righthand) + bms_free(child_sjinfo->min_righthand); + + if (child_sjinfo->syn_lefthand != parent_sjinfo->syn_lefthand) + bms_free(child_sjinfo->syn_lefthand); + + if (child_sjinfo->syn_righthand != parent_sjinfo->syn_righthand) + bms_free(child_sjinfo->syn_righthand); + + Assert(child_sjinfo->commute_above_l == parent_sjinfo->commute_above_l); + Assert(child_sjinfo->commute_above_r == parent_sjinfo->commute_above_r); + Assert(child_sjinfo->commute_below_l == parent_sjinfo->commute_below_l); + Assert(child_sjinfo->commute_below_r == parent_sjinfo->commute_below_r); + + Assert(child_sjinfo->semi_operators == parent_sjinfo->semi_operators); /* * semi_rhs_exprs may in principle be freed, but a simple pfree() does @@ -1763,7 +1779,7 @@ free_child_join_sjinfo(SpecialJoinInfo *sjinfo) */ } - pfree(sjinfo); + pfree(child_sjinfo); } /* diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 6d07f86b9bc07..b9b41340663e1 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -674,6 +674,41 @@ SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) RESET enable_partitionwise_aggregate; RESET enable_hashjoin; +-- bug in freeing the SpecialJoinInfo of a child-join +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM prt1 t3); + QUERY PLAN +-------------------------------------------------- + Append + -> Hash Semi Join + Hash Cond: (t1_1.a = t3_1.a) + -> Hash Join + Hash Cond: (t1_1.a = t2_1.a) + -> Seq Scan on prt1_p1 t1_1 + -> Hash + -> Seq Scan on prt1_p1 t2_1 + -> Hash + -> Seq Scan on prt1_p1 t3_1 + -> Hash Semi Join + Hash Cond: (t1_2.a = t3_2.a) + -> Hash Join + Hash Cond: (t1_2.a = t2_2.a) + -> Seq Scan on prt1_p2 t1_2 + -> Hash + -> Seq Scan on prt1_p2 t2_2 + -> Hash + -> Seq Scan on prt1_p2 t3_2 + -> Hash Semi Join + Hash Cond: (t1_3.a = t3_3.a) + -> Hash Join + Hash Cond: (t1_3.a = t2_3.a) + -> Seq Scan on prt1_p3 t1_3 + -> Hash + -> Seq Scan on prt1_p3 t2_3 + -> Hash + -> Seq Scan on prt1_p3 t3_3 +(28 rows) + -- -- partitioned by expression -- diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 128ce8376e609..afadefd50fd20 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -138,6 +138,10 @@ SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) RESET enable_partitionwise_aggregate; RESET enable_hashjoin; +-- bug in freeing the SpecialJoinInfo of a child-join +EXPLAIN (COSTS OFF) +SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM prt1 t3); + -- -- partitioned by expression -- From b6613e3025289a83d8913831e86f39d2502c8dd1 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Wed, 19 Feb 2025 11:05:35 +0900 Subject: [PATCH 227/796] Fix unsafe access to BufferDescriptors When considering a local buffer, the GetBufferDescriptor() call in BufferGetLSNAtomic() would be retrieving a shared buffer with a bad buffer ID. Since the code checks whether the buffer is shared before using the retrieved BufferDesc, this issue did not lead to any malfunction. Nonetheless this seems like trouble waiting to happen, so fix it by ensuring that GetBufferDescriptor() is only called when we know the buffer is shared. Author: Tender Wang Reviewed-by: Xuneng Zhou Reviewed-by: Richard Guo Discussion: https://postgr.es/m/CAHewXNku-o46-9cmUgyv6LkSZ25doDrWq32p=oz9kfD8ovVJMg@mail.gmail.com Backpatch-through: 13 --- src/backend/storage/buffer/bufmgr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 6c7792f08c92b..922f1f4141c5f 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3973,8 +3973,8 @@ BufferIsPermanent(Buffer buffer) XLogRecPtr BufferGetLSNAtomic(Buffer buffer) { - BufferDesc *bufHdr = GetBufferDescriptor(buffer - 1); char *page = BufferGetPage(buffer); + BufferDesc *bufHdr; XLogRecPtr lsn; uint32 buf_state; @@ -3988,6 +3988,7 @@ BufferGetLSNAtomic(Buffer buffer) Assert(BufferIsValid(buffer)); Assert(BufferIsPinned(buffer)); + bufHdr = GetBufferDescriptor(buffer - 1); buf_state = LockBufHdr(bufHdr); lsn = PageGetLSN(page); UnlockBufHdr(bufHdr, buf_state); From cd6b3fc5e8d1cd415a6d47215400a8d8bd67dcda Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 18 Feb 2025 21:23:59 -0500 Subject: [PATCH 228/796] Avoid null pointer dereference crash after OOM in Snowball stemmers. Absorb upstream bug fix (their commit e322673a841d9abd69994ae8cd20e191090b6ef4), which prevents a null pointer dereference crash if SN_create_env() gets a malloc failure at just the wrong point. Thanks to Maksim Korotkov for discovering the null-pointer bug and submitting the fix to upstream snowball. Reported-by: Maksim Korotkov Author: Maksim Korotkov Discussion: https://postgr.es/m/1d1a46-67ab1000-21-80c451@83151435 Backpatch-through: 13 --- src/backend/snowball/libstemmer/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/snowball/libstemmer/api.c b/src/backend/snowball/libstemmer/api.c index 375938e6d13fd..358f5633b28fe 100644 --- a/src/backend/snowball/libstemmer/api.c +++ b/src/backend/snowball/libstemmer/api.c @@ -34,7 +34,7 @@ extern struct SN_env * SN_create_env(int S_size, int I_size) extern void SN_close_env(struct SN_env * z, int S_size) { if (z == NULL) return; - if (S_size) + if (z->S) { int i; for (i = 0; i < S_size; i++) From 251863370f98758b30f383f5ffe4fa6b970bc728 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 19 Feb 2025 15:07:24 +0900 Subject: [PATCH 229/796] doc: Fix some issues with JSON_TABLE() exampls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove an unused PASSING variable. 2. Adjust formatting of JSON data used in an example to be valid under strict mode Reported-by: Miłosz Chmura Author: Robert Treat Discussion: https://postgr.es/m/173859550337.1071.4748984213168572913@wrigleys.postgresql.org --- doc/src/sgml/func.sgml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index cb9a6e2c91084..1c5e4bc573202 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -19308,7 +19308,7 @@ SELECT jt.* FROM SELECT jt.* FROM my_films, JSON_TABLE (js, '$.favorites[*] ? (@.films[*].director == $filter)' - PASSING 'Alfred Hitchcock' AS filter, 'Vertigo' AS filter2 + PASSING 'Alfred Hitchcock' AS filter COLUMNS ( id FOR ORDINALITY, kind text PATH '$.kind', @@ -19395,13 +19395,13 @@ SELECT jt.* FROM SELECT * FROM JSON_TABLE ( '{"favorites": - {"movies": + [{"movies": [{"name": "One", "director": "John Doe"}, {"name": "Two", "director": "Don Joe"}], "books": [{"name": "Mystery", "authors": [{"name": "Brown Dan"}]}, {"name": "Wonder", "authors": [{"name": "Jun Murakami"}, {"name":"Craig Doe"}]}] -}}'::json, '$.favorites[*]' +}]}'::json, '$.favorites[*]' COLUMNS ( user_id FOR ORDINALITY, NESTED '$.movies[*]' From f757650ab88382c01226e44e279539f16ca1f05a Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 19 Feb 2025 09:39:49 -0500 Subject: [PATCH 230/796] backport: Improve handling of empty query results in BackgroundPsql This is a backport of 70291a3c66e. Originally it was only applied to master, but I (Andres) am planning to fix a few bugs in BackgroundPsql that are harder to fix in the backbranches with the old behavior. It's also generally good for test infrastructure to behave similarly across branches, to avoid pain during backpatching. 70291a3c66e changes the behavior in some cases, but after discussing it, we are ok with that, it seems unlikely that there are out-of-core tests relying on the prior behavior. Discussion: https://postgr.es/m/ilcctzb5ju2gulvnadjmhgatnkxsdpac652byb2u3d3wqziyvx@fbuqcglker46 Michael's original commit message: A newline is not added at the end of an empty query result, causing the banner of the hardcoded \echo to not be discarded. This would reflect on scripts that expect an empty result by showing the "QUERY_SEPARATOR" in the output returned back to the caller, which was confusing. This commit changes BackgroundPsql::query() so as empty results are able to work correctly, making the first newline before the banner optional, bringing more flexibility. Note that this change affects 037_invalid_database.pl, where three queries generated an empty result, with the script relying on the data from the hardcoded banner to exist in the expected output. These queries are changed to use query_safe(), leading to a simpler script. The author has also proposed a test in a different patch where empty results would exist when using BackgroundPsql. Author: Jacob Champion Reviewed-by: Andrew Dunstan, Michael Paquier Discussion: https://postgr.es/m/CAOYmi+=60deN20WDyCoHCiecgivJxr=98s7s7-C8SkXwrCfHXg@mail.gmail.com --- src/test/perl/PostgreSQL/Test/BackgroundPsql.pm | 6 ++++-- src/test/recovery/t/037_invalid_database.pl | 14 +++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm index 3c2aca1c5d7bc..3f35be8dc2b2d 100644 --- a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm +++ b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm @@ -222,8 +222,10 @@ sub query die "psql query timed out" if $self->{timeout}->is_expired; $output = $self->{stdout}; - # remove banner again, our caller doesn't care - $output =~ s/\n$banner\n$//s; + # Remove banner again, our caller doesn't care. The first newline is + # optional, as there would not be one if consuming an empty query + # result. + $output =~ s/\n?$banner\n$//s; # clear out output for the next query $self->{stdout} = ''; diff --git a/src/test/recovery/t/037_invalid_database.pl b/src/test/recovery/t/037_invalid_database.pl index 6d1c711796421..3735086aee4c0 100644 --- a/src/test/recovery/t/037_invalid_database.pl +++ b/src/test/recovery/t/037_invalid_database.pl @@ -96,13 +96,12 @@ my $pid = $bgpsql->query('SELECT pg_backend_pid()'); # create the database, prevent drop database via lock held by a 2PC transaction -ok( $bgpsql->query_safe( - qq( +$bgpsql->query_safe( + qq( CREATE DATABASE regression_invalid_interrupt; BEGIN; LOCK pg_tablespace; - PREPARE TRANSACTION 'lock_tblspc';)), - "blocked DROP DATABASE completion"); + PREPARE TRANSACTION 'lock_tblspc';)); # Try to drop. This will wait due to the still held lock. $bgpsql->query_until(qr//, "DROP DATABASE regression_invalid_interrupt;\n"); @@ -135,11 +134,8 @@ # To properly drop the database, we need to release the lock previously preventing # doing so. -ok($bgpsql->query_safe(qq(ROLLBACK PREPARED 'lock_tblspc')), - "unblock DROP DATABASE"); - -ok($bgpsql->query(qq(DROP DATABASE regression_invalid_interrupt)), - "DROP DATABASE invalid_interrupt"); +$bgpsql->query_safe(qq(ROLLBACK PREPARED 'lock_tblspc')); +$bgpsql->query_safe(qq(DROP DATABASE regression_invalid_interrupt)); $bgpsql->quit(); From 920f3033090b1970cdb7052675ab9f2ffc918f8d Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 19 Feb 2025 09:41:08 -0500 Subject: [PATCH 231/796] backport: Extend background_psql() to be able to start asynchronously This is a backport of ba08edb0654. Originally it was only applied to master, but I (Andres) am planning to fix a few bugs in BackgroundPsql, which would be somewhat harder with the behavioural differences across branches. It's also generally good for test infrastructure to behave similarly across branches, to avoid pain during backpatching. Discussion: https://postgr.es/m/ilcctzb5ju2gulvnadjmhgatnkxsdpac652byb2u3d3wqziyvx@fbuqcglker46 Michael's original commit message: This commit extends the constructor routine of BackgroundPsql.pm with a new "wait" parameter. If set to 0, the routine returns without waiting for psql to start, ready to consume input. background_psql() in Cluster.pm gains the same "wait" parameter. The default behavior is still to wait for psql to start. It becomes now possible to not wait, giving to TAP scripts the possibility to perform actions between a BackgroundPsql startup and its wait_connect() call. Author: Jacob Champion Discussion: https://postgr.es/m/CAOYmi+=60deN20WDyCoHCiecgivJxr=98s7s7-C8SkXwrCfHXg@mail.gmail.com --- .../perl/PostgreSQL/Test/BackgroundPsql.pm | 28 ++++++++++++++----- src/test/perl/PostgreSQL/Test/Cluster.pm | 10 ++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm index 3f35be8dc2b2d..2216918b253c6 100644 --- a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm +++ b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm @@ -68,7 +68,7 @@ use Test::More; =over -=item PostgreSQL::Test::BackgroundPsql->new(interactive, @psql_params, timeout) +=item PostgreSQL::Test::BackgroundPsql->new(interactive, @psql_params, timeout, wait) Builds a new object of class C for either an interactive or background session and starts it. If C is @@ -76,12 +76,15 @@ true then a PTY will be attached. C should contain the full command to run psql with all desired parameters and a complete connection string. For C sessions, IO::Pty is required. +This routine will not return until psql has started up and is ready to +consume input. Set B to 0 to return immediately instead. + =cut sub new { my $class = shift; - my ($interactive, $psql_params, $timeout) = @_; + my ($interactive, $psql_params, $timeout, $wait) = @_; my $psql = { 'stdin' => '', 'stdout' => '', @@ -119,14 +122,25 @@ sub new my $self = bless $psql, $class; - $self->_wait_connect(); + $wait = 1 unless defined($wait); + if ($wait) + { + $self->wait_connect(); + } return $self; } -# Internal routine for awaiting psql starting up and being ready to consume -# input. -sub _wait_connect +=pod + +=item $session->wait_connect + +Returns once psql has started up and is ready to consume input. This is called +automatically for clients unless requested otherwise in the constructor. + +=cut + +sub wait_connect { my ($self) = @_; @@ -187,7 +201,7 @@ sub reconnect_and_clear $self->{stdin} = ''; $self->{stdout} = ''; - $self->_wait_connect(); + $self->wait_connect(); } =pod diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index d7a5deda7d9cb..7e97cc6e66e05 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -2191,6 +2191,12 @@ connection. If given, it must be an array reference containing additional parameters to B. +=item wait => 1 + +By default, this method will not return until connection has completed (or +failed). Set B to 0 to return immediately instead. (Clients can call the +session's C method manually when needed.) + =back =cut @@ -2214,13 +2220,15 @@ sub background_psql '-'); $params{on_error_stop} = 1 unless defined $params{on_error_stop}; + $params{wait} = 1 unless defined $params{wait}; $timeout = $params{timeout} if defined $params{timeout}; push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop}; push @psql_params, @{ $params{extra_params} } if defined $params{extra_params}; - return PostgreSQL::Test::BackgroundPsql->new(0, \@psql_params, $timeout); + return PostgreSQL::Test::BackgroundPsql->new(0, \@psql_params, $timeout, + $params{wait}); } =pod From 1f2ab31409b68f468f583d0bf42af9b115a36419 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 19 Feb 2025 10:45:48 -0500 Subject: [PATCH 232/796] tests: BackgroundPsql: Fix potential for lost errors on windows This addresses various corner cases in BackgroundPsql: - On windows stdout and stderr may arrive out of order, leading to errors not being reported, or attributed to the wrong statement. To fix, emit the "query-separation banner" on both stdout and stderr and wait for both. - Very occasionally the "query-separation banner" would not get removed, because we waited until the banner arrived, but then replaced the banner plus newline. To fix, wait for banner and newline. - For interactive psql replacing $banner\n is not sufficient, interactive psql outputs \r\n. - For interactive psql, where commands are echoed to stdout, the \echo command, rather than its output, would be matched. This would sometimes lead to output from the prior query, or wait_connect(), being returned in the next command. This also affected wait_connect(), leading to sometimes sending queries to psql before the connection actually was established. While debugging these issues I also found that it's hard to know whether a query separation banner was attributed to the right query. Make that easier by counting the queries each BackgroundPsql instance has emitted and include the number in the banner. Also emit psql stdout/stderr in query() and wait_connect() as Test::More notes, without that it's rather hard to debug some issues in CI and buildfarm. As this can cause issues not just to-be-added tests, but also existing ones, backpatch the fix to all supported versions. Reviewed-by: Daniel Gustafsson Reviewed-by: Noah Misch Discussion: https://postgr.es/m/wmovm6xcbwh7twdtymxuboaoarbvwj2haasd3sikzlb3dkgz76@n45rzycluzft Backpatch-through: 13 --- .../perl/PostgreSQL/Test/BackgroundPsql.pm | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm index 2216918b253c6..a552132484ec9 100644 --- a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm +++ b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm @@ -89,7 +89,8 @@ sub new 'stdin' => '', 'stdout' => '', 'stderr' => '', - 'query_timer_restart' => undef + 'query_timer_restart' => undef, + 'query_cnt' => 1, }; my $run; @@ -148,11 +149,25 @@ sub wait_connect # connection failures are caught here, relieving callers of the need to # handle those. (Right now, we have no particularly good handling for # errors anyway, but that might be added later.) + # + # See query() for details about why/how the banner is used. my $banner = "background_psql: ready"; - $self->{stdin} .= "\\echo $banner\n"; + my $banner_match = qr/(^|\n)$banner\r?\n/; + $self->{stdin} .= "\\echo $banner\n\\warn $banner\n"; $self->{run}->pump() - until $self->{stdout} =~ /$banner/ || $self->{timeout}->is_expired; - $self->{stdout} = ''; # clear out banner + until ($self->{stdout} =~ /$banner_match/ + && $self->{stderr} =~ /$banner\r?\n/) + || $self->{timeout}->is_expired; + + note "connect output:\n", + explain { + stdout => $self->{stdout}, + stderr => $self->{stderr}, + }; + + # clear out banners + $self->{stdout} = ''; + $self->{stderr} = ''; die "psql startup timed out" if $self->{timeout}->is_expired; } @@ -219,27 +234,57 @@ sub query my ($self, $query) = @_; my $ret; my $output; + my $query_cnt = $self->{query_cnt}++; + local $Test::Builder::Level = $Test::Builder::Level + 1; - note "issuing query via background psql: $query"; + note "issuing query $query_cnt via background psql: $query"; $self->{timeout}->start() if (defined($self->{query_timer_restart})); # Feed the query to psql's stdin, followed by \n (so psql processes the # line), by a ; (so that psql issues the query, if it doesn't include a ; - # itself), and a separator echoed with \echo, that we can wait on. - my $banner = "background_psql: QUERY_SEPARATOR"; - $self->{stdin} .= "$query\n;\n\\echo $banner\n"; - - pump_until($self->{run}, $self->{timeout}, \$self->{stdout}, qr/$banner/); + # itself), and a separator echoed both with \echo and \warn, that we can + # wait on. + # + # To avoid somehow confusing the separator from separately issued queries, + # and to make it easier to debug, we include a per-psql query counter in + # the separator. + # + # We need both \echo (printing to stdout) and \warn (printing to stderr), + # because on windows we can get data on stdout before seeing data on + # stderr (or vice versa), even if psql printed them in the opposite + # order. We therefore wait on both. + # + # We need to match for the newline, because we try to remove it below, and + # it's possible to consume just the input *without* the newline. In + # interactive psql we emit \r\n, so we need to allow for that. Also need + # to be careful that we don't e.g. match the echoed \echo command, rather + # than its output. + my $banner = "background_psql: QUERY_SEPARATOR $query_cnt:"; + my $banner_match = qr/(^|\n)$banner\r?\n/; + $self->{stdin} .= "$query\n;\n\\echo $banner\n\\warn $banner\n"; + pump_until( + $self->{run}, $self->{timeout}, + \$self->{stdout}, qr/$banner_match/); + pump_until( + $self->{run}, $self->{timeout}, + \$self->{stderr}, qr/$banner_match/); die "psql query timed out" if $self->{timeout}->is_expired; - $output = $self->{stdout}; - # Remove banner again, our caller doesn't care. The first newline is - # optional, as there would not be one if consuming an empty query - # result. - $output =~ s/\n?$banner\n$//s; + note "results query $query_cnt:\n", + explain { + stdout => $self->{stdout}, + stderr => $self->{stderr}, + }; + + # Remove banner from stdout and stderr, our caller doesn't care. The + # first newline is optional, as there would not be one if consuming an + # empty query result. + $output = $self->{stdout}; + $output =~ s/$banner_match//; + $self->{stderr} =~ s/$banner_match//; # clear out output for the next query $self->{stdout} = ''; From d39167f3b8ab43ca5c8eb9bfb03d37321038f569 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 19 Feb 2025 16:35:15 -0500 Subject: [PATCH 233/796] Fix crash in brininsertcleanup during logical replication. Logical replication crashes if the subscriber's partitioned table has a BRIN index. There are two independently blamable causes, and this patch fixes both: 1. brininsertcleanup fails if called twice for the same IndexInfo, because it half-destroys its BrinInsertState but leaves it still linked from ii_AmCache. brininsert would also fail in that state, so it's pretty hard to see any advantage to this coding. Fully remove the BrinInsertState, instead, so that a new brininsert call would create a new cache. 2. A logical replication subscriber sometimes does ExecOpenIndices twice on the same ResultRelInfo, followed by doing ExecCloseIndices twice; the second call reaches the brininsertcleanup bug. Quite aside from tickling unexpected cases in aminsertcleanup methods, this seems very wasteful, because the IndexInfos built in the first ExecOpenIndices call are just lost during the second call, and have to be rebuilt at possibly-nontrivial cost. We should establish a coding rule that you don't do that. The problematic coding is that when the target table is partitioned, apply_handle_tuple_routing calls ExecFindPartition which does ExecOpenIndices (and expects that ExecCleanupTupleRouting will close the indexes again). Using the ResultRelInfo made by ExecFindPartition, it calls apply_handle_delete_internal or apply_handle_insert_internal, both of which think they need to do ExecOpenIndices/ExecCloseIndices for themselves. They do in the main non-partitioned code paths, but not here. The simplest fix is to pull their ExecOpenIndices/ExecCloseIndices calls out and put them in the call sites for the non-partitioned cases. (We could have refactored apply_handle_update_internal similarly, but I did not do so today because there's no bug there: the partitioned code path doesn't call it.) Also, remove the always-duplicative open/close calls within apply_handle_tuple_routing itself. Since brininsertcleanup and indeed the whole aminsertcleanup mechanism are new in v17, there's no observable bug in older branches. A case could be made for trying to avoid these duplicative open/close calls in the older branches, but for now it seems not worth the trouble and risk of new bugs. Bug: #18815 Reported-by: Sergey Belyashov Discussion: https://postgr.es/m/18815-2a0407cc7f40b327@postgresql.org Backpatch-through: 17 --- src/backend/access/brin/brin.c | 8 ++++-- src/backend/replication/logical/worker.c | 35 ++++++++++++++++-------- src/test/subscription/t/013_partition.pl | 4 +++ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 92cb72ebba6ed..6cbd31f0a3d8b 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -505,16 +505,18 @@ brininsertcleanup(Relation index, IndexInfo *indexInfo) BrinInsertState *bistate = (BrinInsertState *) indexInfo->ii_AmCache; /* bail out if cache not initialized */ - if (indexInfo->ii_AmCache == NULL) + if (bistate == NULL) return; + /* do this first to avoid dangling pointer if we fail partway through */ + indexInfo->ii_AmCache = NULL; + /* * Clean up the revmap. Note that the brinDesc has already been cleaned up * as part of its own memory context. */ brinRevmapTerminate(bistate->bis_rmAccess); - bistate->bis_rmAccess = NULL; - bistate->bis_desc = NULL; + pfree(bistate); } /* diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index d091a1dd27cdc..65e22306c4863 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -2432,8 +2432,13 @@ apply_handle_insert(StringInfo s) apply_handle_tuple_routing(edata, remoteslot, NULL, CMD_INSERT); else - apply_handle_insert_internal(edata, edata->targetRelInfo, - remoteslot); + { + ResultRelInfo *relinfo = edata->targetRelInfo; + + ExecOpenIndices(relinfo, false); + apply_handle_insert_internal(edata, relinfo, remoteslot); + ExecCloseIndices(relinfo); + } finish_edata(edata); @@ -2460,15 +2465,14 @@ apply_handle_insert_internal(ApplyExecutionData *edata, { EState *estate = edata->estate; - /* We must open indexes here. */ - ExecOpenIndices(relinfo, false); + /* Caller should have opened indexes already. */ + Assert(relinfo->ri_IndexRelationDescs != NULL || + !relinfo->ri_RelationDesc->rd_rel->relhasindex || + RelationGetIndexList(relinfo->ri_RelationDesc) == NIL); /* Do the insert. */ TargetPrivilegesCheck(relinfo->ri_RelationDesc, ACL_INSERT); ExecSimpleRelationInsert(relinfo, estate, remoteslot); - - /* Cleanup. */ - ExecCloseIndices(relinfo); } /* @@ -2767,8 +2771,14 @@ apply_handle_delete(StringInfo s) apply_handle_tuple_routing(edata, remoteslot, NULL, CMD_DELETE); else - apply_handle_delete_internal(edata, edata->targetRelInfo, + { + ResultRelInfo *relinfo = edata->targetRelInfo; + + ExecOpenIndices(relinfo, false); + apply_handle_delete_internal(edata, relinfo, remoteslot, rel->localindexoid); + ExecCloseIndices(relinfo); + } finish_edata(edata); @@ -2802,7 +2812,11 @@ apply_handle_delete_internal(ApplyExecutionData *edata, bool found; EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); - ExecOpenIndices(relinfo, false); + + /* Caller should have opened indexes already. */ + Assert(relinfo->ri_IndexRelationDescs != NULL || + !localrel->rd_rel->relhasindex || + RelationGetIndexList(localrel) == NIL); found = FindReplTupleInLocalRel(edata, localrel, remoterel, localindexoid, remoteslot, &localslot); @@ -2831,7 +2845,6 @@ apply_handle_delete_internal(ApplyExecutionData *edata, } /* Cleanup. */ - ExecCloseIndices(relinfo); EvalPlanQualEnd(&epqstate); } @@ -3042,14 +3055,12 @@ apply_handle_tuple_routing(ApplyExecutionData *edata, EPQState epqstate; EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); - ExecOpenIndices(partrelinfo, false); EvalPlanQualSetSlot(&epqstate, remoteslot_part); TargetPrivilegesCheck(partrelinfo->ri_RelationDesc, ACL_UPDATE); ExecSimpleRelationUpdate(partrelinfo, estate, &epqstate, localslot, remoteslot_part); - ExecCloseIndices(partrelinfo); EvalPlanQualEnd(&epqstate); } else diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl index 29580525a97d8..db7a1604643b7 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -49,6 +49,10 @@ $node_subscriber1->safe_psql('postgres', "CREATE TABLE tab1 (c text, a int PRIMARY KEY, b text) PARTITION BY LIST (a)" ); +# make a BRIN index to test aminsertcleanup logic in subscriber +$node_subscriber1->safe_psql('postgres', + "CREATE INDEX tab1_c_brin_idx ON tab1 USING brin (c)" +); $node_subscriber1->safe_psql('postgres', "CREATE TABLE tab1_1 (b text, c text DEFAULT 'sub1_tab1', a int NOT NULL)" ); From b597930f3c70cd81769b7202718464e6e8ddef30 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Wed, 19 Feb 2025 23:51:48 +0100 Subject: [PATCH 234/796] Correct relation size estimate with low fillfactor Since commit 29cf61ade3, table_block_relation_estimate_size() considers fillfactor when estimating number of rows in a relation before the first ANALYZE. The formula however did not consider tuples may be larger than available space determined by fillfactor, ending with density 0. This ultimately means the relation was estimated to contain a single row. The executor however places at least one tuple per page, even with very low fillfactor values, so the density should be at least 1. Fixed by clamping the density estimate using clamp_row_est(). Reported by Heikki Linnakangas. Fix by me, with regression test inspired by example provided by Heikki. Backpatch to 17, where the issue was introduced. Reported-by: Heikki Linnakangas Backpatch-through: 17 Discussion: https://postgr.es/m/2bf9d973-7789-4937-a7ca-0af9fb49c71e@iki.fi --- src/backend/access/table/tableam.c | 3 +++ src/test/regress/expected/stats.out | 17 +++++++++++++++++ src/test/regress/sql/stats.sql | 16 ++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index 9e0fae5194afd..01e96ea8a1d88 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -25,6 +25,7 @@ #include "access/tableam.h" #include "access/xact.h" #include "commands/defrem.h" +#include "optimizer/optimizer.h" #include "optimizer/plancat.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" @@ -741,6 +742,8 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths, tuple_width += overhead_bytes_per_tuple; /* note: integer division is intentional here */ density = (usable_bytes_per_page * fillfactor / 100) / tuple_width; + /* There's at least one row on the page, even with low fillfactor. */ + density = clamp_row_est(density); } *tuples = rint(density * (double) curpages); diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 6e08898b18306..43b7873c62b18 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -1646,4 +1646,21 @@ SELECT COUNT(*) FROM brin_hot_3 WHERE a = 2; DROP TABLE brin_hot_3; SET enable_seqscan = on; +-- Test that estimation of relation size works with tuples wider than the +-- relation fillfactor. We create a table with wide inline attributes and +-- low fillfactor, insert rows and then see how many rows EXPLAIN shows +-- before running analyze. We disable autovacuum so that it does not +-- interfere with the test. +CREATE TABLE table_fillfactor ( + n char(1000) +) with (fillfactor=10, autovacuum_enabled=off); +INSERT INTO table_fillfactor +SELECT 'x' FROM generate_series(1,1000); +SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor'); + estimated | actual +-----------+-------- + 1000 | 1000 +(1 row) + +DROP TABLE table_fillfactor; -- End of Stats Test diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index d8ac0d06f484d..ed0bc1ef0745b 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -849,4 +849,20 @@ DROP TABLE brin_hot_3; SET enable_seqscan = on; +-- Test that estimation of relation size works with tuples wider than the +-- relation fillfactor. We create a table with wide inline attributes and +-- low fillfactor, insert rows and then see how many rows EXPLAIN shows +-- before running analyze. We disable autovacuum so that it does not +-- interfere with the test. +CREATE TABLE table_fillfactor ( + n char(1000) +) with (fillfactor=10, autovacuum_enabled=off); + +INSERT INTO table_fillfactor +SELECT 'x' FROM generate_series(1,1000); + +SELECT * FROM check_estimated_rows('SELECT * FROM table_fillfactor'); + +DROP TABLE table_fillfactor; + -- End of Stats Test From f345bf7db7c4c4a04096cbaa776e818743ee7835 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 20 Feb 2025 09:31:01 +0900 Subject: [PATCH 235/796] test_escape: Fix output of --help The short option name -f was not listed, only its long option name --force-unsupported. Author: Japin Li Discussion: https://postgr.es/m/ME0P300MB04452BD1FB1B277D4C1C20B9B6C52@ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM Backpatch-through: 13 --- src/test/modules/test_escape/test_escape.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/modules/test_escape/test_escape.c b/src/test/modules/test_escape/test_escape.c index a8e9c3cb518c3..f6b364489774f 100644 --- a/src/test/modules/test_escape/test_escape.c +++ b/src/test/modules/test_escape/test_escape.c @@ -801,7 +801,7 @@ usage(const char *hint) " -c, --conninfo=CONNINFO connection information to use\n" " -v, --verbose show test details even for successes\n" " -q, --quiet only show failures\n" - " --force-unsupported test invalid input even if unsupported\n" + " -f, --force-unsupported test invalid input even if unsupported\n" ); if (hint) From 4e7a4e8e9b028d3d1869178ff1a31b889cb89cc6 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 20 Feb 2025 10:43:35 +0900 Subject: [PATCH 236/796] Fix FATAL message for invalid recovery timeline at beginning of recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the requested recovery timeline is not reachable, the logged checkpoint and timeline should to be the values read from the backup_label when it is defined. The message generated used the values from the control file in this case, which is fine when recovering from the control file without a backup_label, but not if there is a backup_label. Issue introduced in ee994272ca50. v15 has introduced xlogrecovery.c and more simplifications in this area (4a92a1c3d1c3, a27048cbcb58), making this change a bit simpler to think about, so backpatch only down to this version. Author: David Steele Reviewed-by: Andrey M. Borodin Reviewed-by: Benoit Lobréau Discussion: https://postgr.es/m/c3d617d4-1696-4aa7-8a4d-5a7d19cc5618@pgbackrest.org Backpatch-through: 15 --- src/backend/access/transam/xlogrecovery.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 104687b9cd762..8becf0420b515 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -845,13 +845,13 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr, * tliSwitchPoint will throw an error if the checkpoint's timeline is * not in expectedTLEs at all. */ - switchpoint = tliSwitchPoint(ControlFile->checkPointCopy.ThisTimeLineID, expectedTLEs, NULL); + switchpoint = tliSwitchPoint(CheckPointTLI, expectedTLEs, NULL); ereport(FATAL, (errmsg("requested timeline %u is not a child of this server's history", recoveryTargetTLI), errdetail("Latest checkpoint is at %X/%X on timeline %u, but in the history of the requested timeline, the server forked off from that timeline at %X/%X.", - LSN_FORMAT_ARGS(ControlFile->checkPoint), - ControlFile->checkPointCopy.ThisTimeLineID, + LSN_FORMAT_ARGS(CheckPointLoc), + CheckPointTLI, LSN_FORMAT_ARGS(switchpoint)))); } From 0557f16e830dcaa11f5ddbf7d211a4ac3840386b Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 15 Feb 2025 13:13:19 +1300 Subject: [PATCH 237/796] Fix explicit valgrind interaction in read_stream.c. This is a back-patch of commits 2a8a0067 and 2509b857 into REL_17_STABLE. It's doesn't fix any known live bug in PostgreSQL v17 itself, but an extension could in theory have used the per-buffer data feature and seen spurious errors under Valgrind. Discussion: https://postgr.es/m/CA%2BhUKG%2Bg6aXpi2FEHqeLOzE%2BxYw%3DOV%2B-N5jhOEnnV%2BF0USM9xA%40mail.gmail.com --- src/backend/storage/aio/read_stream.c | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c index 9b962c301bff6..b5be92d461128 100644 --- a/src/backend/storage/aio/read_stream.c +++ b/src/backend/storage/aio/read_stream.c @@ -176,9 +176,20 @@ read_stream_get_block(ReadStream *stream, void *per_buffer_data) if (blocknum != InvalidBlockNumber) stream->buffered_blocknum = InvalidBlockNumber; else + { + /* + * Tell Valgrind that the per-buffer data is undefined. That replaces + * the "noaccess" state that was set when the consumer moved past this + * entry last time around the queue, and should also catch callbacks + * that fail to initialize data that the buffer consumer later + * accesses. On the first go around, it is undefined already. + */ + VALGRIND_MAKE_MEM_UNDEFINED(per_buffer_data, + stream->per_buffer_data_size); blocknum = stream->callback(stream, stream->callback_private_data, per_buffer_data); + } return blocknum; } @@ -687,8 +698,11 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data) } #ifdef CLOBBER_FREED_MEMORY - /* Clobber old buffer and per-buffer data for debugging purposes. */ + /* Clobber old buffer for debugging purposes. */ stream->buffers[oldest_buffer_index] = InvalidBuffer; +#endif + +#if defined(CLOBBER_FREED_MEMORY) || defined(USE_VALGRIND) /* * The caller will get access to the per-buffer data, until the next call. @@ -697,11 +711,23 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data) * that is holding a dangling pointer to it. */ if (stream->per_buffer_data) - wipe_mem(get_per_buffer_data(stream, - oldest_buffer_index == 0 ? - stream->queue_size - 1 : - oldest_buffer_index - 1), - stream->per_buffer_data_size); + { + void *per_buffer_data; + + per_buffer_data = get_per_buffer_data(stream, + oldest_buffer_index == 0 ? + stream->queue_size - 1 : + oldest_buffer_index - 1); + +#if defined(CLOBBER_FREED_MEMORY) + /* This also tells Valgrind the memory is "noaccess". */ + wipe_mem(per_buffer_data, stream->per_buffer_data_size); +#elif defined(USE_VALGRIND) + /* Tell it ourselves. */ + VALGRIND_MAKE_MEM_NOACCESS(per_buffer_data, + stream->per_buffer_data_size); +#endif + } #endif /* Pin transferred to caller. */ From 6b5cb7b0c5cd45d5b058d8d0273fbca86952f1cd Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 21 Feb 2025 17:31:01 +0900 Subject: [PATCH 238/796] Add missing deparsing of [NO] IDENT to XMLSERIALIZE() NO INDENT is the default, and is added if no explicit indentation flag was provided with XMLSERIALIZE(). Oversight in 483bdb2afec9. Author: Jim Jones Discussion: https://postgr.es/m/bebd457e-5b43-46b3-8fc6-f6a6509483ba@uni-muenster.de Backpatch-through: 16 --- src/backend/utils/adt/ruleutils.c | 7 +++++++ src/test/regress/expected/xml.out | 16 ++++++++++------ src/test/regress/expected/xml_1.out | 10 ++++++++++ src/test/regress/expected/xml_2.out | 16 ++++++++++------ src/test/regress/sql/xml.sql | 2 ++ 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 7d6d9141a9417..c6293b20cfeaf 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9898,9 +9898,16 @@ get_rule_expr(Node *node, deparse_context *context, } } if (xexpr->op == IS_XMLSERIALIZE) + { appendStringInfo(buf, " AS %s", format_type_with_typemod(xexpr->type, xexpr->typmod)); + if (xexpr->indent) + appendStringInfoString(buf, " INDENT"); + else + appendStringInfoString(buf, " NO INDENT"); + } + if (xexpr->op == IS_DOCUMENT) appendStringInfoString(buf, " IS DOCUMENT"); else diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index bd548f2bc5ba0..868479997d8ac 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -822,21 +822,25 @@ CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar'); CREATE VIEW xmlview7 AS SELECT xmlroot(xml '', version no value, standalone yes); CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); +CREATE VIEW xmlview10 AS SELECT xmlserialize(document '42' AS text indent); +CREATE VIEW xmlview11 AS SELECT xmlserialize(document '42' AS character varying no indent); SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%' ORDER BY 1; - table_name | view_definition -------------+------------------------------------------------------------------------------------------------------------ + table_name | view_definition +------------+--------------------------------------------------------------------------------------------------------------------------------------- xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment; + xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '42'::xml AS text INDENT) AS "xmlserialize"; + xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '42'::xml AS character varying NO INDENT))::character varying AS "xmlserialize"; xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat"; xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement"; - xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" + + xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" + | FROM emp; xmlview5 | SELECT XMLPARSE(CONTENT 'x'::text STRIP WHITESPACE) AS "xmlparse"; xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi"; xmlview7 | SELECT XMLROOT(''::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot"; - xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10)))::character(10) AS "xmlserialize"; - xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text) AS "xmlserialize"; -(9 rows) + xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize"; + xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize"; +(11 rows) -- Text XPath expressions evaluation SELECT xpath('/value', data) FROM xmltest; diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 5e48a67b8e2c7..4e8f65de0416b 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -583,6 +583,16 @@ ERROR: unsupported XML feature LINE 1: ...EATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as ... ^ DETAIL: This functionality requires the server to be built with libxml support. +CREATE VIEW xmlview10 AS SELECT xmlserialize(document '42' AS text indent); +ERROR: unsupported XML feature +LINE 1: ...TE VIEW xmlview10 AS SELECT xmlserialize(document '42' AS character varying no indent); +ERROR: unsupported XML feature +LINE 1: ...TE VIEW xmlview11 AS SELECT xmlserialize(document '', version no value, standalone yes); CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); +CREATE VIEW xmlview10 AS SELECT xmlserialize(document '42' AS text indent); +CREATE VIEW xmlview11 AS SELECT xmlserialize(document '42' AS character varying no indent); SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%' ORDER BY 1; - table_name | view_definition -------------+------------------------------------------------------------------------------------------------------------ + table_name | view_definition +------------+--------------------------------------------------------------------------------------------------------------------------------------- xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment; + xmlview10 | SELECT XMLSERIALIZE(DOCUMENT '42'::xml AS text INDENT) AS "xmlserialize"; + xmlview11 | SELECT (XMLSERIALIZE(DOCUMENT '42'::xml AS character varying NO INDENT))::character varying AS "xmlserialize"; xmlview2 | SELECT XMLCONCAT('hello'::xml, 'you'::xml) AS "xmlconcat"; xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement"; - xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" + + xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(name AS name, age AS age, salary AS pay)) AS "xmlelement" + | FROM emp; xmlview5 | SELECT XMLPARSE(CONTENT 'x'::text STRIP WHITESPACE) AS "xmlparse"; xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi"; xmlview7 | SELECT XMLROOT(''::xml, VERSION NO VALUE, STANDALONE YES) AS "xmlroot"; - xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10)))::character(10) AS "xmlserialize"; - xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text) AS "xmlserialize"; -(9 rows) + xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::xml AS character(10) NO INDENT))::character(10) AS "xmlserialize"; + xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::xml AS text NO INDENT) AS "xmlserialize"; +(11 rows) -- Text XPath expressions evaluation SELECT xpath('/value', data) FROM xmltest; diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index bac0388ac11c2..4c3520ce8980f 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -219,6 +219,8 @@ CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar'); CREATE VIEW xmlview7 AS SELECT xmlroot(xml '', version no value, standalone yes); CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); +CREATE VIEW xmlview10 AS SELECT xmlserialize(document '42' AS text indent); +CREATE VIEW xmlview11 AS SELECT xmlserialize(document '42' AS character varying no indent); SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%' ORDER BY 1; From 7787d0bf175ca410fdc86cfa5ea03384ac63bd16 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Fri, 21 Feb 2025 14:21:29 +0530 Subject: [PATCH 239/796] Fix a WARNING for data origin discrepancies. Previously, a WARNING was issued at the time of defining a subscription with origin=NONE only when the publisher subscribed to the same table from other publishers, indicating potential data origination from different origins. However, the publisher can subscribe to the partition ancestors or partition children of the table from other publishers, which could also result in mixed-origin data inclusion. So, give a WARNING in those cases as well. Reported-by: Sergey Tatarintsev Author: Hou Zhijie Author: Shlok Kyal Reviewed-by: Vignesh C Reviewed-by: Amit Kapila Backpatch-through: 16, where it was introduced Discussion: https://postgr.es/m/5eda6a9c-63cf-404d-8a49-8dcb116a29f3@postgrespro.ru --- doc/src/sgml/ref/create_subscription.sgml | 12 ++- src/backend/commands/subscriptioncmds.c | 15 +-- src/test/subscription/t/030_origin.pl | 120 +++++++++++++++++++++- 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml index 740b7d9421041..c9c8dd440dc7e 100644 --- a/doc/src/sgml/ref/create_subscription.sgml +++ b/doc/src/sgml/ref/create_subscription.sgml @@ -526,12 +526,14 @@ CREATE SUBSCRIPTION subscription_name # substitute <pub-names> below with your publication name(s) to be queried SELECT DISTINCT PT.schemaname, PT.tablename -FROM pg_publication_tables PT, +FROM pg_publication_tables PT + JOIN pg_class C ON (C.relname = PT.tablename) + JOIN pg_namespace N ON (N.nspname = PT.schemaname), pg_subscription_rel PS - JOIN pg_class C ON (C.oid = PS.srrelid) - JOIN pg_namespace N ON (N.oid = C.relnamespace) -WHERE N.nspname = PT.schemaname AND - C.relname = PT.tablename AND +WHERE C.relnamespace = N.oid AND + (PS.srrelid = C.oid OR + C.oid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION + SELECT relid FROM pg_partition_tree(PS.srrelid))) AND PT.pubname IN (<pub-names>); diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 8ecb6e0bb87ad..9467f58a23df3 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -2012,11 +2012,12 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId) } /* - * Check and log a warning if the publisher has subscribed to the same table - * from some other publisher. This check is required only if "copy_data = true" - * and "origin = none" for CREATE SUBSCRIPTION and - * ALTER SUBSCRIPTION ... REFRESH statements to notify the user that data - * having origin might have been copied. + * Check and log a warning if the publisher has subscribed to the same table, + * its partition ancestors (if it's a partition), or its partition children (if + * it's a partitioned table), from some other publishers. This check is + * required only if "copy_data = true" and "origin = none" for CREATE + * SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH statements to notify the + * user that data having origin might have been copied. * * This check need not be performed on the tables that are already added * because incremental sync for those tables will happen through WAL and the @@ -2046,7 +2047,9 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications, "SELECT DISTINCT P.pubname AS pubname\n" "FROM pg_publication P,\n" " LATERAL pg_get_publication_tables(P.pubname) GPT\n" - " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid),\n" + " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR" + " GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION" + " SELECT relid FROM pg_partition_tree(PS.srrelid))),\n" " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n" "WHERE C.oid = GPT.relid AND P.pubname IN ("); get_publications_str(publications, &cmd, true); diff --git a/src/test/subscription/t/030_origin.pl b/src/test/subscription/t/030_origin.pl index 056561f00841c..c0c627ee83a99 100644 --- a/src/test/subscription/t/030_origin.pl +++ b/src/test/subscription/t/030_origin.pl @@ -204,9 +204,123 @@ $node_B->wait_for_catchup($subname_AB2); # clear the operations done by this test -$node_A->safe_psql('postgres', "DROP TABLE tab_new"); -$node_B->safe_psql('postgres', "DROP TABLE tab_new"); -$node_A->safe_psql('postgres', "DROP SUBSCRIPTION $subname_AB2"); +$node_A->safe_psql( + 'postgres', qq( +DROP TABLE tab_new; +DROP SUBSCRIPTION $subname_AB2; +DROP SUBSCRIPTION $subname_AB; +DROP PUBLICATION tap_pub_A; +)); +$node_B->safe_psql( + 'postgres', qq( +DROP TABLE tab_new; +DROP SUBSCRIPTION $subname_BA; +DROP PUBLICATION tap_pub_B; +)); + +############################################################################### +# Specifying origin = NONE and copy_data = on must raise WARNING if we subscribe +# to a partitioned table and this table contains any remotely originated data. +# +# node_B +# __________________________ +# | tab_main | --------------> node_C (tab_main) +# |__________________________| +# | tab_part1 | tab_part2 | <-------------- node_A (tab_part2) +# |____________|_____________| +# | tab_part2_1 | +# |_____________| +# +# node_B +# __________________________ +# | tab_main | +# |__________________________| +# | tab_part1 | tab_part2 | <-------------- node_A (tab_part2) +# |____________|_____________| +# | tab_part2_1 | --------------> node_C (tab_part2_1) +# |_____________| +############################################################################### + +# create a table on node A which will act as a source for a partition on node B +$node_A->safe_psql( + 'postgres', qq( +CREATE TABLE tab_part2(a int); +CREATE PUBLICATION tap_pub_A FOR TABLE tab_part2; +)); + +# create a partition table on node B +$node_B->safe_psql( + 'postgres', qq( +CREATE TABLE tab_main(a int) PARTITION BY RANGE(a); +CREATE TABLE tab_part1 PARTITION OF tab_main FOR VALUES FROM (0) TO (5); +CREATE TABLE tab_part2(a int) PARTITION BY RANGE(a); +CREATE TABLE tab_part2_1 PARTITION OF tab_part2 FOR VALUES FROM (5) TO (10); +ALTER TABLE tab_main ATTACH PARTITION tab_part2 FOR VALUES FROM (5) to (10); +CREATE SUBSCRIPTION tap_sub_A_B CONNECTION '$node_A_connstr' PUBLICATION tap_pub_A; +)); + +# create a table on node C +$node_C->safe_psql( + 'postgres', qq( +CREATE TABLE tab_main(a int); +CREATE TABLE tab_part2_1(a int); +)); + +# create a logical replication setup between node B and node C with +# subscription on node C having origin = NONE and copy_data = on +$node_B->safe_psql( + 'postgres', qq( +CREATE PUBLICATION tap_pub_B FOR TABLE tab_main WITH (publish_via_partition_root); +CREATE PUBLICATION tap_pub_B_2 FOR TABLE tab_part2_1; +)); + +($result, $stdout, $stderr) = $node_C->psql( + 'postgres', " + CREATE SUBSCRIPTION tap_sub_B_C CONNECTION '$node_B_connstr' PUBLICATION tap_pub_B WITH (origin = none, copy_data = on); +"); + +# A warning must be logged as a partition 'tab_part2' in node B is subscribed to +# node A so partition 'tab_part2' can have remotely originated data +like( + $stderr, + qr/WARNING: ( [A-Z0-9]+:)? subscription "tap_sub_b_c" requested copy_data with origin = NONE but might copy data that had a different origin/, + "Create subscription with origin = none and copy_data when the publisher's partition is subscribing from different origin" +); +$node_C->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_B_C"); + +($result, $stdout, $stderr) = $node_C->psql( + 'postgres', " + CREATE SUBSCRIPTION tap_sub_B_C CONNECTION '$node_B_connstr' PUBLICATION tap_pub_B_2 WITH (origin = none, copy_data = on); +"); + +# A warning must be logged as ancestor of table 'tab_part2_1' in node B is +# subscribed to node A so table 'tab_part2_1' can have remotely originated +# data +like( + $stderr, + qr/WARNING: ( [A-Z0-9]+:)? subscription "tap_sub_b_c" requested copy_data with origin = NONE but might copy data that had a different origin/, + "Create subscription with origin = none and copy_data when the publisher's ancestor is subscribing from different origin" +); + +# clear the operations done by this test +$node_C->safe_psql( + 'postgres', qq( +DROP SUBSCRIPTION tap_sub_B_C; +DROP TABLE tab_main; +DROP TABLE tab_part2_1; +)); +$node_B->safe_psql( + 'postgres', qq( +DROP SUBSCRIPTION tap_sub_A_B; +DROP PUBLICATION tap_pub_B; +DROP PUBLICATION tap_pub_B_2; +DROP TABLE tab_main; +)); +$node_A->safe_psql( + 'postgres', qq( +DROP PUBLICATION tap_pub_A; +DROP TABLE tab_part2; +)); # shutdown $node_B->stop('fast'); From c8d8431a0624949e2fca1c500e65b844d043528c Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 21 Feb 2025 20:37:36 +0900 Subject: [PATCH 240/796] Fix cross-version upgrades with XMLSERIALIZE(NO INDENT) Dumps from versions older than v16 do not know about NO INDENT in a XMLSERIALIZE() clause. This commit adjusts AdjustUpgrade.pm so as NO INDENT is discarded in the contents of the new dump adjusted for comparison when the old version is v15 or older. This should be enough to make the cross-version upgrade tests pass. Per report from buildfarm member crake. Oversight in 984410b92326. Reviewed-by: Andrew Dunstan Discussion: https://postgr.es/m/88b183f1-ebf9-4f51-9144-3704380ccae7@dunslane.net Backpatch-through: 16 --- src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm index 3cec72d9d4ff2..db833ba251717 100644 --- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm +++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm @@ -627,6 +627,12 @@ sub adjust_new_dumpfile # Version comments will certainly not match. $dump =~ s/^-- Dumped from database version.*\n//mg; + # pre-v16 dumps do not know about XMLSERIALIZE(NO INDENT). + if ($old_version < 16) + { + $dump =~ s/XMLSERIALIZE\((.*)? NO INDENT\)/XMLSERIALIZE\($1\)/mg; + } + if ($old_version < 14) { # Suppress noise-word uses of IN in CREATE/ALTER PROCEDURE. From 2ba3af41a60dbf950f9a750185f118c47335e153 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 21 Feb 2025 11:16:57 -0500 Subject: [PATCH 241/796] Make test portlock logic work with meson Previously the portlock logic, added in 9b4eafcaf41, didn't actually work properly when the tests were run via meson. 9b4eafcaf41 used the MESON_BUILD_ROOT environment variable to determine the directory for the port lock directory, but that's never set for running the tests. That meant that each test used its own portlock dir, unless the PG_TEST_PORT_DIR environment variable was set. Fix the problem by setting top_builddir for the environment. That's also used for the autoconf/make build. Backpatch back to 16, where meson support was added. Reported-by: Zharkov Roman Reviewed-by: Andrew Dunstan Backpatch-through: 16 --- meson.build | 2 ++ src/test/perl/PostgreSQL/Test/Cluster.pm | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 0e26f322d958d..7c6483055443b 100644 --- a/meson.build +++ b/meson.build @@ -3386,6 +3386,8 @@ test_initdb_template = meson.build_root() / 'tmp_install' / 'initdb-template' test_env.set('PG_REGRESS', pg_regress.full_path()) test_env.set('REGRESS_SHLIB', regress_module.full_path()) test_env.set('INITDB_TEMPLATE', test_initdb_template) +# for Cluster.pm's portlock logic +test_env.set('top_builddir', meson.build_root()) # Test suites that are not safe by default but can be run if selected # by the user via the whitespace-separated list in variable PG_TEST_EXTRA. diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index 7e97cc6e66e05..f2d9afd398fbb 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -167,9 +167,7 @@ INIT $portdir = $ENV{PG_TEST_PORT_DIR}; # Otherwise, try to use a directory at the top of the build tree # or as a last resort use the tmp_check directory - my $build_dir = - $ENV{MESON_BUILD_ROOT} - || $ENV{top_builddir} + my $build_dir = $ENV{top_builddir} || $PostgreSQL::Test::Utils::tmp_check; $portdir ||= "$build_dir/portlock"; $portdir =~ s!\\!/!g; From 934e997e016c470808f8fe61e8cede1aa90919a3 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Fri, 21 Feb 2025 13:03:29 -0500 Subject: [PATCH 242/796] doc: clarify default checksum behavior in non-master branches Also simplify and correct data checksum wording in master now that it is the default. PG 13 did not have the awkward wording. Reported-by: Felix Reviewed-by: Laurenz Albe Discussion: https://postgr.es/m/173928241056.707.3989867022954178032@wrigleys.postgresql.org Backpatch-through: 14 --- doc/src/sgml/wal.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/wal.sgml b/doc/src/sgml/wal.sgml index e7f409ddd6430..dc3c061256d4a 100644 --- a/doc/src/sgml/wal.sgml +++ b/doc/src/sgml/wal.sgml @@ -246,7 +246,7 @@ - Checksums are normally enabled when the cluster is initialized using initdb. They can also be enabled or disabled at a later time as an offline operation. Data checksums are enabled or disabled at the full cluster From 6f9f56dcbd58c978ae98428148362f0808184cf6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 21 Feb 2025 13:37:12 -0500 Subject: [PATCH 243/796] Fix pg_dumpall to cope with dangling OIDs in pg_auth_members. There is a race condition between "GRANT role" and "DROP ROLE", which allows GRANT to install pg_auth_members entries that refer to dropped roles. (Commit 6566133c5 prevented that for the grantor field, but not for the granted or grantee roles.) We'll soon fix that, at least in HEAD, but pg_dumpall needs to cope with the situation in case of pre-existing inconsistency. As pg_dumpall stands, it will emit invalid commands like 'GRANT foo TO ""', which causes pg_upgrade to fail. Fix it to emit warnings and skip those GRANTs, instead. There was some discussion of removing the problem by changing dumpRoleMembership's query to use JOIN not LEFT JOIN, but that would result in silently ignoring such entries. It seems better to produce a warning. Pre-v16 branches already coped with dangling grantor OIDs by simply omitting the GRANTED BY clause. I left that behavior as-is, although it's somewhat inconsistent with the behavior of later branches. Reported-by: Virender Singla Discussion: https://postgr.es/m/CAM6Zo8woa62ZFHtMKox6a4jb8qQ=w87R2L0K8347iE-juQL2EA@mail.gmail.com Backpatch-through: 13 --- src/bin/pg_dump/pg_dumpall.c | 66 +++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index c0b8467a2fc72..d5282ecb17a1a 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -966,6 +966,13 @@ dumpRoleMembership(PGconn *conn) total; bool dump_grantors; bool dump_grant_options; + int i_role; + int i_member; + int i_grantor; + int i_roleid; + int i_memberid; + int i_grantorid; + int i_admin_option; int i_inherit_option; int i_set_option; @@ -975,6 +982,10 @@ dumpRoleMembership(PGconn *conn) * they didn't have ADMIN OPTION on the role, or a user that no longer * existed. To avoid dump and restore failures, don't dump the grantor * when talking to an old server version. + * + * Also, in older versions the roleid and/or member could be role OIDs + * that no longer exist. If we find such cases, print a warning and skip + * the entry. */ dump_grantors = (PQserverVersion(conn) >= 160000); @@ -986,8 +997,10 @@ dumpRoleMembership(PGconn *conn) /* Generate and execute query. */ printfPQExpBuffer(buf, "SELECT ur.rolname AS role, " "um.rolname AS member, " - "ug.oid AS grantorid, " "ug.rolname AS grantor, " + "a.roleid AS roleid, " + "a.member AS memberid, " + "a.grantor AS grantorid, " "a.admin_option"); if (dump_grant_options) appendPQExpBufferStr(buf, ", a.inherit_option, a.set_option"); @@ -996,8 +1009,15 @@ dumpRoleMembership(PGconn *conn) "LEFT JOIN %s um on um.oid = a.member " "LEFT JOIN %s ug on ug.oid = a.grantor " "WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')" - "ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog); + "ORDER BY 1,2,3", role_catalog, role_catalog, role_catalog); res = executeQuery(conn, buf->data); + i_role = PQfnumber(res, "role"); + i_member = PQfnumber(res, "member"); + i_grantor = PQfnumber(res, "grantor"); + i_roleid = PQfnumber(res, "roleid"); + i_memberid = PQfnumber(res, "memberid"); + i_grantorid = PQfnumber(res, "grantorid"); + i_admin_option = PQfnumber(res, "admin_option"); i_inherit_option = PQfnumber(res, "inherit_option"); i_set_option = PQfnumber(res, "set_option"); @@ -1021,24 +1041,32 @@ dumpRoleMembership(PGconn *conn) total = PQntuples(res); while (start < total) { - char *role = PQgetvalue(res, start, 0); + char *role = PQgetvalue(res, start, i_role); int i; bool *done; int remaining; int prev_remaining = 0; rolename_hash *ht; + /* If we hit a null roleid, we're done (nulls sort to the end). */ + if (PQgetisnull(res, start, i_role)) + { + /* translator: %s represents a numeric role OID */ + pg_log_warning("found orphaned pg_auth_members entry for role %s", + PQgetvalue(res, start, i_roleid)); + break; + } + /* All memberships for a single role should be adjacent. */ for (end = start; end < total; ++end) { char *otherrole; - otherrole = PQgetvalue(res, end, 0); + otherrole = PQgetvalue(res, end, i_role); if (strcmp(role, otherrole) != 0) break; } - role = PQgetvalue(res, start, 0); remaining = end - start; done = pg_malloc0(remaining * sizeof(bool)); ht = rolename_create(remaining, NULL); @@ -1078,10 +1106,30 @@ dumpRoleMembership(PGconn *conn) if (done[i - start]) continue; - member = PQgetvalue(res, i, 1); - grantorid = PQgetvalue(res, i, 2); - grantor = PQgetvalue(res, i, 3); - admin_option = PQgetvalue(res, i, 4); + /* Complain about, then ignore, entries with orphaned OIDs. */ + if (PQgetisnull(res, i, i_member)) + { + /* translator: %s represents a numeric role OID */ + pg_log_warning("found orphaned pg_auth_members entry for role %s", + PQgetvalue(res, i, i_memberid)); + done[i - start] = true; + --remaining; + continue; + } + if (PQgetisnull(res, i, i_grantor)) + { + /* translator: %s represents a numeric role OID */ + pg_log_warning("found orphaned pg_auth_members entry for role %s", + PQgetvalue(res, i, i_grantorid)); + done[i - start] = true; + --remaining; + continue; + } + + member = PQgetvalue(res, i, i_member); + grantor = PQgetvalue(res, i, i_grantor); + grantorid = PQgetvalue(res, i, i_grantorid); + admin_option = PQgetvalue(res, i, i_admin_option); if (dump_grant_options) set_option = PQgetvalue(res, i, i_set_option); From 5decd283115dd0f4be9600980868e403166d0a08 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Mon, 24 Feb 2025 18:03:29 +0700 Subject: [PATCH 244/796] Silence warning in older versions of Valgrind Due to misunderstanding on my part, commit 235328ee4 did not go far enough to silence older versions of Valgrind. For those, it was the bit scan that was problematic, not the subsequent bit-masking operation. To fix, use the unaligned path for the trailing bytes. Since we don't have a bit scan here anymore, also remove some comments and endian-specific coding around that. Reported-by: Anton A. Melnikov Discussion: https://postgr.es/m/f3aa2d45-3b28-41c5-9499-a1bc30e0f8ec@postgrespro.ru Backpatch-through: 17 --- src/include/common/hashfn_unstable.h | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/src/include/common/hashfn_unstable.h b/src/include/common/hashfn_unstable.h index e67a4d3158d08..101f118077e37 100644 --- a/src/include/common/hashfn_unstable.h +++ b/src/include/common/hashfn_unstable.h @@ -14,8 +14,6 @@ #ifndef HASHFN_UNSTABLE_H #define HASHFN_UNSTABLE_H -#include "port/pg_bitutils.h" -#include "port/pg_bswap.h" /* * fasthash is a modification of code taken from @@ -262,26 +260,13 @@ fasthash_accum_cstring_aligned(fasthash_state *hs, const char *str) /* * For every chunk of input, check for zero bytes before mixing into the - * hash. The chunk with zeros must contain the NUL terminator. We arrange - * so that zero_byte_low tells us not only that a zero exists, but also - * where it is, so we can hash the remainder of the string. - * - * The haszero64 calculation will set bits corresponding to the lowest - * byte where a zero exists, so that suffices for little-endian machines. - * For big-endian machines, we would need bits set for the highest zero - * byte in the chunk, since the trailing junk past the terminator could - * contain additional zeros. haszero64 does not give us that, so we - * byteswap the chunk first. + * hash. The chunk with zeros must contain the NUL terminator. */ for (;;) { uint64 chunk = *(uint64 *) str; -#ifdef WORDS_BIGENDIAN - zero_byte_low = haszero64(pg_bswap64(chunk)); -#else zero_byte_low = haszero64(chunk); -#endif if (zero_byte_low) break; @@ -290,13 +275,8 @@ fasthash_accum_cstring_aligned(fasthash_state *hs, const char *str) str += FH_SIZEOF_ACCUM; } - /* - * The byte corresponding to the NUL will be 0x80, so the rightmost bit - * position will be in the range 7, 15, ..., 63. Turn this into byte - * position by dividing by 8. - */ - remainder = pg_rightmost_one_pos64(zero_byte_low) / BITS_PER_BYTE; - fasthash_accum(hs, str, remainder); + /* mix in remaining bytes */ + remainder = fasthash_accum_cstring_unaligned(hs, str); str += remainder; return str - start; From ab052b55dd7559dd59ce0840be54697b0e873065 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Mon, 24 Feb 2025 14:03:07 -0800 Subject: [PATCH 245/796] Fix assertion when decoding XLOG_PARAMETER_CHANGE on promoted primary. When a standby replays an XLOG_PARAMETER_CHANGE record that lowers wal_level below logical, we invalidate all logical slots in hot standby mode. However, if this record was replayed while not in hot standby mode, logical slots could remain valid even after promotion, potentially causing an assertion failure during WAL record decoding. To fix this issue, this commit adds a check for hot_standby status when restoring a logical replication slot on standbys. This check ensures that logical slots are invalidated when they become incompatible due to insufficient wal_level during recovery. Backpatch to v16 where logical decoding on standby was introduced. Reviewed-by: Amit Kapila Reviewed-by: Bertrand Drouvot Discussion: https://postgr.es/m/CAD21AoABoFwGY_Rh2aeE6tEq3HkJxf0c6UeOXn4VV9v6BAQPSw%40mail.gmail.com Backpatch-through: 16 --- src/backend/replication/slot.c | 29 +++++++++++--- .../t/035_standby_logical_decoding.pl | 38 +++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 419f070ab71a9..780d22afbca51 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -2327,12 +2327,29 @@ RestoreSlotFromDisk(const char *name) * NB: Changing the requirements here also requires adapting * CheckSlotRequirements() and CheckLogicalDecodingRequirements(). */ - if (cp.slotdata.database != InvalidOid && wal_level < WAL_LEVEL_LOGICAL) - ereport(FATAL, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"", - NameStr(cp.slotdata.name)), - errhint("Change \"wal_level\" to be \"logical\" or higher."))); + if (cp.slotdata.database != InvalidOid) + { + if (wal_level < WAL_LEVEL_LOGICAL) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"", + NameStr(cp.slotdata.name)), + errhint("Change \"wal_level\" to be \"logical\" or higher."))); + + /* + * In standby mode, the hot standby must be enabled. This check is + * necessary to ensure logical slots are invalidated when they become + * incompatible due to insufficient wal_level. Otherwise, if the + * primary reduces wal_level < logical while hot standby is disabled, + * logical slots would remain valid even after promotion. + */ + if (StandbyMode && !EnableHotStandby) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = \"off\"", + NameStr(cp.slotdata.name)), + errhint("Change \"hot_standby\" to be \"on\"."))); + } else if (wal_level < WAL_LEVEL_REPLICA) ereport(FATAL, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index 4628f9fb80635..aeb79f51e7131 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -345,6 +345,44 @@ sub wait_until_vacuum_can_remove \$psql_subscriber{subscriber_stderr}, IPC::Run::timeout($default_timeout)); +################################################## +# Test that the standby requires hot_standby to be +# enabled for pre-existing logical slots. +################################################## + +# create the logical slots +$node_standby->create_logical_slot_on_standby($node_primary, 'restart_test'); +$node_standby->stop; +$node_standby->append_conf('postgresql.conf', qq[hot_standby = off]); + +# Use run_log instead of $node_standby->start because this test expects +# that the server ends with an error during startup. +run_log( + [ + 'pg_ctl', + '--pgdata' => $node_standby->data_dir, + '--log' => $node_standby->logfile, + 'start', + ]); + +# wait for postgres to terminate +foreach my $i (0 .. 10 * $PostgreSQL::Test::Utils::timeout_default) +{ + last if !-f $node_standby->data_dir . '/postmaster.pid'; + usleep(100_000); +} + +# Confirm that the server startup fails with an expected error +my $logfile = slurp_file($node_standby->logfile()); +ok( $logfile =~ + qr/FATAL: .* logical replication slot ".*" exists on the standby, but "hot_standby" = "off"/, + "the standby ends with an error during startup because hot_standby was disabled" +); +$node_standby->adjust_conf('postgresql.conf', 'hot_standby', 'on'); +$node_standby->start; +$node_standby->safe_psql('postgres', + qq[SELECT pg_drop_replication_slot('restart_test')]); + ################################################## # Test that logical decoding on the standby # behaves correctly. From b9a3ad50ea96d09d612283e411c9e9d70e5acbff Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Tue, 25 Feb 2025 09:22:16 +0530 Subject: [PATCH 246/796] Doc: Fix pg_copy_logical_replication_slot description. This commit documents that the failover option is not copied when using the pg_copy_logical_replication_slot function. In passing, we modify the comments in the function clarifying the reason for this behavior. Reported-by: Author: Hou Zhijie Reviewed-by: Amit Kapila Backpatch-through: 17, where it was introduced Discussion: https://postgr.es/m/173976850802.682632.11315364077431550250@wrigleys.postgresql.org --- doc/src/sgml/func.sgml | 4 ++++ src/backend/replication/slotfuncs.c | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1c5e4bc573202..d8b27903593fd 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29118,6 +29118,10 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset from the same LSN as the source logical slot. Both temporary and plugin are optional; if they are omitted, the values of the source slot are used. + The failover option of the source logical slot + is not copied and is set to false by default. This + is to avoid the risk of being unable to continue logical replication + after failover to standby where the slot is being synchronized. diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index dd6c1d5a7e353..43c3cc7336b62 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -698,13 +698,18 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) * hence pass find_startpoint false. confirmed_flush will be set * below, by copying from the source slot. * - * To avoid potential issues with the slot synchronization where the - * restart_lsn of a replication slot can go backward, we set the - * failover option to false here. This situation occurs when a slot - * on the primary server is dropped and immediately replaced with a - * new slot of the same name, created by copying from another existing - * slot. However, the slot synchronization will only observe the - * restart_lsn of the same slot going backward. + * We don't copy the failover option to prevent potential issues with + * slot synchronization. For instance, if a slot was synchronized to + * the standby, then dropped on the primary, and immediately recreated + * by copying from another existing slot with much earlier restart_lsn + * and confirmed_flush_lsn, the slot synchronization would only + * observe the LSN of the same slot moving backward. As slot + * synchronization does not copy the restart_lsn and + * confirmed_flush_lsn backward (see update_local_synced_slot() for + * details), if a failover happens before the primary's slot catches + * up, logical replication cannot continue using the synchronized slot + * on the promoted standby because the slot retains the restart_lsn + * and confirmed_flush_lsn that are much later than expected. */ create_logical_replication_slot(NameStr(*dst_name), plugin, From d0fb06de3b5785019e8ee5da1b595bcb61ae1036 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 27 Feb 2025 14:05:55 +0900 Subject: [PATCH 247/796] pg_amcheck: Fix inconsistency in memory freeing The function in charge of freeing the memory from a result created by PQescapeIdentifier() has to be PQfreemem(), to ensure that both allocation and free come from libpq, but one spot in pg_amcheck was missing that. Oversight in b859d94c6389. Author: Ranier Vilela Reviewed-by: vignesh C Discussion: https://postgr.es/m/CAEudQArD_nKSnYCNUZiPPsJ2tNXgRmLbXGSOrH1vpOF_XtP0Vg@mail.gmail.com Discussion: https://postgr.es/m/CAEudQArbTWVSbxq608GRmXJjnNSQ0B6R7CSffNnj2hPWMUsRNg@mail.gmail.com Backpatch-through: 14 --- src/bin/pg_amcheck/pg_amcheck.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c index 0c05cf58bcee1..8844bfb6a0e45 100644 --- a/src/bin/pg_amcheck/pg_amcheck.c +++ b/src/bin/pg_amcheck/pg_amcheck.c @@ -559,7 +559,7 @@ main(int argc, char *argv[]) executeCommand(conn, install_sql, opts.echo); pfree(install_sql); - pfree(schema); + PQfreemem(schema); } /* From 829c175abde2b804f7b418715ab62027aa6b4b93 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 28 Feb 2025 10:15:32 +0900 Subject: [PATCH 248/796] pg_upgrade: Fix inconsistency in memory freeing The function in charge of freeing the memory from a result created by PQescapeIdentifier() has to be PQfreemem(), to ensure that both allocation and free come from libpq. One spot in pg_upgrade was not respecting that for pg_database's datlocale (daticulocale in v16) when the collation provider is libc (aka datlocale/daticulocale is NULL) with an allocation done using pg_strdup() and a free with PQfreemem(). The code is changed to always use PQescapeLiteral() when processing the input. Oversight in 9637badd9f92. This commit is similar to 48e4ae9a0707 and 5b94e2753439. Author: Michael Paquier Co-authored-by: Ranier Vilela Discussion: https://postgr.es/m/Z601RQxTmIUohdkV@paquier.xyz Backpatch-through: 16 --- src/bin/pg_upgrade/pg_upgrade.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index af370768b6027..4fcd4bac153c0 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -408,6 +408,7 @@ set_locale_and_encoding(void) char *datcollate_literal; char *datctype_literal; char *datlocale_literal = NULL; + char *datlocale_src; DbLocaleInfo *locale = old_cluster.template0; prep_status("Setting locale and encoding for new cluster"); @@ -421,12 +422,10 @@ set_locale_and_encoding(void) datctype_literal = PQescapeLiteral(conn_new_template1, locale->db_ctype, strlen(locale->db_ctype)); - if (locale->db_locale) - datlocale_literal = PQescapeLiteral(conn_new_template1, - locale->db_locale, - strlen(locale->db_locale)); - else - datlocale_literal = pg_strdup("NULL"); + datlocale_src = locale->db_locale ? locale->db_locale : "NULL"; + datlocale_literal = PQescapeLiteral(conn_new_template1, + datlocale_src, + strlen(datlocale_src)); /* update template0 in new cluster */ if (GET_MAJOR_VERSION(new_cluster.major_version) >= 1700) From c64e2e967866b6ffae5fb81d6bb743f0b3bbc784 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 1 Mar 2025 14:22:56 -0500 Subject: [PATCH 249/796] Fix pg_strtof() to not crash on NULL endptr. We had managed not to notice this simple oversight because none of our calls exercised the case --- until commit 8f427187d. That led to pg_dump crashing on any platform that uses this code (currently Cygwin and Mingw). Even though there's no immediate bug in the back branches, backpatch, because a non-POSIX-compliant strtof() substitute is trouble waiting to happen for extensions or future back-patches. Diagnosed-by: Alexander Lakhin Author: Tom Lane Discussion: https://postgr.es/m/339b3902-4e98-4e31-a744-94e43b7b9292@gmail.com Backpatch-through: 13 --- src/port/strtof.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/port/strtof.c b/src/port/strtof.c index b93bafd1dfa6d..43d88f8f802b8 100644 --- a/src/port/strtof.c +++ b/src/port/strtof.c @@ -31,15 +31,18 @@ pg_strtof(const char *nptr, char **endptr) { int caller_errno = errno; float fresult; + char *myendptr; errno = 0; - fresult = (strtof) (nptr, endptr); + fresult = (strtof) (nptr, &myendptr); + if (endptr) + *endptr = myendptr; if (errno) { /* On error, just return the error to the caller. */ return fresult; } - else if ((*endptr == nptr) || isnan(fresult) || + else if ((myendptr == nptr) || isnan(fresult) || ((fresult >= FLT_MIN || fresult <= -FLT_MIN) && !isinf(fresult))) { /* @@ -53,7 +56,8 @@ pg_strtof(const char *nptr, char **endptr) else { /* - * Try again. errno is already 0 here. + * Try again. errno is already 0 here, and we assume that the endptr + * won't be any different. */ double dresult = strtod(nptr, NULL); From 1b579bd9d80c37937f221e39aa5b28d6c0e941d3 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 3 Mar 2025 12:43:29 -0500 Subject: [PATCH 250/796] Fix broken handling of domains in atthasmissing logic. If a domain type has a default, adding a column of that type (without any explicit DEFAULT clause) failed to install the domain's default value in existing rows, instead leaving the new column null. This is unexpected, and it used to work correctly before v11. The cause is confusion in the atthasmissing mechanism about which default value to install: we'd only consider installing an explicitly-specified default, and then we'd decide that no table rewrite is needed. To fix, take the responsibility for filling attmissingval out of StoreAttrDefault, and instead put it into ATExecAddColumn's existing logic that derives the correct value to fill the new column with. Also, centralize the logic that determines the need for default-related table rewriting there, instead of spreading it over four or five places. In the back branches, we'll leave the attmissingval-filling code in StoreAttrDefault even though it's now dead, for fear that some extension may be depending on that functionality to exist there. A separate HEAD-only patch will clean up the now-useless code. Reported-by: jian he Author: jian he Author: Tom Lane Discussion: https://postgr.es/m/CACJufxHFssPvkP1we7WMhPD_1kwgbG52o=kQgL+TnVoX5LOyCQ@mail.gmail.com Backpatch-through: 13 --- src/backend/catalog/heap.c | 62 ++++++++++++++-- src/backend/catalog/pg_attrdef.c | 6 ++ src/backend/commands/tablecmds.c | 85 +++++++++++++++------- src/include/catalog/heap.h | 5 +- src/test/regress/expected/fast_default.out | 65 +++++++++++++++++ src/test/regress/sql/fast_default.sql | 44 +++++++++++ 6 files changed, 233 insertions(+), 34 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 794bf3dc6364a..71eb3b2432c48 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -69,6 +69,7 @@ #include "pgstat.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/inval.h" @@ -2033,6 +2034,60 @@ RelationClearMissing(Relation rel) table_close(attr_rel, RowExclusiveLock); } +/* + * StoreAttrMissingVal + * + * Set the missing value of a single attribute. + */ +void +StoreAttrMissingVal(Relation rel, AttrNumber attnum, Datum missingval) +{ + Datum valuesAtt[Natts_pg_attribute] = {0}; + bool nullsAtt[Natts_pg_attribute] = {0}; + bool replacesAtt[Natts_pg_attribute] = {0}; + Relation attrrel; + Form_pg_attribute attStruct; + HeapTuple atttup, + newtup; + + /* This is only supported for plain tables */ + Assert(rel->rd_rel->relkind == RELKIND_RELATION); + + /* Fetch the pg_attribute row */ + attrrel = table_open(AttributeRelationId, RowExclusiveLock); + + atttup = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(RelationGetRelid(rel)), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(atttup)) /* shouldn't happen */ + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, RelationGetRelid(rel)); + attStruct = (Form_pg_attribute) GETSTRUCT(atttup); + + /* Make a one-element array containing the value */ + missingval = PointerGetDatum(construct_array(&missingval, + 1, + attStruct->atttypid, + attStruct->attlen, + attStruct->attbyval, + attStruct->attalign)); + + /* Update the pg_attribute row */ + valuesAtt[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(true); + replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true; + + valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval; + replacesAtt[Anum_pg_attribute_attmissingval - 1] = true; + + newtup = heap_modify_tuple(atttup, RelationGetDescr(attrrel), + valuesAtt, nullsAtt, replacesAtt); + CatalogTupleUpdate(attrrel, &newtup->t_self, newtup); + + /* clean up */ + ReleaseSysCache(atttup); + table_close(attrrel, RowExclusiveLock); +} + /* * SetAttrMissing * @@ -2360,13 +2415,8 @@ AddRelationNewConstraints(Relation rel, castNode(Const, expr)->constisnull)) continue; - /* If the DEFAULT is volatile we cannot use a missing value */ - if (colDef->missingMode && - contain_volatile_functions_after_planning((Expr *) expr)) - colDef->missingMode = false; - defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal, - colDef->missingMode); + false); cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c index 003ae70b4d255..65296bd9e0555 100644 --- a/src/backend/catalog/pg_attrdef.c +++ b/src/backend/catalog/pg_attrdef.c @@ -120,6 +120,12 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; + /* + * Note: this code is dead so far as core Postgres is concerned, + * because no caller passes add_column_mode = true anymore. We keep + * it in back branches on the slight chance that some extension is + * depending on it. + */ if (rel->rd_rel->relkind == RELKIND_RELATION && add_column_mode && !attgenerated) { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 507f6c17d6ae2..9b5a44ba4f2c4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7209,14 +7209,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attribute->attnum; rawEnt->raw_default = copyObject(colDef->raw_default); - - /* - * Attempt to skip a complete table rewrite by storing the specified - * DEFAULT value outside of the heap. This may be disabled inside - * AddRelationNewConstraints if the optimization cannot be applied. - */ - rawEnt->missingMode = (!colDef->generated); - + rawEnt->missingMode = false; /* XXX vestigial */ rawEnt->generated = colDef->generated; /* @@ -7228,13 +7221,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Make the additional catalog changes visible */ CommandCounterIncrement(); - - /* - * Did the request for a missing value work? If not we'll have to do a - * rewrite - */ - if (!rawEnt->missingMode) - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } /* @@ -7251,9 +7237,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, * rejects nulls. If there are any domain constraints then we construct * an explicit NULL default value that will be passed through * CoerceToDomain processing. (This is a tad inefficient, since it causes - * rewriting the table which we really don't have to do, but the present - * design of domain processing doesn't offer any simple way of checking - * the constraints more directly.) + * rewriting the table which we really wouldn't have to do; but we do it + * to preserve the historical behavior that such a failure will be raised + * only if the table currently contains some rows.) * * Note: we use build_column_default, and not just the cooked default * returned by AddRelationNewConstraints, so that the right thing happens @@ -7272,6 +7258,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, */ if (RELKIND_HAS_STORAGE(relkind)) { + bool has_domain_constraints; + bool has_missing = false; + /* * For an identity column, we can't use build_column_default(), * because the sequence ownership isn't set yet. So do it manually. @@ -7284,14 +7273,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, nve->typeId = attribute->atttypid; defval = (Expr *) nve; - - /* must do a rewrite for identity columns */ - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } else defval = (Expr *) build_column_default(rel, attribute->attnum); - if (!defval && DomainHasConstraints(attribute->atttypid)) + /* Build CoerceToDomain(NULL) expression if needed */ + has_domain_constraints = DomainHasConstraints(attribute->atttypid); + if (!defval && has_domain_constraints) { Oid baseTypeId; int32 baseTypeMod; @@ -7317,18 +7305,61 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, { NewColumnValue *newval; + /* Prepare defval for execution, either here or in Phase 3 */ + defval = expression_planner(defval); + + /* Add the new default to the newvals list */ newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); newval->attnum = attribute->attnum; - newval->expr = expression_planner(defval); + newval->expr = defval; newval->is_generated = (colDef->generated != '\0'); tab->newvals = lappend(tab->newvals, newval); - } - if (DomainHasConstraints(attribute->atttypid)) - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + /* + * Attempt to skip a complete table rewrite by storing the + * specified DEFAULT value outside of the heap. This is only + * allowed for plain relations and non-generated columns, and the + * default expression can't be volatile (stable is OK). Note that + * contain_volatile_functions deems CoerceToDomain immutable, but + * here we consider that coercion to a domain with constraints is + * volatile; else it might fail even when the table is empty. + */ + if (rel->rd_rel->relkind == RELKIND_RELATION && + !colDef->generated && + !has_domain_constraints && + !contain_volatile_functions((Node *) defval)) + { + EState *estate; + ExprState *exprState; + Datum missingval; + bool missingIsNull; + + /* Evaluate the default expression */ + estate = CreateExecutorState(); + exprState = ExecPrepareExpr(defval, estate); + missingval = ExecEvalExpr(exprState, + GetPerTupleExprContext(estate), + &missingIsNull); + /* If it turns out NULL, nothing to do; else store it */ + if (!missingIsNull) + { + StoreAttrMissingVal(rel, attribute->attnum, missingval); + has_missing = true; + } + FreeExecutorState(estate); + } + else + { + /* + * Failed to use missing mode. We have to do a table rewrite + * to install the value. + */ + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } + } - if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing) + if (!has_missing) { /* * If the new column is NOT NULL, and there is no missing value, diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index fc5a13f0b8318..1e376579b0f33 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -29,7 +29,7 @@ typedef struct RawColumnDefault { AttrNumber attnum; /* attribute to attach default to */ Node *raw_default; /* default value (untransformed parse tree) */ - bool missingMode; /* true if part of add column processing */ + bool missingMode; /* obsolete, no longer used */ char generated; /* attgenerated setting */ } RawColumnDefault; @@ -117,6 +117,9 @@ extern List *AddRelationNewConstraints(Relation rel, const char *queryString); extern void RelationClearMissing(Relation rel); + +extern void StoreAttrMissingVal(Relation rel, AttrNumber attnum, + Datum missingval); extern void SetAttrMissing(Oid relid, char *attname, char *value); extern Node *cookDefault(ParseState *pstate, diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index 59365dad964a9..3944812c298c2 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -245,6 +245,71 @@ SELECT comp(); (1 row) DROP TABLE T; +-- Test domains with default value for table rewrite. +CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant +CREATE DOMAIN domain2 AS int DEFAULT random(min=>10, max=>100); -- volatile +CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable +CREATE DOMAIN domain4 AS text[] + DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[]; +CREATE TABLE t2 (a domain1); +INSERT INTO t2 VALUES (1),(2); +-- no table rewrite +ALTER TABLE t2 ADD COLUMN b domain1 default 3; +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + attnum | attname | atthasmissing | atthasdef | attmissingval +--------+---------+---------------+-----------+--------------- + 1 | a | f | f | + 2 | b | t | t | {3} +(2 rows) + +-- table rewrite should happen +ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3); +NOTICE: rewriting table t2 for reason 2 +-- no table rewrite +ALTER TABLE t2 ADD COLUMN d domain4; +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + attnum | attname | atthasmissing | atthasdef | attmissingval +--------+---------+---------------+-----------+----------------------------------- + 1 | a | f | f | + 2 | b | f | t | + 3 | c | f | t | + 4 | d | t | f | {"{This,is,abcd,the,real,world}"} +(4 rows) + +-- table rewrite should happen +ALTER TABLE t2 ADD COLUMN e domain2; +NOTICE: rewriting table t2 for reason 2 +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + attnum | attname | atthasmissing | atthasdef | attmissingval +--------+---------+---------------+-----------+--------------- + 1 | a | f | f | + 2 | b | f | t | + 3 | c | f | t | + 4 | d | f | f | + 5 | e | f | f | +(5 rows) + +SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2; + a | b | c_ok | d | e_ok +---+---+------+-------------------------------+------ + 1 | 3 | t | {This,is,abcd,the,real,world} | t + 2 | 3 | t | {This,is,abcd,the,real,world} | t +(2 rows) + +DROP TABLE t2; +DROP DOMAIN domain1; +DROP DOMAIN domain2; +DROP DOMAIN domain3; +DROP DOMAIN domain4; DROP FUNCTION foo(INT); -- Fall back to full rewrite for volatile expressions CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index dc9df78a35d5d..a21b406e65936 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -237,6 +237,50 @@ SELECT comp(); DROP TABLE T; +-- Test domains with default value for table rewrite. +CREATE DOMAIN domain1 AS int DEFAULT 11; -- constant +CREATE DOMAIN domain2 AS int DEFAULT random(min=>10, max=>100); -- volatile +CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable +CREATE DOMAIN domain4 AS text[] + DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[]; + +CREATE TABLE t2 (a domain1); +INSERT INTO t2 VALUES (1),(2); + +-- no table rewrite +ALTER TABLE t2 ADD COLUMN b domain1 default 3; + +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + +-- table rewrite should happen +ALTER TABLE t2 ADD COLUMN c domain3 default left(random()::text,3); + +-- no table rewrite +ALTER TABLE t2 ADD COLUMN d domain4; + +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + +-- table rewrite should happen +ALTER TABLE t2 ADD COLUMN e domain2; + +SELECT attnum, attname, atthasmissing, atthasdef, attmissingval +FROM pg_attribute +WHERE attnum > 0 AND attrelid = 't2'::regclass +ORDER BY attnum; + +SELECT a, b, length(c) = 3 as c_ok, d, e >= 10 as e_ok FROM t2; + +DROP TABLE t2; +DROP DOMAIN domain1; +DROP DOMAIN domain2; +DROP DOMAIN domain3; +DROP DOMAIN domain4; DROP FUNCTION foo(INT); -- Fall back to full rewrite for volatile expressions From 0a817f94de361a10410176e9225fd208f25bff88 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Tue, 4 Mar 2025 16:17:19 +0900 Subject: [PATCH 251/796] Avoid NullTest deduction for clone clauses In commit b262ad440, we introduced an optimization that reduces an IS NOT NULL qual on a column defined as NOT NULL to constant true, and an IS NULL qual on a NOT NULL column to constant false, provided we can prove that the input expression of the NullTest is not nullable by any outer join. This deduction happens after we have generated multiple clones of the same qual condition to cope with commuted-left-join cases. However, performing the NullTest deduction for clone clauses can be unsafe, because we don't have a reliable way to determine if the input expression of a NullTest is non-nullable: nullingrel bits in clone clauses may not reflect reality, so we dare not draw conclusions from clones about whether Vars are guaranteed not-null. To fix, we check whether the given RestrictInfo is a clone clause in restriction_is_always_true and restriction_is_always_false, and avoid performing any reduction if it is. There are several ensuing plan changes in predicate.out, and we have to modify the tests to ensure that they continue to test what they are intended to. Additionally, this fix causes the test case added in f00ab1fd1 to no longer trigger the bug that commit fixed, so we also remove that test case. Back-patch to v17 where this bug crept in. Reported-by: Ronald Cruz Diagnosed-by: Tom Lane Author: Richard Guo Reviewed-by: Tom Lane Discussion: https://postgr.es/m/f5320d3d-77af-4ce8-b9c3-4715ff33f213@rentec.com Backpatch-through: 17 --- src/backend/optimizer/plan/initsplan.c | 18 +++ src/test/regress/expected/predicate.out | 175 +++++++++++++++++------- src/test/regress/sql/predicate.sql | 56 ++++++-- 3 files changed, 187 insertions(+), 62 deletions(-) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index cd0ce25bd8694..3b21f8ae5968d 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -2740,6 +2740,15 @@ bool restriction_is_always_true(PlannerInfo *root, RestrictInfo *restrictinfo) { + /* + * For a clone clause, we don't have a reliable way to determine if the + * input expression of a NullTest is non-nullable: nullingrel bits in + * clone clauses may not reflect reality, so we dare not draw conclusions + * from clones about whether Vars are guaranteed not-null. + */ + if (restrictinfo->has_clone || restrictinfo->is_clone) + return false; + /* Check for NullTest qual */ if (IsA(restrictinfo->clause, NullTest)) { @@ -2789,6 +2798,15 @@ bool restriction_is_always_false(PlannerInfo *root, RestrictInfo *restrictinfo) { + /* + * For a clone clause, we don't have a reliable way to determine if the + * input expression of a NullTest is non-nullable: nullingrel bits in + * clone clauses may not reflect reality, so we dare not draw conclusions + * from clones about whether Vars are guaranteed not-null. + */ + if (restrictinfo->has_clone || restrictinfo->is_clone) + return false; + /* Check for NullTest qual */ if (IsA(restrictinfo->clause, NullTest)) { diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index 6f1cc0d54cd3f..b79037748b7e6 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -97,55 +97,50 @@ SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL; -- and b) its Var is not nullable by any outer joins EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; - QUERY PLAN -------------------------------------------------- + LEFT JOIN pred_tab t2 ON t1.a IS NOT NULL; + QUERY PLAN +------------------------------------- Nested Loop Left Join -> Seq Scan on pred_tab t1 -> Materialize - -> Nested Loop Left Join - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(7 rows) + -> Seq Scan on pred_tab t2 +(4 rows) -- Ensure the IS_NOT_NULL qual is not ignored when columns are made nullable -- by an outer join EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 + FULL JOIN pred_tab t2 ON t1.a = t2.a LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; QUERY PLAN ------------------------------------------- Nested Loop Left Join Join Filter: (t2.a IS NOT NULL) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize + -> Merge Full Join + Merge Cond: (t1.a = t2.a) + -> Sort + Sort Key: t1.a + -> Seq Scan on pred_tab t1 + -> Sort + Sort Key: t2.a -> Seq Scan on pred_tab t2 -> Materialize -> Seq Scan on pred_tab t3 -(9 rows) +(12 rows) -- Ensure the IS_NULL qual is reduced to constant-FALSE, since a) it's on a NOT -- NULL column, and b) its Var is not nullable by any outer joins EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NULL AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- + LEFT JOIN pred_tab t2 ON t1.a IS NULL; + QUERY PLAN +-------------------------------- Nested Loop Left Join + Join Filter: false -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) - -> Seq Scan on pred_tab t2 - -> Result - One-Time Filter: false -(8 rows) + -> Result + One-Time Filter: false +(5 rows) -- Ensure the IS_NULL qual is not reduced to constant-FALSE when the column is -- nullable by an outer join @@ -172,55 +167,50 @@ SELECT * FROM pred_tab t1 -- Ensure the OR clause is ignored when an OR branch is provably always true EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; - QUERY PLAN -------------------------------------------------- + LEFT JOIN pred_tab t2 ON t1.a IS NOT NULL OR t2.b = 1; + QUERY PLAN +------------------------------------- Nested Loop Left Join -> Seq Scan on pred_tab t1 -> Materialize - -> Nested Loop Left Join - -> Seq Scan on pred_tab t2 - -> Materialize - -> Seq Scan on pred_tab t3 -(7 rows) + -> Seq Scan on pred_tab t2 +(4 rows) -- Ensure the NullTest is not ignored when the column is nullable by an outer -- join EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 + FULL JOIN pred_tab t2 ON t1.a = t2.a LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; QUERY PLAN --------------------------------------------------- Nested Loop Left Join Join Filter: ((t2.a IS NOT NULL) OR (t2.b = 1)) - -> Nested Loop Left Join - Join Filter: (t1.a = 1) - -> Seq Scan on pred_tab t1 - -> Materialize + -> Merge Full Join + Merge Cond: (t1.a = t2.a) + -> Sort + Sort Key: t1.a + -> Seq Scan on pred_tab t1 + -> Sort + Sort Key: t2.a -> Seq Scan on pred_tab t2 -> Materialize -> Seq Scan on pred_tab t3 -(9 rows) +(12 rows) -- Ensure the OR clause is reduced to constant-FALSE when all OR branches are -- provably false EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON (t2.a IS NULL OR t2.c IS NULL) AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- + LEFT JOIN pred_tab t2 ON (t1.a IS NULL OR t1.c IS NULL); + QUERY PLAN +-------------------------------- Nested Loop Left Join + Join Filter: false -> Seq Scan on pred_tab t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) - -> Seq Scan on pred_tab t2 - -> Result - One-Time Filter: false -(8 rows) + -> Result + One-Time Filter: false +(5 rows) -- Ensure the OR clause is not reduced to constant-FALSE when a column is -- made nullable from an outer join @@ -290,3 +280,84 @@ SELECT * FROM pred_parent WHERE a IS NULL; (2 rows) DROP TABLE pred_parent, pred_child; +-- Validate we do not reduce a clone clause to a constant true or false +CREATE TABLE pred_tab (a int, b int); +CREATE TABLE pred_tab_notnull (a int, b int NOT NULL); +INSERT INTO pred_tab VALUES (1, 1); +INSERT INTO pred_tab VALUES (2, 2); +INSERT INTO pred_tab_notnull VALUES (2, 2); +INSERT INTO pred_tab_notnull VALUES (3, 3); +ANALYZE pred_tab; +ANALYZE pred_tab_notnull; +-- Ensure the IS_NOT_NULL qual is not reduced to constant true and removed +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NOT NULL; + QUERY PLAN +--------------------------------------------------------------- + Nested Loop Left Join + -> Seq Scan on pred_tab t1 + -> Materialize + -> Nested Loop Left Join + Join Filter: (t3.b IS NOT NULL) + -> Nested Loop Left Join + Join Filter: (t2.a = t3.a) + -> Seq Scan on pred_tab t2 + -> Materialize + -> Seq Scan on pred_tab_notnull t3 + -> Materialize + -> Seq Scan on pred_tab t4 +(12 rows) + +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NOT NULL; + a | b | a | b | a | b | a | b +---+---+---+---+---+---+---+--- + 1 | 1 | 1 | 1 | | | | + 1 | 1 | 2 | 2 | 2 | 2 | 1 | 1 + 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 + 2 | 2 | 1 | 1 | | | | + 2 | 2 | 2 | 2 | 2 | 2 | 1 | 1 + 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 +(6 rows) + +-- Ensure the IS_NULL qual is not reduced to constant false +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NULL AND t3.a IS NOT NULL; + QUERY PLAN +-------------------------------------------------------------------- + Nested Loop Left Join + -> Seq Scan on pred_tab t1 + -> Materialize + -> Nested Loop Left Join + Join Filter: ((t3.b IS NULL) AND (t3.a IS NOT NULL)) + -> Nested Loop Left Join + Join Filter: (t2.a = t3.a) + -> Seq Scan on pred_tab t2 + -> Materialize + -> Seq Scan on pred_tab_notnull t3 + -> Materialize + -> Seq Scan on pred_tab t4 +(12 rows) + +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NULL AND t3.a IS NOT NULL; + a | b | a | b | a | b | a | b +---+---+---+---+---+---+---+--- + 1 | 1 | 1 | 1 | | | | + 1 | 1 | 2 | 2 | 2 | 2 | | + 2 | 2 | 1 | 1 | | | | + 2 | 2 | 2 | 2 | 2 | 2 | | +(4 rows) + +DROP TABLE pred_tab; +DROP TABLE pred_tab_notnull; diff --git a/src/test/regress/sql/predicate.sql b/src/test/regress/sql/predicate.sql index 63f6a7786f369..9dcb81b1bc52f 100644 --- a/src/test/regress/sql/predicate.sql +++ b/src/test/regress/sql/predicate.sql @@ -64,22 +64,20 @@ SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL; -- and b) its Var is not nullable by any outer joins EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; + LEFT JOIN pred_tab t2 ON t1.a IS NOT NULL; -- Ensure the IS_NOT_NULL qual is not ignored when columns are made nullable -- by an outer join EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 + FULL JOIN pred_tab t2 ON t1.a = t2.a LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL; -- Ensure the IS_NULL qual is reduced to constant-FALSE, since a) it's on a NOT -- NULL column, and b) its Var is not nullable by any outer joins EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NULL AND t2.b = 1; + LEFT JOIN pred_tab t2 ON t1.a IS NULL; -- Ensure the IS_NULL qual is not reduced to constant-FALSE when the column is -- nullable by an outer join @@ -95,22 +93,20 @@ SELECT * FROM pred_tab t1 -- Ensure the OR clause is ignored when an OR branch is provably always true EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; + LEFT JOIN pred_tab t2 ON t1.a IS NOT NULL OR t2.b = 1; -- Ensure the NullTest is not ignored when the column is nullable by an outer -- join EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON t1.a = 1 + FULL JOIN pred_tab t2 ON t1.a = t2.a LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; -- Ensure the OR clause is reduced to constant-FALSE when all OR branches are -- provably false EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 - LEFT JOIN pred_tab t2 ON TRUE - LEFT JOIN pred_tab t3 ON (t2.a IS NULL OR t2.c IS NULL) AND t2.b = 1; + LEFT JOIN pred_tab t2 ON (t1.a IS NULL OR t1.c IS NULL); -- Ensure the OR clause is not reduced to constant-FALSE when a column is -- made nullable from an outer join @@ -147,3 +143,43 @@ EXPLAIN (COSTS OFF) SELECT * FROM pred_parent WHERE a IS NULL; DROP TABLE pred_parent, pred_child; + +-- Validate we do not reduce a clone clause to a constant true or false +CREATE TABLE pred_tab (a int, b int); +CREATE TABLE pred_tab_notnull (a int, b int NOT NULL); + +INSERT INTO pred_tab VALUES (1, 1); +INSERT INTO pred_tab VALUES (2, 2); + +INSERT INTO pred_tab_notnull VALUES (2, 2); +INSERT INTO pred_tab_notnull VALUES (3, 3); + +ANALYZE pred_tab; +ANALYZE pred_tab_notnull; + +-- Ensure the IS_NOT_NULL qual is not reduced to constant true and removed +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NOT NULL; + +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NOT NULL; + +-- Ensure the IS_NULL qual is not reduced to constant false +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NULL AND t3.a IS NOT NULL; + +SELECT * FROM pred_tab t1 + LEFT JOIN pred_tab t2 ON TRUE + LEFT JOIN pred_tab_notnull t3 ON t2.a = t3.a + LEFT JOIN pred_tab t4 ON t3.b IS NULL AND t3.a IS NOT NULL; + +DROP TABLE pred_tab; +DROP TABLE pred_tab_notnull; From 1727747af0ec79b83ebc7c7fd8ec148ca32f7ac6 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 4 Mar 2025 12:08:27 +0100 Subject: [PATCH 252/796] doc: Expand version compatibility for pg_basebackup features This updates the paragraph on backwards compatitibility for server features to include --incremental which only works on servers with v17 or newer. Backpatch down to v17 where incremental backup was added. Author: David G. Johnston Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CAKFQuwZYfZyeTkS3g2Ovw84TsxHa796xnf-u5kfgn_auyxZk0Q@mail.gmail.com Backpatch-through: 17 --- doc/src/sgml/ref/pg_basebackup.sgml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml index ad36166c6850e..95b9b302e950e 100644 --- a/doc/src/sgml/ref/pg_basebackup.sgml +++ b/doc/src/sgml/ref/pg_basebackup.sgml @@ -1005,10 +1005,11 @@ PostgreSQL documentation pg_basebackup works with servers of the same - or an older major version, down to 9.1. However, WAL streaming mode (-X - stream) only works with server version 9.3 and later, and tar format + or older major version, down to 9.1. However, WAL streaming mode (-X + stream) only works with server version 9.3 and later, the tar format (--format=tar) only works with server version 9.5 - and later. + and later, and incremental backup (--incremental) only works + with server version 17 and later. From 26974b3f994f29eb17722055475411a34921786e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Tue, 4 Mar 2025 20:07:30 +0100 Subject: [PATCH 253/796] Fix ALTER TABLE error message This bogus error message was introduced in 2013 by commit f177cbfe676d, because of misunderstanding the processCASbits() API; at the time, no test cases were added that would be affected by this change. Only in ca87c415e2fc was one added (along with a couple of typos), with an XXX note that the error message was bogus. Fix the whole, add some test cases. Backpatch all the way back. Reviewed-by: Nathan Bossart Discussion: https://postgr.es/m/202503041822.aobpqke3igvb@alvherre.pgsql --- src/backend/parser/gram.y | 2 +- src/test/regress/expected/foreign_key.out | 6 +++++- src/test/regress/sql/foreign_key.sql | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index bca627c5463e2..08e2195fa48a4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2649,7 +2649,7 @@ alter_table_cmd: n->def = (Node *) c; c->contype = CONSTR_FOREIGN; /* others not supported, yet */ c->conname = $3; - processCASbits($4, @4, "ALTER CONSTRAINT statement", + processCASbits($4, @4, "FOREIGN KEY", &c->deferrable, &c->initdeferred, NULL, NULL, yyscanner); diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index fe6a1015f21bf..a7fe9407d25c2 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1278,11 +1278,15 @@ DETAIL: Key (fk)=(20) is not present in table "pktable". COMMIT; -- try additional syntax ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; --- illegal option +-- illegal options ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED; ERROR: constraint declared INITIALLY DEFERRED must be DEFERRABLE LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ... ^ +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NO INHERIT; +ERROR: FOREIGN KEY constraints cannot be marked NO INHERIT +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID; +ERROR: FOREIGN KEY constraints cannot be marked NOT VALID -- test order of firing of FK triggers when several RI-induced changes need to -- be made to the same row. This was broken by subtransaction-related -- changes in 8.0. diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 8c4e4c7c83358..6aa675c72864a 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -970,8 +970,10 @@ COMMIT; -- try additional syntax ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; --- illegal option +-- illegal options ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NO INHERIT; +ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID; -- test order of firing of FK triggers when several RI-induced changes need to -- be made to the same row. This was broken by subtransaction-related From 31c87a1912ab22db46531b24814823e89fbb971e Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 5 Mar 2025 10:29:08 -0500 Subject: [PATCH 254/796] ci: Upgrade FreeBSD image Upgrade to the current stable version. To avoid needing commits like this in the future, the CI image name now doesn't contain the OS version number anymore. Backpatch to all versions with CI support, we don't want to generate CI images for multiple FreeBSD versions. Author: Nazir Bilal Yavuz Discussion: https://postgr.es/m/CAN55FZ3_P4JJ6tWZafjf-_XbHgG6DQGXhH-y6Yp78_bwBJjcww@mail.gmail.com --- .cirrus.tasks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index 8cbadc5849bb2..aa7c7dcd34dbd 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -126,13 +126,13 @@ task: task: - name: FreeBSD - 13 - Meson + name: FreeBSD - Meson env: CPUS: 4 BUILD_JOBS: 4 TEST_JOBS: 8 - IMAGE_FAMILY: pg-ci-freebsd-13 + IMAGE_FAMILY: pg-ci-freebsd DISK_SIZE: 50 CCACHE_DIR: /tmp/ccache_dir From c8abd7ef614c702395ce3fddb13910c7936ca44c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 6 Mar 2025 11:54:27 -0500 Subject: [PATCH 255/796] Fix some performance issues in GIN query startup. If a GIN index search had a lot of search keys (for example, "jsonbcol ?| array[]" with tens of thousands of array elements), both ginFillScanKey() and startScanKey() took O(N^2) time. Worse, those loops were uncancelable for lack of CHECK_FOR_INTERRUPTS. The problem in ginFillScanKey() is the brute-force search key de-duplication done in ginFillScanEntry(). The most expedient solution seems to be to just stop trying to de-duplicate once there are "too many" search keys. We could imagine working harder, say by using a sort-and-unique algorithm instead of brute force compare-all-the-keys. But it seems unlikely to be worth the trouble. There is no correctness issue here, since the code already allowed duplicate keys if any extra_data is present. The problem in startScanKey() is the loop that attempts to identify the first non-required search key. In the submitted test case, that vainly tests all the key positions, and each iteration takes O(N) time. One part of that is that it's reinitializing the entryRes[] array from scratch each time, which is entirely unnecessary given that the triConsistentFn isn't supposed to scribble on its input. We can easily adjust the array contents incrementally instead. The other part of it is that the triConsistentFn may itself take O(N) time (and does in this test case). This is all extremely brute force: in simple cases with AND or OR semantics, we could know without any looping whatever that all or none of the keys are required. But GIN opclasses don't have any API for exposing that knowledge, so at least in the short run there is little to be done about that. Put in a CHECK_FOR_INTERRUPTS so that at least the loop is cancelable. These two changes together resolve the primary complaint that the test query doesn't respond promptly to cancel interrupts. Also, while they don't completely eliminate the O(N^2) behavior, they do provide quite a nice speedup for mid-sized examples. Bug: #18831 Reported-by: Niek Author: Tom Lane Discussion: https://postgr.es/m/18831-e845ac44ebc5dd36@postgresql.org Backpatch-through: 13 --- src/backend/access/gin/ginget.c | 10 ++++++---- src/backend/access/gin/ginscan.c | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index 0b4f2ebadb6c8..17601bb56b270 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -556,16 +556,18 @@ startScanKey(GinState *ginstate, GinScanOpaque so, GinScanKey key) qsort_arg(entryIndexes, key->nentries, sizeof(int), entryIndexByFrequencyCmp, key); + for (i = 1; i < key->nentries; i++) + key->entryRes[entryIndexes[i]] = GIN_MAYBE; for (i = 0; i < key->nentries - 1; i++) { /* Pass all entries <= i as FALSE, and the rest as MAYBE */ - for (j = 0; j <= i; j++) - key->entryRes[entryIndexes[j]] = GIN_FALSE; - for (j = i + 1; j < key->nentries; j++) - key->entryRes[entryIndexes[j]] = GIN_MAYBE; + key->entryRes[entryIndexes[i]] = GIN_FALSE; if (key->triConsistentFn(key) == GIN_FALSE) break; + + /* Make this loop interruptible in case there are many keys */ + CHECK_FOR_INTERRUPTS(); } /* i is now the last required entry. */ diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index af24d38544eb9..d832f2707fb79 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -68,8 +68,13 @@ ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum, * * Entries with non-null extra_data are never considered identical, since * we can't know exactly what the opclass might be doing with that. + * + * Also, give up de-duplication once we have 100 entries. That avoids + * spending O(N^2) time on probably-fruitless de-duplication of large + * search-key sets. The threshold of 100 is arbitrary but matches + * predtest.c's threshold for what's a large array. */ - if (extra_data == NULL) + if (extra_data == NULL && so->totalentries < 100) { for (i = 0; i < so->totalentries; i++) { From cd24f0adf735d72a2cc18ea6b68bedb904337a35 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Fri, 7 Mar 2025 10:22:56 +0700 Subject: [PATCH 256/796] Doc: correct aggressive vacuum threshold for multixact members storage The threshold is two billion members, which was interpreted as 2GB in the documentation. Fix to reflect that each member takes up five bytes, which translates to about 10GB. This is not exact, because of page boundaries. While at it, mention the maximum size 20GB. This has been wrong since commit c552e171d16e, so backpatch to version 14. Author: Alex Friedman Reviewed-by: Sami Imseih Reviewed-by: Bertrand Drouvot Discussion: https://postgr.es/m/CACbFw60UOk6fCC02KsyT3OfU9Dnuq5roYxdw2aFisiN_p1L0bg@mail.gmail.com Backpatch-through: 14 --- doc/src/sgml/maintenance.sgml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 0be90bdc7efe1..89040942be2fe 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -793,10 +793,11 @@ HINT: Execute a database-wide VACUUM in that database. As a safety device, an aggressive vacuum scan will occur for any table whose multixact-age is greater than . Also, if the - storage occupied by multixacts members exceeds 2GB, aggressive vacuum + storage occupied by multixacts members exceeds about 10GB, aggressive vacuum scans will occur more often for all tables, starting with those that have the oldest multixact-age. Both of these kinds of aggressive - scans will occur even if autovacuum is nominally disabled. + scans will occur even if autovacuum is nominally disabled. The members storage + area can grow up to about 20GB before reaching wraparound. From 095352bab21f569eec26e017c5f5aa66f9b53af6 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 7 Mar 2025 15:23:09 -0600 Subject: [PATCH 257/796] Assert that wrapper_handler()'s argument is within expected range. pqsignal() already does a similar check, but strange Valgrind reports have us wondering if wrapper_handler() is somehow getting called with an invalid signal number. Reported-by: Tomas Vondra Suggested-by: Andres Freund Discussion: https://postgr.es/m/ace01111-f9ac-4f61-b1b1-8e9379415444%40vondra.me Backpatch-through: 17 --- src/port/pqsignal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/port/pqsignal.c b/src/port/pqsignal.c index bbd28da080531..9dadce8357dca 100644 --- a/src/port/pqsignal.c +++ b/src/port/pqsignal.c @@ -87,6 +87,9 @@ wrapper_handler(SIGNAL_ARGS) { int save_errno = errno; + Assert(postgres_signal_arg > 0); + Assert(postgres_signal_arg < PG_NSIG); + #ifndef FRONTEND /* @@ -139,6 +142,7 @@ pqsignal(int signo, pqsigfunc func) pqsigfunc ret; #endif + Assert(signo > 0); Assert(signo < PG_NSIG); if (func != SIG_IGN && func != SIG_DFL) From 8d5c4546770b77511c3f57e6b77d9f89504646aa Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 8 Mar 2025 11:24:22 -0500 Subject: [PATCH 258/796] Clear errno before calling strtol() in spell.c. Per POSIX, a caller of strtol() that wishes to check for errors must set errno to 0 beforehand. Several places in spell.c neglected that, so that they risked delivering a false overflow error in case errno had been ERANGE already. Given the lack of field reports, this case may be unreachable at present --- but it's surely trouble waiting to happen, so fix it. Author: Jacob Brazeal Discussion: https://postgr.es/m/CA+COZaBhsq6EromFm+knMJfzK6nTpG23zJ+K2=nfUQQXcj_xcQ@mail.gmail.com Backpatch-through: 13 --- src/backend/tsearch/spell.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/tsearch/spell.c b/src/backend/tsearch/spell.c index 9b1441fa1adae..b6918a3984a8e 100644 --- a/src/backend/tsearch/spell.c +++ b/src/backend/tsearch/spell.c @@ -374,6 +374,7 @@ getNextFlagFromString(IspellDict *Conf, char **sflagset, char *sflag) stop = (maxstep == 0); break; case FM_NUM: + errno = 0; s = strtol(*sflagset, &next, 10); if (*sflagset == next || errno == ERANGE) ereport(ERROR, @@ -1036,6 +1037,7 @@ setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry, char *next; int i; + errno = 0; i = strtol(s, &next, 10); if (s == next || errno == ERANGE) ereport(ERROR, @@ -1163,6 +1165,7 @@ getAffixFlagSet(IspellDict *Conf, char *s) int curaffix; char *end; + errno = 0; curaffix = strtol(s, &end, 10); if (s == end || errno == ERANGE) ereport(ERROR, @@ -1735,6 +1738,7 @@ NISortDictionary(IspellDict *Conf) if (*Conf->Spell[i]->p.flag != '\0') { + errno = 0; curaffix = strtol(Conf->Spell[i]->p.flag, &end, 10); if (Conf->Spell[i]->p.flag == end || errno == ERANGE) ereport(ERROR, From 56e7066f597ec2f21c3bdd876a0fa393aafad19f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 9 Mar 2025 13:11:20 -0400 Subject: [PATCH 259/796] Don't try to parallelize array_agg() on an anonymous record type. This doesn't work because record_recv requires the typmod that identifies the specific record type (in our session) and array_agg_deserialize has no convenient way to get that information. The result is an "input of anonymous composite types is not implemented" error. We could probably make this work if we had to, but it does not seem worth the trouble, given that it took this long to get a field report. Just shut off parallelization, as though record_recv didn't exist. Oversight in commit 16fd03e95. Back-patch to v16 where that came in. Reported-by: Kirill Zdornyy Diagnosed-by: Richard Guo Author: Tom Lane Reviewed-by: David Rowley Discussion: https://postgr.es/m/atLI5Kce2ie1zcYjU0w_kjtVaxiYbYGTihrkLDmGZQnRDD4pnXukIATaABbnIj9pUnelC4ESvCXMm4HAyHg-v61XABaKpERj0A2IXzJZM7g=@dineserve.com Backpatch-through: 16 --- src/backend/parser/parse_agg.c | 11 ++++++++++- src/test/regress/expected/aggregates.out | 20 +++++++++++++++++++- src/test/regress/sql/aggregates.sql | 9 +++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index b15d9b152ef0f..be88909da222d 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -1961,7 +1961,7 @@ resolve_aggregate_transtype(Oid aggfuncid, /* * agg_args_support_sendreceive - * Returns true if all non-byval of aggref's arg types have send and + * Returns true if all non-byval types of aggref's args have send and * receive functions. */ bool @@ -1976,6 +1976,15 @@ agg_args_support_sendreceive(Aggref *aggref) TargetEntry *tle = (TargetEntry *) lfirst(lc); Oid type = exprType((Node *) tle->expr); + /* + * RECORD is a special case: it has typsend/typreceive functions, but + * record_recv only works if passed the correct typmod to identify the + * specific anonymous record type. array_agg_deserialize cannot do + * that, so we have to disclaim support for the case. + */ + if (type == RECORDOID) + return false; + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "cache lookup failed for type %u", type); diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 1c1ca7573ad3a..30af83f23ccdb 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -2033,8 +2033,8 @@ explain (costs off) select * from v_pagg_test order by y; -> Parallel Seq Scan on pagg_test (13 rows) -set max_parallel_workers_per_gather = 0; -- Ensure results are the same without parallel aggregation. +set max_parallel_workers_per_gather = 0; select * from v_pagg_test order by y; y | tmin | tmax | tndistinct | bmin | bmax | bndistinct | amin | amax | andistinct | aamin | aamax | aandistinct ---+------+------+------------+------+------+------------+------+------+------------+-------+-------+------------- @@ -2050,6 +2050,24 @@ select * from v_pagg_test order by y; 9 | 19 | 4999 | 250 | 1019 | 999 | 250 | 19 | 4999 | 250 | 19 | 4999 | 250 (10 rows) +-- Check that we don't fail on anonymous record types. +set max_parallel_workers_per_gather = 2; +explain (costs off) +select array_dims(array_agg(s)) from (select * from pagg_test) s; + QUERY PLAN +-------------------------------------------- + Aggregate + -> Gather + Workers Planned: 2 + -> Parallel Seq Scan on pagg_test +(4 rows) + +select array_dims(array_agg(s)) from (select * from pagg_test) s; + array_dims +------------ + [1:5000] +(1 row) + -- Clean up reset max_parallel_workers_per_gather; reset bytea_output; diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 1a18ca3d8fe20..675b23add5962 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -805,11 +805,16 @@ select * from v_pagg_test order by y; -- Ensure parallel aggregation is actually being used. explain (costs off) select * from v_pagg_test order by y; -set max_parallel_workers_per_gather = 0; - -- Ensure results are the same without parallel aggregation. +set max_parallel_workers_per_gather = 0; select * from v_pagg_test order by y; +-- Check that we don't fail on anonymous record types. +set max_parallel_workers_per_gather = 2; +explain (costs off) +select array_dims(array_agg(s)) from (select * from pagg_test) s; +select array_dims(array_agg(s)) from (select * from pagg_test) s; + -- Clean up reset max_parallel_workers_per_gather; reset bytea_output; From 50316e4c3477e4ba46ca9d4b7e9fce299bda248a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 10 Mar 2025 10:22:08 -0400 Subject: [PATCH 260/796] Doc: improve description of window function processing. The previous wording talked about a "single pass over the data", which can be read as promising more than intended (to wit, that only one WindowAgg plan node will be used). What we promise is only what the SQL spec requires, namely that the data not get re-sorted between window functions with compatible PARTITION BY/ORDER BY clauses. Adjust the wording in hopes of making this clearer. Reported-by: Christopher Inokuchi Author: Tom Lane Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/CABde6B5va2wMsnM79u_x=n9KUgfKQje_pbLROEBmA9Ru5XWidw@mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/queries.sgml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml index 372cce1a487e3..a326960ff4dfb 100644 --- a/doc/src/sgml/queries.sgml +++ b/doc/src/sgml/queries.sgml @@ -1461,10 +1461,10 @@ GROUP BY GROUPING SETS ( When multiple window functions are used, all the window functions having - syntactically equivalent PARTITION BY and ORDER BY - clauses in their window definitions are guaranteed to be evaluated in a - single pass over the data. Therefore they will see the same sort ordering, - even if the ORDER BY does not uniquely determine an ordering. + equivalent PARTITION BY and ORDER BY + clauses in their window definitions are guaranteed to see the same + ordering of the input rows, even if the ORDER BY does + not uniquely determine the ordering. However, no guarantees are made about the evaluation of functions having different PARTITION BY or ORDER BY specifications. (In such cases a sort step is typically required between the passes of From 62e4c0907255856268efc276cf6ff396583d282a Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 10 Mar 2025 17:07:38 +0200 Subject: [PATCH 261/796] Fix snapshot used in logical replication index lookup The function calls GetLatestSnapshot() to acquire a fresh snapshot, makes it active, and was meant to pass it to table_tuple_lock(), but instead called GetLatestSnapshot() again to acquire yet another snapshot. It was harmless because the heap AM and all other known table AMs ignore the 'snapshot' argument anyway, but let's be tidy. In the long run, this perhaps should be redesigned so that snapshot was not needed in the first place. The table AM API uses TID + snapshot as the unique identifier for the row version, which is questionable when the row came from an index scan with a Dirty snapshot. You might lock a different row version when you use a different snapshot in the table_tuple_lock() call (a fresh MVCC snapshot) than in the index scan (DirtySnapshot). However, in the heap AM and other AMs where the TID alone identifies the row version, it doesn't matter. So for now, just fix the obvious albeit harmless bug. This has been wrong ever since the table AM API was introduced in commit 5db6df0c01, so backpatch to all supported versions. Discussion: https://www.postgresql.org/message-id/83d243d6-ad8d-4307-8b51-2ee5844f6230@iki.fi Backpatch-through: 13 --- src/backend/executor/execReplication.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index f18efdb6f2649..9a57698d2218a 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -250,7 +250,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid, PushActiveSnapshot(GetLatestSnapshot()); - res = table_tuple_lock(rel, &(outslot->tts_tid), GetLatestSnapshot(), + res = table_tuple_lock(rel, &(outslot->tts_tid), GetActiveSnapshot(), outslot, GetCurrentCommandId(false), lockmode, From ee29be844c87d62f79996154956515b69d2aa453 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 10 Mar 2025 18:54:58 +0200 Subject: [PATCH 262/796] Fix a few more redundant calls of GetLatestSnapshot() Commit 2367503177 fixed this in RelationFindReplTupleByIndex(), but I missed two other similar cases. Per report from Ranier Vilela. Discussion: https://www.postgresql.org/message-id/CAEudQArUT1dE45WN87F-Gb7XMy_hW6x1DFd3sqdhhxP-RMDa0Q@mail.gmail.com Backpatch-through: 13 --- src/backend/executor/execReplication.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 9a57698d2218a..cb1202e450678 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -434,7 +434,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, PushActiveSnapshot(GetLatestSnapshot()); - res = table_tuple_lock(rel, &(outslot->tts_tid), GetLatestSnapshot(), + res = table_tuple_lock(rel, &(outslot->tts_tid), GetActiveSnapshot(), outslot, GetCurrentCommandId(false), lockmode, From 6f73848f069efbdaf0315e0e6ae29b352cc61add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Tue, 11 Mar 2025 12:50:35 +0100 Subject: [PATCH 263/796] BRIN: be more strict about required support procs With improperly defined operator classes, it's possible to get a Postgres crash because we'd try to invoke a procedure that doesn't exist. This is because the code is being a bit too trusting that the opclass is correctly defined. Add some ereport(ERROR)s for cases where mandatory support procedures are not defined, transforming the crashes into errors. The particular case that was reported is an incomplete opclass in PostGIS. Backpatch all the way down to 13. Reported-by: Tobias Wendorff Diagnosed-by: David Rowley Reviewed-by: Tomas Vondra Discussion: https://postgr.es/m/fb6d9a35-6c8e-4869-af80-0a4944a793a4@tu-dortmund.de --- src/backend/access/brin/brin_bloom.c | 19 ++++--------- src/backend/access/brin/brin_inclusion.c | 31 ++++++++++++--------- src/backend/access/brin/brin_minmax_multi.c | 19 ++++--------- 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c index f94e2b0bfbd95..89c4b02ed9ee5 100644 --- a/src/backend/access/brin/brin_bloom.c +++ b/src/backend/access/brin/brin_bloom.c @@ -439,7 +439,6 @@ typedef struct BloomOpaque * consistency. We may need additional procs in the future. */ FmgrInfo extra_procinfos[BLOOM_MAX_PROCNUMS]; - bool extra_proc_missing[BLOOM_MAX_PROCNUMS]; } BloomOpaque; static FmgrInfo *bloom_get_procinfo(BrinDesc *bdesc, uint16 attno, @@ -715,27 +714,19 @@ bloom_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) */ opaque = (BloomOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; - /* - * If we already searched for this proc and didn't find it, don't bother - * searching again. - */ - if (opaque->extra_proc_missing[basenum]) - return NULL; - if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) { if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, procnum))) - { fmgr_info_copy(&opaque->extra_procinfos[basenum], index_getprocinfo(bdesc->bd_index, attno, procnum), bdesc->bd_context); - } else - { - opaque->extra_proc_missing[basenum] = true; - return NULL; - } + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg_internal("invalid opclass definition"), + errdetail_internal("The operator class is missing support function %d for column %d.", + procnum, attno)); } return &opaque->extra_procinfos[basenum]; diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 750276998c5a1..c09dad28ef985 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -82,7 +82,7 @@ typedef struct InclusionOpaque } InclusionOpaque; static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, - uint16 procnum); + uint16 procnum, bool missing_ok); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); @@ -179,7 +179,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS) * new value for emptiness; if it returns true, we need to set the * "contains empty" flag in the element (unless already set). */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_EMPTY); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_EMPTY, true); if (finfo != NULL && DatumGetBool(FunctionCall1Coll(finfo, colloid, newval))) { if (!DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY])) @@ -195,7 +195,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); /* Check if the new value is already contained. */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_CONTAINS); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_CONTAINS, true); if (finfo != NULL && DatumGetBool(FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], @@ -210,7 +210,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS) * it's not going to be used any longer. However, the BRIN framework * doesn't allow for the value not being present. Improve someday. */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE, true); if (finfo != NULL && !DatumGetBool(FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], @@ -221,8 +221,7 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS) } /* Finally, merge the new value to the existing union. */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); - Assert(finfo != NULL); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE, false); result = FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], newval); if (!attr->attbyval && @@ -506,7 +505,7 @@ brin_inclusion_union(PG_FUNCTION_ARGS) } /* Check if A and B are mergeable; if not, mark A unmergeable. */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE, true); if (finfo != NULL && !DatumGetBool(FunctionCall2Coll(finfo, colloid, col_a->bv_values[INCLUSION_UNION], @@ -517,8 +516,7 @@ brin_inclusion_union(PG_FUNCTION_ARGS) } /* Finally, merge B to A. */ - finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); - Assert(finfo != NULL); + finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE, false); result = FunctionCall2Coll(finfo, colloid, col_a->bv_values[INCLUSION_UNION], col_b->bv_values[INCLUSION_UNION]); @@ -539,10 +537,12 @@ brin_inclusion_union(PG_FUNCTION_ARGS) * Cache and return inclusion opclass support procedure * * Return the procedure corresponding to the given function support number - * or null if it is not exists. + * or null if it is not exists. If missing_ok is true and the procedure + * isn't set up for this opclass, return NULL instead of raising an error. */ static FmgrInfo * -inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) +inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum, + bool missing_ok) { InclusionOpaque *opaque; uint16 basenum = procnum - PROCNUM_BASE; @@ -564,13 +564,18 @@ inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) { if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, procnum))) - { fmgr_info_copy(&opaque->extra_procinfos[basenum], index_getprocinfo(bdesc->bd_index, attno, procnum), bdesc->bd_context); - } else { + if (!missing_ok) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg_internal("invalid opclass definition"), + errdetail_internal("The operator class is missing support function %d for column %d.", + procnum, attno)); + opaque->extra_proc_missing[basenum] = true; return NULL; } diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index e5d95de5d84a7..8a13cd59c0b03 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -111,7 +111,6 @@ typedef struct MinmaxMultiOpaque { FmgrInfo extra_procinfos[MINMAX_MAX_PROCNUMS]; - bool extra_proc_missing[MINMAX_MAX_PROCNUMS]; Oid cached_subtype; FmgrInfo strategy_procinfos[BTMaxStrategyNumber]; } MinmaxMultiOpaque; @@ -2872,27 +2871,19 @@ minmax_multi_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) */ opaque = (MinmaxMultiOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; - /* - * If we already searched for this proc and didn't find it, don't bother - * searching again. - */ - if (opaque->extra_proc_missing[basenum]) - return NULL; - if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) { if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, procnum))) - { fmgr_info_copy(&opaque->extra_procinfos[basenum], index_getprocinfo(bdesc->bd_index, attno, procnum), bdesc->bd_context); - } else - { - opaque->extra_proc_missing[basenum] = true; - return NULL; - } + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg_internal("invalid opclass definition"), + errdetail_internal("The operator class is missing support function %d for column %d.", + procnum, attno)); } return &opaque->extra_procinfos[basenum]; From b3d739460c7aef7c6c38f13c4ba040b9e235ee8a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 12 Mar 2025 11:47:19 -0400 Subject: [PATCH 264/796] Build whole-row Vars the same way during parsing and planning. makeWholeRowVar() has different rules for constructing a whole-row Var depending on the kind of RTE it's representing. This turns out to be problematic because the rewriter and planner can convert view RTEs and set-returning-function RTEs into subquery RTEs; so a whole-row Var made during planning might look different from one made by the parser. In isolation this doesn't cause any problem, but if a query contains Vars made both ways for the same varno, there are cross-checks in the executor that will complain. This manifests for UPDATE, DELETE, and MERGE queries that use whole-row table references. To fix, we need makeWholeRowVar() to produce the same result from an inlined RTE as it would have for the original. For an inlined view, we can use RangeTblEntry.relid to detect that this had been a view RTE. For inlined SRFs, make a data structure definition change akin to commit 47bb9db75, and say that we won't clear RangeTblEntry.functions until the end of planning. That allows makeWholeRowVar() to repeat what it would have done with the unmodified RTE. Reported-by: Duncan Sands Reported-by: Dean Rasheed Diagnosed-by: Tender Wang Author: Tom Lane Reviewed-by: Dean Rasheed Discussion: https://postgr.es/m/3518c50a-ab18-482f-b916-a37263622501@deepbluecap.com Backpatch-through: 13 --- src/backend/nodes/makefuncs.c | 51 +++++++++++++++++++- src/backend/optimizer/prep/prepjointree.c | 10 +++- src/test/regress/expected/returning.out | 57 +++++++++++++++++++++++ src/test/regress/sql/returning.sql | 24 ++++++++++ 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 61ac172a85748..9f3b4f200fb0c 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -159,6 +159,53 @@ makeWholeRowVar(RangeTblEntry *rte, varlevelsup); break; + case RTE_SUBQUERY: + + /* + * For a standard subquery, the Var should be of RECORD type. + * However, if we're looking at a subquery that was expanded from + * a view or SRF (only possible during planning), we must use the + * appropriate rowtype, so that the resulting Var has the same + * type that we would have produced from the original RTE. + */ + if (OidIsValid(rte->relid)) + { + /* Subquery was expanded from a view */ + toid = get_rel_type_id(rte->relid); + if (!OidIsValid(toid)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" does not have a composite type", + get_rel_name(rte->relid)))); + } + else if (rte->functions) + { + /* + * Subquery was expanded from a set-returning function. That + * would not have happened if there's more than one function + * or ordinality was requested. We also needn't worry about + * the allowScalar case, since the planner doesn't use that. + * Otherwise this must match the RTE_FUNCTION code below. + */ + Assert(!allowScalar); + fexpr = ((RangeTblFunction *) linitial(rte->functions))->funcexpr; + toid = exprType(fexpr); + if (!type_is_rowtype(toid)) + toid = RECORDOID; + } + else + { + /* Normal subquery-in-FROM */ + toid = RECORDOID; + } + result = makeVar(varno, + InvalidAttrNumber, + toid, + -1, + InvalidOid, + varlevelsup); + break; + case RTE_FUNCTION: /* @@ -215,8 +262,8 @@ makeWholeRowVar(RangeTblEntry *rte, default: /* - * RTE is a join, subselect, tablefunc, or VALUES. We represent - * this as a whole-row Var of RECORD type. (Note that in most + * RTE is a join, tablefunc, VALUES, CTE, etc. We represent these + * cases as a whole-row Var of RECORD type. (Note that in most * cases the Var will be expanded to a RowExpr during planning, * but that is not our concern here.) */ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index e7e8c1499f297..2c6faad4daefe 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -908,8 +908,14 @@ preprocess_function_rtes(PlannerInfo *root) rte->rtekind = RTE_SUBQUERY; rte->subquery = funcquery; rte->security_barrier = false; - /* Clear fields that should not be set in a subquery RTE */ - rte->functions = NIL; + + /* + * Clear fields that should not be set in a subquery RTE. + * However, we leave rte->functions filled in for the moment, + * in case makeWholeRowVar needs to consult it. We'll clear + * it in setrefs.c (see add_rte_to_flat_rtable) so that this + * abuse of the data structure doesn't escape the planner. + */ rte->funcordinality = false; } } diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index cb51bb86876db..a5ebc8acc0fed 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -286,6 +286,63 @@ SELECT * FROM voo; 16 | zoo2 (2 rows) +-- Check use of a whole-row variable for an un-flattenable view +CREATE TEMP VIEW foo_v AS SELECT * FROM foo OFFSET 0; +UPDATE foo SET f2 = foo_v.f2 FROM foo_v WHERE foo_v.f1 = foo.f1 + RETURNING foo_v; + foo_v +----------------- + (2,more,42,141) + (16,zoo2,57,99) +(2 rows) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +----+------+----+----- + 2 | more | 42 | 141 + 16 | zoo2 | 57 | 99 +(2 rows) + +-- Check use of a whole-row variable for an inlined set-returning function +CREATE FUNCTION foo_f() RETURNS SETOF foo AS + $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 + RETURNING foo_f; + foo_f +----------------- + (2,more,42,141) + (16,zoo2,57,99) +(2 rows) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +----+------+----+----- + 2 | more | 42 | 141 + 16 | zoo2 | 57 | 99 +(2 rows) + +DROP FUNCTION foo_f(); +-- As above, but SRF is defined to return a composite type +CREATE TYPE foo_t AS (f1 int, f2 text, f3 int, f4 int8); +CREATE FUNCTION foo_f() RETURNS SETOF foo_t AS + $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 + RETURNING foo_f; + foo_f +----------------- + (2,more,42,141) + (16,zoo2,57,99) +(2 rows) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +----+------+----+----- + 2 | more | 42 | 141 + 16 | zoo2 | 57 | 99 +(2 rows) + +DROP FUNCTION foo_f(); +DROP TYPE foo_t; -- Try a join case CREATE TEMP TABLE joinme (f2j text, other int); INSERT INTO joinme VALUES('more', 12345); diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql index a460f82fb7c84..8a2a2a5861d03 100644 --- a/src/test/regress/sql/returning.sql +++ b/src/test/regress/sql/returning.sql @@ -132,6 +132,30 @@ DELETE FROM foo WHERE f2 = 'zit' RETURNING *; SELECT * FROM foo; SELECT * FROM voo; +-- Check use of a whole-row variable for an un-flattenable view +CREATE TEMP VIEW foo_v AS SELECT * FROM foo OFFSET 0; +UPDATE foo SET f2 = foo_v.f2 FROM foo_v WHERE foo_v.f1 = foo.f1 + RETURNING foo_v; +SELECT * FROM foo; + +-- Check use of a whole-row variable for an inlined set-returning function +CREATE FUNCTION foo_f() RETURNS SETOF foo AS + $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 + RETURNING foo_f; +SELECT * FROM foo; +DROP FUNCTION foo_f(); + +-- As above, but SRF is defined to return a composite type +CREATE TYPE foo_t AS (f1 int, f2 text, f3 int, f4 int8); +CREATE FUNCTION foo_f() RETURNS SETOF foo_t AS + $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE; +UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1 + RETURNING foo_f; +SELECT * FROM foo; +DROP FUNCTION foo_f(); +DROP TYPE foo_t; + -- Try a join case CREATE TEMP TABLE joinme (f2j text, other int); From 0a1b6970093ab5c97d5f481911204efddb2abd96 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Wed, 12 Mar 2025 20:53:09 +0200 Subject: [PATCH 265/796] Handle interrupts while waiting on Append's async subplans We did not wake up on interrupts while waiting on async events on an async-capable append node. For example, if you tried to cancel the query, nothing would happen until one of the async subplans becomes readable. To fix, add WL_LATCH_SET to the WaitEventSet. Backpatch down to v14 where async Append execution was introduced. Discussion: https://www.postgresql.org/message-id/37a40570-f558-40d3-b5ea-5c2079b3b30b@iki.fi --- src/backend/executor/nodeAppend.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index ca0f54d676f4a..6d5bee668c437 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -1016,7 +1016,7 @@ ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result) static void ExecAppendAsyncEventWait(AppendState *node) { - int nevents = node->as_nasyncplans + 1; + int nevents = node->as_nasyncplans + 2; long timeout = node->as_syncdone ? -1 : 0; WaitEvent occurred_event[EVENT_BUFFER_SIZE]; int noccurred; @@ -1041,8 +1041,8 @@ ExecAppendAsyncEventWait(AppendState *node) } /* - * No need for further processing if there are no configured events other - * than the postmaster death event. + * No need for further processing if none of the subplans configured any + * events. */ if (GetNumRegisteredWaitEvents(node->as_eventset) == 1) { @@ -1051,6 +1051,21 @@ ExecAppendAsyncEventWait(AppendState *node) return; } + /* + * Add the process latch to the set, so that we wake up to process the + * standard interrupts with CHECK_FOR_INTERRUPTS(). + * + * NOTE: For historical reasons, it's important that this is added to the + * WaitEventSet after the ExecAsyncConfigureWait() calls. Namely, + * postgres_fdw calls "GetNumRegisteredWaitEvents(set) == 1" to check if + * any other events are in the set. That's a poor design, it's + * questionable for postgres_fdw to be doing that in the first place, but + * we cannot change it now. The pattern has possibly been copied to other + * extensions too. + */ + AddWaitEventToSet(node->as_eventset, WL_LATCH_SET, PGINVALID_SOCKET, + MyLatch, NULL); + /* Return at most EVENT_BUFFER_SIZE events in one call. */ if (nevents > EVENT_BUFFER_SIZE) nevents = EVENT_BUFFER_SIZE; @@ -1092,6 +1107,13 @@ ExecAppendAsyncEventWait(AppendState *node) ExecAsyncNotify(areq); } } + + /* Handle standard interrupts */ + if ((w->events & WL_LATCH_SET) != 0) + { + ResetLatch(MyLatch); + CHECK_FOR_INTERRUPTS(); + } } } From 3abce879810e2337ac1854b391cf090d05fc0d76 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 13 Mar 2025 09:56:49 +0900 Subject: [PATCH 266/796] Fix copy-paste error in datum_to_jsonb_internal() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 3c152a27b06 mistakenly repeated JSONTYPE_JSON in a condition, omitting JSONTYPE_CAST. As a result, datum_to_jsonb_internal() failed to reject inputs that were casts (e.g., from an enum to json as in the example below) when used as keys in JSON constructors. This led to a crash in cases like: SELECT JSON_OBJECT('happy'::mood: '123'::jsonb); where 'happy'::mood is implicitly cast to json. The missing check meant such casted values weren’t properly rejected as invalid (non-scalar) JSON keys. Reported-by: Maciek Sakrejda Reviewed-by: Tender Wang Reviewed-by: Alvaro Herrera Reviewed-by: Maciek Sakrejda Discussion: https://postgr.es/m/CADXhmgTJtJZK9A3Na_ry+Xrq-ghjcejBRhcRMzWZvbd__QdgJA@mail.gmail.com Backpatch-through: 17 --- src/backend/utils/adt/jsonb.c | 2 +- src/test/regress/expected/sqljson.out | 12 ++++++++++++ src/test/regress/sql/sqljson.sql | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index e4562b3c6cea7..b6110abaa52fe 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -657,7 +657,7 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result, tcategory == JSONTYPE_COMPOSITE || tcategory == JSONTYPE_JSON || tcategory == JSONTYPE_JSONB || - tcategory == JSONTYPE_JSON)) + tcategory == JSONTYPE_CAST)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index e25a49928bcac..17a889eb8ef94 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -571,6 +571,18 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WIT {"1": 1, "3": 1, "5": "a"} (1 row) +-- BUG: https://postgr.es/m/CADXhmgTJtJZK9A3Na_ry%2BXrq-ghjcejBRhcRMzWZvbd__QdgJA%40mail.gmail.com +-- datum_to_jsonb_internal() didn't catch keys that are casts instead of a simple scalar +CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral'); +CREATE FUNCTION mood_to_json(mood) RETURNS json AS $$ + SELECT to_json($1::text); +$$ LANGUAGE sql IMMUTABLE; +CREATE CAST (mood AS json) WITH FUNCTION mood_to_json(mood) AS IMPLICIT; +SELECT JSON_OBJECT('happy'::mood: '123'::jsonb); +ERROR: key value must be scalar, not array, composite, or json +DROP CAST (mood AS json); +DROP FUNCTION mood_to_json; +DROP TYPE mood; -- JSON_ARRAY() SELECT JSON_ARRAY(); json_array diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 9b5d82602d071..3fd6ac260b8ed 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -152,6 +152,17 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb); SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +-- BUG: https://postgr.es/m/CADXhmgTJtJZK9A3Na_ry%2BXrq-ghjcejBRhcRMzWZvbd__QdgJA%40mail.gmail.com +-- datum_to_jsonb_internal() didn't catch keys that are casts instead of a simple scalar +CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral'); +CREATE FUNCTION mood_to_json(mood) RETURNS json AS $$ + SELECT to_json($1::text); +$$ LANGUAGE sql IMMUTABLE; +CREATE CAST (mood AS json) WITH FUNCTION mood_to_json(mood) AS IMPLICIT; +SELECT JSON_OBJECT('happy'::mood: '123'::jsonb); +DROP CAST (mood AS json); +DROP FUNCTION mood_to_json; +DROP TYPE mood; -- JSON_ARRAY() SELECT JSON_ARRAY(); From 0fa90106037a1b201f5e06c3dd7a4b9b21ae515c Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Thu, 13 Mar 2025 15:43:34 +1300 Subject: [PATCH 267/796] Fix read_stream.c for changing io_combine_limit. In a couple of places, read_stream.c assumed that io_combine_limit would be stable during the lifetime of a stream. That is not true in at least one unusual case: streams held by CURSORs where you could change the GUC between FETCH commands, with unpredictable results. Fix, by storing stream->io_combine_limit and referring only to that after construction. This mirrors the treatment of the other important setting {effective,maintenance}_io_concurrency, which is stored in stream->max_ios. One of the cases was the queue overflow space, which was sized for io_combine_limit and could be overrun if the GUC was increased. Since that coding was a little hard to follow, also introduce a variable for better readability instead of open-coding the arithmetic. Doing so revealed an off-by-one thinko while clamping max_pinned_buffers to INT16_MAX, though that wasn't a live bug due to the current limits on GUC values. Back-patch to 17. Discussion: https://postgr.es/m/CA%2BhUKG%2B2T9p-%2BzM6Eeou-RAJjTML6eit1qn26f9twznX59qtCA%40mail.gmail.com --- src/backend/storage/aio/read_stream.c | 38 +++++++++++++++++++-------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c index b5be92d461128..da1753b6137d9 100644 --- a/src/backend/storage/aio/read_stream.c +++ b/src/backend/storage/aio/read_stream.c @@ -109,6 +109,7 @@ typedef struct InProgressIO struct ReadStream { int16 max_ios; + int16 io_combine_limit; int16 ios_in_progress; int16 queue_size; int16 max_pinned_buffers; @@ -219,7 +220,7 @@ read_stream_start_pending_read(ReadStream *stream, bool suppress_advice) /* This should only be called with a pending read. */ Assert(stream->pending_read_nblocks > 0); - Assert(stream->pending_read_nblocks <= io_combine_limit); + Assert(stream->pending_read_nblocks <= stream->io_combine_limit); /* We had better not exceed the pin limit by starting this read. */ Assert(stream->pinned_buffers + stream->pending_read_nblocks <= @@ -307,7 +308,7 @@ read_stream_look_ahead(ReadStream *stream, bool suppress_advice) int16 buffer_index; void *per_buffer_data; - if (stream->pending_read_nblocks == io_combine_limit) + if (stream->pending_read_nblocks == stream->io_combine_limit) { read_stream_start_pending_read(stream, suppress_advice); suppress_advice = false; @@ -367,7 +368,7 @@ read_stream_look_ahead(ReadStream *stream, bool suppress_advice) * signaled end-of-stream, we start the read immediately. */ if (stream->pending_read_nblocks > 0 && - (stream->pending_read_nblocks == io_combine_limit || + (stream->pending_read_nblocks == stream->io_combine_limit || (stream->pending_read_nblocks == stream->distance && stream->pinned_buffers == 0) || stream->distance == 0) && @@ -396,6 +397,7 @@ read_stream_begin_relation(int flags, ReadStream *stream; size_t size; int16 queue_size; + int16 queue_overflow; int max_ios; int strategy_pin_limit; uint32 max_pinned_buffers; @@ -429,6 +431,14 @@ read_stream_begin_relation(int flags, /* Cap to INT16_MAX to avoid overflowing below */ max_ios = Min(max_ios, PG_INT16_MAX); + /* + * If starting a multi-block I/O near the end of the queue, we might + * temporarily need extra space for overflowing buffers before they are + * moved to regular circular position. This is the maximum extra space we + * could need. + */ + queue_overflow = io_combine_limit - 1; + /* * Choose the maximum number of buffers we're prepared to pin. We try to * pin fewer if we can, though. We clamp it to at least io_combine_limit @@ -439,7 +449,7 @@ read_stream_begin_relation(int flags, */ max_pinned_buffers = Max(max_ios * 4, io_combine_limit); max_pinned_buffers = Min(max_pinned_buffers, - PG_INT16_MAX - io_combine_limit - 1); + PG_INT16_MAX - queue_overflow - 1); /* Give the strategy a chance to limit the number of buffers we pin. */ strategy_pin_limit = GetAccessStrategyPinLimit(strategy); @@ -465,18 +475,17 @@ read_stream_begin_relation(int flags, * one big chunk. Though we have queue_size buffers, we want to be able * to assume that all the buffers for a single read are contiguous (i.e. * don't wrap around halfway through), so we allow temporary overflows of - * up to the maximum possible read size by allocating an extra - * io_combine_limit - 1 elements. + * up to the maximum possible overflow size. */ size = offsetof(ReadStream, buffers); - size += sizeof(Buffer) * (queue_size + io_combine_limit - 1); + size += sizeof(Buffer) * (queue_size + queue_overflow); size += sizeof(InProgressIO) * Max(1, max_ios); size += per_buffer_data_size * queue_size; size += MAXIMUM_ALIGNOF * 2; stream = (ReadStream *) palloc(size); memset(stream, 0, offsetof(ReadStream, buffers)); stream->ios = (InProgressIO *) - MAXALIGN(&stream->buffers[queue_size + io_combine_limit - 1]); + MAXALIGN(&stream->buffers[queue_size + queue_overflow]); if (per_buffer_data_size > 0) stream->per_buffer_data = (void *) MAXALIGN(&stream->ios[Max(1, max_ios)]); @@ -503,7 +512,14 @@ read_stream_begin_relation(int flags, if (max_ios == 0) max_ios = 1; + /* + * Capture stable values for these two GUC-derived numbers for the + * lifetime of this stream, so we don't have to worry about the GUCs + * changing underneath us beyond this point. + */ stream->max_ios = max_ios; + stream->io_combine_limit = io_combine_limit; + stream->per_buffer_data_size = per_buffer_data_size; stream->max_pinned_buffers = max_pinned_buffers; stream->queue_size = queue_size; @@ -517,7 +533,7 @@ read_stream_begin_relation(int flags, * doing full io_combine_limit sized reads (behavior B). */ if (flags & READ_STREAM_FULL) - stream->distance = Min(max_pinned_buffers, io_combine_limit); + stream->distance = Min(max_pinned_buffers, stream->io_combine_limit); else stream->distance = 1; @@ -683,14 +699,14 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data) else { /* No advice; move towards io_combine_limit (behavior B). */ - if (stream->distance > io_combine_limit) + if (stream->distance > stream->io_combine_limit) { stream->distance--; } else { distance = stream->distance * 2; - distance = Min(distance, io_combine_limit); + distance = Min(distance, stream->io_combine_limit); distance = Min(distance, stream->max_pinned_buffers); stream->distance = distance; } From 4b6f78f1a47185858c007c3044f51bf5eed7de0f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 13 Mar 2025 16:07:55 -0400 Subject: [PATCH 268/796] Fix ARRAY_SUBLINK and ARRAY[] for int2vector and oidvector input. If the given input_type yields valid results from both get_element_type and get_array_type, initArrayResultAny believed the former and treated the input as an array type. However this is inconsistent with what get_promoted_array_type does, leading to situations where the output of an ARRAY() subquery is labeled with the wrong type: it's labeled as oidvector[] but is really a 2-D array of OID. That at least results in strange output, and can result in crashes if further processing such as unnest() is applied. AFAIK this is only possible with the int2vector and oidvector types, which are special-cased to be treated mostly as true arrays even though they aren't quite. Fix by switching the logic to match get_promoted_array_type by testing get_array_type not get_element_type, and remove an Assert thereby made pointless. (We need not introduce a symmetrical check for get_element_type in the other if-branch, because initArrayResultArr will check it.) This restores the behavior that existed before bac27394a introduced initArrayResultAny: the output really is int2vector[] or oidvector[]. Comparable confusion exists when an input of an ARRAY[] construct is int2vector or oidvector: transformArrayExpr decides it's dealing with a multidimensional array constructor, and we end up with something that's a multidimensional OID array but is alleged to be of type oidvector. I have not found a crashing case here, but it's easy to demonstrate totally-wrong results. Adjust that code so that what you get is an oidvector[] instead, for consistency with ARRAY() subqueries. (This change also makes these types work like domains-over-arrays in this context, which seems correct.) Bug: #18840 Reported-by: yang lei Author: Tom Lane Discussion: https://postgr.es/m/18840-fbc9505f066e50d6@postgresql.org Backpatch-through: 13 --- src/backend/parser/parse_expr.c | 14 ++- src/backend/utils/adt/arrayfuncs.c | 12 +-- src/test/regress/expected/arrays.out | 126 +++++++++++++++++++++++++++ src/test/regress/sql/arrays.sql | 22 +++++ 4 files changed, 166 insertions(+), 8 deletions(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 3905dd36340e0..2258701f3013a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -2055,10 +2055,18 @@ transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, /* * Check for sub-array expressions, if we haven't already found - * one. + * one. Note we don't accept domain-over-array as a sub-array, + * nor int2vector nor oidvector; those have constraints that don't + * map well to being treated as a sub-array. */ - if (!newa->multidims && type_is_array(exprType(newe))) - newa->multidims = true; + if (!newa->multidims) + { + Oid newetype = exprType(newe); + + if (newetype != INT2VECTOROID && newetype != OIDVECTOROID && + type_is_array(newetype)) + newa->multidims = true; + } } newelems = lappend(newelems, newe); diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index e5c7e57a5decf..2fbd1972d7220 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -5770,9 +5770,14 @@ ArrayBuildStateAny * initArrayResultAny(Oid input_type, MemoryContext rcontext, bool subcontext) { ArrayBuildStateAny *astate; - Oid element_type = get_element_type(input_type); - if (OidIsValid(element_type)) + /* + * int2vector and oidvector will satisfy both get_element_type and + * get_array_type. We prefer to treat them as scalars, to be consistent + * with get_promoted_array_type. Hence, check get_array_type not + * get_element_type. + */ + if (!OidIsValid(get_array_type(input_type))) { /* Array case */ ArrayBuildStateArr *arraystate; @@ -5789,9 +5794,6 @@ initArrayResultAny(Oid input_type, MemoryContext rcontext, bool subcontext) /* Scalar case */ ArrayBuildState *scalarstate; - /* Let's just check that we have a type that can be put into arrays */ - Assert(OidIsValid(get_array_type(input_type))); - scalarstate = initArrayResult(input_type, rcontext, subcontext); astate = (ArrayBuildStateAny *) MemoryContextAlloc(scalarstate->mcontext, diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index a6d81fd5f9284..44fa7b214c71a 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -2457,6 +2457,132 @@ select array(select array['Hello', i::text] from generate_series(9,11) i); {{Hello,9},{Hello,10},{Hello,11}} (1 row) +-- int2vector and oidvector should be treated as scalar types for this purpose +select pg_typeof(array(select '11 22 33'::int2vector from generate_series(1,5))); + pg_typeof +-------------- + int2vector[] +(1 row) + +select array(select '11 22 33'::int2vector from generate_series(1,5)); + array +---------------------------------------------------------- + {"11 22 33","11 22 33","11 22 33","11 22 33","11 22 33"} +(1 row) + +select unnest(array(select '11 22 33'::int2vector from generate_series(1,5))); + unnest +---------- + 11 22 33 + 11 22 33 + 11 22 33 + 11 22 33 + 11 22 33 +(5 rows) + +select pg_typeof(array(select '11 22 33'::oidvector from generate_series(1,5))); + pg_typeof +------------- + oidvector[] +(1 row) + +select array(select '11 22 33'::oidvector from generate_series(1,5)); + array +---------------------------------------------------------- + {"11 22 33","11 22 33","11 22 33","11 22 33","11 22 33"} +(1 row) + +select unnest(array(select '11 22 33'::oidvector from generate_series(1,5))); + unnest +---------- + 11 22 33 + 11 22 33 + 11 22 33 + 11 22 33 + 11 22 33 +(5 rows) + +-- array[] should do the same +select pg_typeof(array['11 22 33'::int2vector]); + pg_typeof +-------------- + int2vector[] +(1 row) + +select array['11 22 33'::int2vector]; + array +-------------- + {"11 22 33"} +(1 row) + +select pg_typeof(unnest(array['11 22 33'::int2vector])); + pg_typeof +------------ + int2vector +(1 row) + +select unnest(array['11 22 33'::int2vector]); + unnest +---------- + 11 22 33 +(1 row) + +select pg_typeof(unnest('11 22 33'::int2vector)); + pg_typeof +----------- + smallint + smallint + smallint +(3 rows) + +select unnest('11 22 33'::int2vector); + unnest +-------- + 11 + 22 + 33 +(3 rows) + +select pg_typeof(array['11 22 33'::oidvector]); + pg_typeof +------------- + oidvector[] +(1 row) + +select array['11 22 33'::oidvector]; + array +-------------- + {"11 22 33"} +(1 row) + +select pg_typeof(unnest(array['11 22 33'::oidvector])); + pg_typeof +----------- + oidvector +(1 row) + +select unnest(array['11 22 33'::oidvector]); + unnest +---------- + 11 22 33 +(1 row) + +select pg_typeof(unnest('11 22 33'::oidvector)); + pg_typeof +----------- + oid + oid + oid +(3 rows) + +select unnest('11 22 33'::oidvector); + unnest +-------- + 11 + 22 + 33 +(3 rows) + -- Insert/update on a column that is array of composite create temp table t1 (f1 int8_tbl[]); insert into t1 (f1[5].q1) values(42); diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 47058dfde5092..305371debae3b 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -713,6 +713,28 @@ select array_replace(array['AB',NULL,'CDE'],NULL,'12'); select array(select array[i,i/2] from generate_series(1,5) i); select array(select array['Hello', i::text] from generate_series(9,11) i); +-- int2vector and oidvector should be treated as scalar types for this purpose +select pg_typeof(array(select '11 22 33'::int2vector from generate_series(1,5))); +select array(select '11 22 33'::int2vector from generate_series(1,5)); +select unnest(array(select '11 22 33'::int2vector from generate_series(1,5))); +select pg_typeof(array(select '11 22 33'::oidvector from generate_series(1,5))); +select array(select '11 22 33'::oidvector from generate_series(1,5)); +select unnest(array(select '11 22 33'::oidvector from generate_series(1,5))); + +-- array[] should do the same +select pg_typeof(array['11 22 33'::int2vector]); +select array['11 22 33'::int2vector]; +select pg_typeof(unnest(array['11 22 33'::int2vector])); +select unnest(array['11 22 33'::int2vector]); +select pg_typeof(unnest('11 22 33'::int2vector)); +select unnest('11 22 33'::int2vector); +select pg_typeof(array['11 22 33'::oidvector]); +select array['11 22 33'::oidvector]; +select pg_typeof(unnest(array['11 22 33'::oidvector])); +select unnest(array['11 22 33'::oidvector]); +select pg_typeof(unnest('11 22 33'::oidvector)); +select unnest('11 22 33'::oidvector); + -- Insert/update on a column that is array of composite create temp table t1 (f1 int8_tbl[]); From 2f1905f9251bb002fbbe1970a838cdf1f44fcbae Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sun, 16 Mar 2025 13:28:22 +0200 Subject: [PATCH 269/796] reindexdb: Fix the index-level REINDEX with multiple jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 47f99a407d introduced a parallel index-level REINDEX. The code was written assuming that running run_reindex_command() with 'async == true' can schedule a number of queries for a connection. That's not true, and the second query sent using run_reindex_command() will wait for the completion of the previous one. This commit fixes that by putting REINDEX commands for the same table into a single query. Also, this commit removes the 'async' argument from run_reindex_command(), as only its call always passes 'async == true'. Reported-by: Álvaro Herrera Discussion: https://postgr.es/m/202503071820.j25zn3lo4hvn%40alvherre.pgsql Reviewed-by: Álvaro Herrera Backpatch-through: 17 --- src/bin/scripts/reindexdb.c | 135 +++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c index e736b2baccc06..2ff16140ad880 100644 --- a/src/bin/scripts/reindexdb.c +++ b/src/bin/scripts/reindexdb.c @@ -50,10 +50,13 @@ static void reindex_all_databases(ConnParams *cparams, bool syscatalog, SimpleStringList *schemas, SimpleStringList *tables, SimpleStringList *indexes); -static void run_reindex_command(PGconn *conn, ReindexType type, +static void gen_reindex_command(PGconn *conn, ReindexType type, const char *name, bool echo, bool verbose, - bool concurrently, bool async, - const char *tablespace); + bool concurrently, const char *tablespace, + PQExpBufferData *sql); +static void run_reindex_command(PGconn *conn, ReindexType type, + const char *name, bool echo, + PQExpBufferData *sq); static void help(const char *progname); @@ -285,7 +288,6 @@ reindex_one_database(ConnParams *cparams, ReindexType type, ParallelSlotArray *sa; bool failed = false; int items_count = 0; - char *prev_index_table_name = NULL; ParallelSlot *free_slot = NULL; conn = connectDatabase(cparams, progname, echo, false, true); @@ -421,8 +423,8 @@ reindex_one_database(ConnParams *cparams, ReindexType type, cell = process_list->head; do { + PQExpBufferData sql; const char *objname = cell->val; - bool need_new_slot = true; if (CancelRequested) { @@ -430,35 +432,45 @@ reindex_one_database(ConnParams *cparams, ReindexType type, goto finish; } - /* - * For parallel index-level REINDEX, the indices of the same table are - * ordered together and they are to be processed by the same job. So, - * we don't switch the job as soon as the index belongs to the same - * table as the previous one. - */ - if (parallel && process_type == REINDEX_INDEX) + free_slot = ParallelSlotsGetIdle(sa, NULL); + if (!free_slot) { - if (prev_index_table_name != NULL && - strcmp(prev_index_table_name, indices_tables_cell->val) == 0) - need_new_slot = false; - prev_index_table_name = indices_tables_cell->val; - indices_tables_cell = indices_tables_cell->next; + failed = true; + goto finish; } - if (need_new_slot) + ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL); + initPQExpBuffer(&sql); + if (parallel && process_type == REINDEX_INDEX) { - free_slot = ParallelSlotsGetIdle(sa, NULL); - if (!free_slot) + /* + * For parallel index-level REINDEX, the indices of the same table + * are ordered together and they are to be processed by the same + * job. So, we put all the relevant REINDEX commands into the + * same SQL query to be processed by this job at once. + */ + gen_reindex_command(free_slot->connection, process_type, objname, + echo, verbose, concurrently, tablespace, &sql); + while (indices_tables_cell->next && + strcmp(indices_tables_cell->val, indices_tables_cell->next->val) == 0) { - failed = true; - goto finish; + indices_tables_cell = indices_tables_cell->next; + cell = cell->next; + objname = cell->val; + appendPQExpBufferChar(&sql, '\n'); + gen_reindex_command(free_slot->connection, process_type, objname, + echo, verbose, concurrently, tablespace, &sql); } - - ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL); + indices_tables_cell = indices_tables_cell->next; + } + else + { + gen_reindex_command(free_slot->connection, process_type, objname, + echo, verbose, concurrently, tablespace, &sql); } - run_reindex_command(free_slot->connection, process_type, objname, - echo, verbose, concurrently, true, tablespace); + echo, &sql); + termPQExpBuffer(&sql); cell = cell->next; } while (cell != NULL); @@ -486,57 +498,57 @@ reindex_one_database(ConnParams *cparams, ReindexType type, exit(1); } +/* + * Append a SQL command required to reindex a given database object to the + * '*sql' string. + */ static void -run_reindex_command(PGconn *conn, ReindexType type, const char *name, - bool echo, bool verbose, bool concurrently, bool async, - const char *tablespace) +gen_reindex_command(PGconn *conn, ReindexType type, const char *name, + bool echo, bool verbose, bool concurrently, + const char *tablespace, PQExpBufferData *sql) { const char *paren = "("; const char *comma = ", "; const char *sep = paren; - PQExpBufferData sql; - bool status; Assert(name); /* build the REINDEX query */ - initPQExpBuffer(&sql); - - appendPQExpBufferStr(&sql, "REINDEX "); + appendPQExpBufferStr(sql, "REINDEX "); if (verbose) { - appendPQExpBuffer(&sql, "%sVERBOSE", sep); + appendPQExpBuffer(sql, "%sVERBOSE", sep); sep = comma; } if (tablespace) { - appendPQExpBuffer(&sql, "%sTABLESPACE %s", sep, + appendPQExpBuffer(sql, "%sTABLESPACE %s", sep, fmtIdEnc(tablespace, PQclientEncoding(conn))); sep = comma; } if (sep != paren) - appendPQExpBufferStr(&sql, ") "); + appendPQExpBufferStr(sql, ") "); /* object type */ switch (type) { case REINDEX_DATABASE: - appendPQExpBufferStr(&sql, "DATABASE "); + appendPQExpBufferStr(sql, "DATABASE "); break; case REINDEX_INDEX: - appendPQExpBufferStr(&sql, "INDEX "); + appendPQExpBufferStr(sql, "INDEX "); break; case REINDEX_SCHEMA: - appendPQExpBufferStr(&sql, "SCHEMA "); + appendPQExpBufferStr(sql, "SCHEMA "); break; case REINDEX_SYSTEM: - appendPQExpBufferStr(&sql, "SYSTEM "); + appendPQExpBufferStr(sql, "SYSTEM "); break; case REINDEX_TABLE: - appendPQExpBufferStr(&sql, "TABLE "); + appendPQExpBufferStr(sql, "TABLE "); break; } @@ -546,37 +558,43 @@ run_reindex_command(PGconn *conn, ReindexType type, const char *name, * object type. */ if (concurrently) - appendPQExpBufferStr(&sql, "CONCURRENTLY "); + appendPQExpBufferStr(sql, "CONCURRENTLY "); /* object name */ switch (type) { case REINDEX_DATABASE: case REINDEX_SYSTEM: - appendPQExpBufferStr(&sql, + appendPQExpBufferStr(sql, fmtIdEnc(name, PQclientEncoding(conn))); break; case REINDEX_INDEX: case REINDEX_TABLE: - appendQualifiedRelation(&sql, name, conn, echo); + appendQualifiedRelation(sql, name, conn, echo); break; case REINDEX_SCHEMA: - appendPQExpBufferStr(&sql, name); + appendPQExpBufferStr(sql, name); break; } /* finish the query */ - appendPQExpBufferChar(&sql, ';'); + appendPQExpBufferChar(sql, ';'); +} - if (async) - { - if (echo) - printf("%s\n", sql.data); +/* + * Run one or more reindex commands accumulated in the '*sql' string against + * a given database connection. + */ +static void +run_reindex_command(PGconn *conn, ReindexType type, const char *name, + bool echo, PQExpBufferData *sql) +{ + bool status; - status = PQsendQuery(conn, sql.data) == 1; - } - else - status = executeMaintenanceCommand(conn, sql.data, echo); + if (echo) + printf("%s\n", sql->data); + + status = PQsendQuery(conn, sql->data) == 1; if (!status) { @@ -603,14 +621,7 @@ run_reindex_command(PGconn *conn, ReindexType type, const char *name, name, PQdb(conn), PQerrorMessage(conn)); break; } - if (!async) - { - PQfinish(conn); - exit(1); - } } - - termPQExpBuffer(&sql); } /* From 64b6be77d6e418632e3a24e02417a595f49a9549 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Tue, 18 Mar 2025 13:43:10 -0400 Subject: [PATCH 270/796] smgr: Make SMgrRelation initialization safer against errors In case the smgr_open callback failed, the ->pincount field would not be initialized and the relation would not be put onto the unpinned_relns list. This buglet was introduced in 21d9c3ee4ef7, in 17. Discussion: https://postgr.es/m/3vae7l5ozvqtxmd7rr7zaeq3qkuipz365u3rtim5t5wdkr6f4g@vkgf2fogjirl Backpatch-through: 17 --- src/backend/storage/smgr/smgr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 60bc48f4aecb0..c9f32ccdb3f4b 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -230,12 +230,12 @@ smgropen(RelFileLocator rlocator, ProcNumber backend) reln->smgr_which = storage_manager_id; - /* implementation-specific initialization */ - smgrsw[reln->smgr_which].smgr_open(reln); - /* it is not pinned yet */ reln->pincount = 0; dlist_push_tail(&unpinned_relns, &reln->node); + + /* implementation-specific initialization */ + smgrsw[reln->smgr_which].smgr_open(reln); } return reln; From d6338a1b4f7db266e9771d7b5e8141ec5c40d2cb Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Tue, 18 Mar 2025 16:36:59 -0700 Subject: [PATCH 271/796] Fix assertion failure in parallel vacuum with minimal maintenance_work_mem setting. bbf668d66fbf lowered the minimum value of maintenance_work_mem to 64kB. However, in parallel vacuum cases, since the initial underlying DSA size is 256kB, it attempts to perform a cycle of index vacuuming and table vacuuming with an empty TID store, resulting in an assertion failure. This commit ensures that at least one page is processed before index vacuuming and table vacuuming begins. Backpatch to 17, where the minimum maintenance_work_mem value was lowered. Reviewed-by: David Rowley Discussion: https://postgr.es/m/CAD21AoCEAmbkkXSKbj4dB+5pJDRL4ZHxrCiLBgES_g_g8mVi1Q@mail.gmail.com Backpatch-through: 17 --- src/backend/access/heap/vacuumlazy.c | 7 +++++-- src/test/regress/expected/vacuum.out | 12 ++++++++++++ src/test/regress/sql/vacuum.sql | 13 +++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index f2d25987120bf..b22604e96003b 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -872,9 +872,12 @@ lazy_scan_heap(LVRelState *vacrel) * Consider if we definitely have enough space to process TIDs on page * already. If we are close to overrunning the available space for * dead_items TIDs, pause and do a cycle of vacuuming before we tackle - * this page. + * this page. However, let's force at least one page-worth of tuples + * to be stored as to ensure we do at least some work when the memory + * configured is so low that we run out before storing anything. */ - if (TidStoreMemoryUsage(vacrel->dead_items) > vacrel->dead_items_info->max_bytes) + if (vacrel->dead_items_info->num_items > 0 && + TidStoreMemoryUsage(vacrel->dead_items) > vacrel->dead_items_info->max_bytes) { /* * Before beginning index vacuuming, we release any pin we may diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 2eba7128876f2..7518806e61f42 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -148,6 +148,10 @@ CREATE INDEX brin_pvactst ON pvactst USING brin (i); CREATE INDEX gin_pvactst ON pvactst USING gin (a); CREATE INDEX gist_pvactst ON pvactst USING gist (p); CREATE INDEX spgist_pvactst ON pvactst USING spgist (p); +CREATE TABLE pvactst2 (i INT) WITH (autovacuum_enabled = off); +INSERT INTO pvactst2 SELECT generate_series(1, 1000); +CREATE INDEX ON pvactst2 (i); +CREATE INDEX ON pvactst2 (i); -- VACUUM invokes parallel index cleanup SET min_parallel_index_scan_size to 0; VACUUM (PARALLEL 2) pvactst; @@ -167,6 +171,13 @@ VACUUM (PARALLEL) pvactst; -- error, cannot use PARALLEL option without parallel ERROR: parallel option requires a value between 0 and 1024 LINE 1: VACUUM (PARALLEL) pvactst; ^ +-- Test parallel vacuum using the minimum maintenance_work_mem with and without +-- dead tuples. +SET maintenance_work_mem TO 64; +VACUUM (PARALLEL 2) pvactst2; +DELETE FROM pvactst2 WHERE i < 1000; +VACUUM (PARALLEL 2) pvactst2; +RESET maintenance_work_mem; -- Test different combinations of parallel and full options for temporary tables CREATE TEMPORARY TABLE tmp (a int PRIMARY KEY); CREATE INDEX tmp_idx1 ON tmp (a); @@ -175,6 +186,7 @@ WARNING: disabling parallel option of vacuum on "tmp" --- cannot vacuum tempora VACUUM (PARALLEL 0, FULL TRUE) tmp; -- can specify parallel disabled (even though that's implied by FULL) RESET min_parallel_index_scan_size; DROP TABLE pvactst; +DROP TABLE pvactst2; -- INDEX_CLEANUP option CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT); -- Use uncompressed data stored in toast. diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 548cd7accace4..d272dd064eb55 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -113,6 +113,10 @@ CREATE INDEX brin_pvactst ON pvactst USING brin (i); CREATE INDEX gin_pvactst ON pvactst USING gin (a); CREATE INDEX gist_pvactst ON pvactst USING gist (p); CREATE INDEX spgist_pvactst ON pvactst USING spgist (p); +CREATE TABLE pvactst2 (i INT) WITH (autovacuum_enabled = off); +INSERT INTO pvactst2 SELECT generate_series(1, 1000); +CREATE INDEX ON pvactst2 (i); +CREATE INDEX ON pvactst2 (i); -- VACUUM invokes parallel index cleanup SET min_parallel_index_scan_size to 0; @@ -130,6 +134,14 @@ VACUUM (PARALLEL 2, INDEX_CLEANUP FALSE) pvactst; VACUUM (PARALLEL 2, FULL TRUE) pvactst; -- error, cannot use both PARALLEL and FULL VACUUM (PARALLEL) pvactst; -- error, cannot use PARALLEL option without parallel degree +-- Test parallel vacuum using the minimum maintenance_work_mem with and without +-- dead tuples. +SET maintenance_work_mem TO 64; +VACUUM (PARALLEL 2) pvactst2; +DELETE FROM pvactst2 WHERE i < 1000; +VACUUM (PARALLEL 2) pvactst2; +RESET maintenance_work_mem; + -- Test different combinations of parallel and full options for temporary tables CREATE TEMPORARY TABLE tmp (a int PRIMARY KEY); CREATE INDEX tmp_idx1 ON tmp (a); @@ -137,6 +149,7 @@ VACUUM (PARALLEL 1, FULL FALSE) tmp; -- parallel vacuum disabled for temp tables VACUUM (PARALLEL 0, FULL TRUE) tmp; -- can specify parallel disabled (even though that's implied by FULL) RESET min_parallel_index_scan_size; DROP TABLE pvactst; +DROP TABLE pvactst2; -- INDEX_CLEANUP option CREATE TABLE no_index_cleanup (i INT PRIMARY KEY, t TEXT); From c16fa2aa7fe9483a81116c0886395146e93d9036 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 19 Mar 2025 09:04:09 -0400 Subject: [PATCH 272/796] meson: Flush stdout in testwrap Otherwise the progress won't reliably be displayed during a test. Reviewed-by: Noah Misch Discussion: https://postgr.es/m/kx6xu7suexal5vwsxpy7ybgkcznx6hgywbuhkr6qabcwxjqax2@i4pcpk75jvaa Backpatch-through: 16 --- src/tools/testwrap | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/testwrap b/src/tools/testwrap index 9a270beb72d2a..9d4a878354034 100755 --- a/src/tools/testwrap +++ b/src/tools/testwrap @@ -51,6 +51,7 @@ for line in sp.stdout: if line.startswith(b'ok '): line = line.replace(b' # TODO ', b' # testwrap-overridden-TODO ', 1) sys.stdout.buffer.write(line) + sys.stdout.flush() returncode = sp.wait() if returncode == 0: From 84695879103429fab8edb9766962b5fbd620baa3 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Fri, 21 Mar 2025 12:56:39 +0900 Subject: [PATCH 273/796] doc: Remove incorrect description about dropping replication slots. pg_drop_replication_slot() can drop replication slots created on a different database than the one where it is executed. This behavior has been in place since PostgreSQL 9.4, when pg_drop_replication_slot() was introduced. However, commit ff539d mistakenly added the following incorrect description in the documentation: For logical slots, this must be called when connected to the same database the slot was created on. This commit removes that incorrect statement. A similar mistake was also present in the documentation for the DROP_REPLICATION_SLOT command, which has now been corrected as well. Back-patch to all supported versions. Author: Hayato Kuroda Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/OSCPR01MB14966C6BE304B5BB2E58D4009F5DE2@OSCPR01MB14966.jpnprd01.prod.outlook.com Backpatch-through: 13 --- doc/src/sgml/func.sgml | 3 +-- doc/src/sgml/protocol.sgml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index d8b27903593fd..ebd42096795c4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29045,8 +29045,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset Drops the physical or logical replication slot named slot_name. Same as replication protocol - command DROP_REPLICATION_SLOT. For logical slots, this must - be called while connected to the same database the slot was created on. + command DROP_REPLICATION_SLOT. diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 5a3d6fdb5a34a..0396d3a9e986f 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -2660,8 +2660,6 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" Drops a replication slot, freeing any reserved server-side resources. - If the slot is a logical slot that was created in a database other than - the database the walsender is connected to, this command fails. From d52f775dbce6304bd894e143ed6ebaae8a7ce14f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 21 Mar 2025 11:30:42 -0400 Subject: [PATCH 274/796] Fix plpgsql's handling of simple expressions in scrollable cursors. exec_save_simple_expr did not account for the possibility that standard_planner would stick a Materialize node atop the plan of even a simple Result, if CURSOR_OPT_SCROLL is set. This led to an "unexpected plan node type" error. This is a very old bug, but it'd only be reached by declaring a cursor for a "SELECT simple-expression" query and explicitly marking it scrollable, which is an odd thing to do. So the lack of prior reports isn't too surprising. Bug: #18859 Reported-by: Olleg Samoylov Author: Andrei Lepikhov Reviewed-by: Tom Lane Discussion: https://postgr.es/m/18859-0d5f28ac99a37059@postgresql.org Backpatch-through: 13 --- src/pl/plpgsql/src/expected/plpgsql_simple.out | 11 +++++++++++ src/pl/plpgsql/src/pl_exec.c | 12 +++++++----- src/pl/plpgsql/src/sql/plpgsql_simple.sql | 12 ++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/pl/plpgsql/src/expected/plpgsql_simple.out b/src/pl/plpgsql/src/expected/plpgsql_simple.out index 7b22e60f1984f..da351873e742e 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_simple.out +++ b/src/pl/plpgsql/src/expected/plpgsql_simple.out @@ -118,3 +118,14 @@ select simplecaller(); 44 (1 row) +-- Check handling of simple expression in a scrollable cursor (bug #18859) +do $$ +declare + p_CurData refcursor; + val int; +begin + open p_CurData scroll for select 42; + fetch p_CurData into val; + raise notice 'val = %', val; +end; $$; +NOTICE: val = 42 diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index ea9740e3f89b5..ca3d1e753bc0e 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8141,10 +8141,12 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan) /* * Ordinarily, the plan node should be a simple Result. However, if * debug_parallel_query is on, the planner might've stuck a Gather node - * atop that. The simplest way to deal with this is to look through the - * Gather node. The Gather node's tlist would normally contain a Var - * referencing the child node's output, but it could also be a Param, or - * it could be a Const that setrefs.c copied as-is. + * atop that; and/or if this plan is for a scrollable cursor, the planner + * might've stuck a Material node atop it. The simplest way to deal with + * this is to look through the Gather and/or Material nodes. The upper + * node's tlist would normally contain a Var referencing the child node's + * output, but it could also be a Param, or it could be a Const that + * setrefs.c copied as-is. */ plan = stmt->planTree; for (;;) @@ -8162,7 +8164,7 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan) ((Result *) plan)->resconstantqual == NULL); break; } - else if (IsA(plan, Gather)) + else if (IsA(plan, Gather) || IsA(plan, Material)) { Assert(plan->lefttree != NULL && plan->righttree == NULL && diff --git a/src/pl/plpgsql/src/sql/plpgsql_simple.sql b/src/pl/plpgsql/src/sql/plpgsql_simple.sql index 143bf09dce469..72d8afe4500d1 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_simple.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_simple.sql @@ -102,3 +102,15 @@ as $$select 22 + 22$$; select simplecaller(); select simplecaller(); + +-- Check handling of simple expression in a scrollable cursor (bug #18859) + +do $$ +declare + p_CurData refcursor; + val int; +begin + open p_CurData scroll for select 42; + fetch p_CurData into val; + raise notice 'val = %', val; +end; $$; From eb73906c1d72bfd2a1c385a20696d6111bbafe82 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Sun, 23 Mar 2025 20:41:16 +0200 Subject: [PATCH 275/796] Fix rare assertion failure in standby, if primary is restarted During hot standby, ExpireAllKnownAssignedTransactionIds() and ExpireOldKnownAssignedTransactionIds() functions mark old transactions as no-longer running, but they failed to update xactCompletionCount and latestCompletedXid. AFAICS it would not lead to incorrect query results, because those functions effectively turn in-progress transactions into aborted transactions and an MVCC snapshot considers both as "not visible". But it could surprise GetSnapshotDataReuse() and trigger the "TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin))" assertion in it, if the apparent xmin in a backend would move backwards. We saw this happen when GetCatalogSnapshot() would reuse an older catalog snapshot, when GetTransactionSnapshot() had already advanced TransactionXmin. The bug goes back all the way to commit 623a9ba79b in v14 that introduced the snapshot reuse mechanism, but it started to happen more frequently with commit 952365cded6 which removed a GetTransactionSnapshot() call from backend startup. That made it more likely for ExpireOldKnownAssignedTransactionIds() to be called between GetCatalogSnapshot() and the first GetTransactionSnapshot() in a backend. Andres Freund first spotted this assertion failure on buildfarm member 'skink'. Reproduction and analysis by Tomas Vondra. Backpatch-through: 14 Discussion: https://www.postgresql.org/message-id/oey246mcw43cy4qw2hqjmurbd62lfdpcuxyqiu7botx3typpax%40h7o7mfg5zmdj --- src/backend/storage/ipc/procarray.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 206b1955734a8..7b931f028e924 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -4496,9 +4496,23 @@ ExpireTreeKnownAssignedTransactionIds(TransactionId xid, int nsubxids, void ExpireAllKnownAssignedTransactionIds(void) { + FullTransactionId latestXid; + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); KnownAssignedXidsRemovePreceding(InvalidTransactionId); + /* Reset latestCompletedXid to nextXid - 1 */ + Assert(FullTransactionIdIsValid(TransamVariables->nextXid)); + latestXid = TransamVariables->nextXid; + FullTransactionIdRetreat(&latestXid); + TransamVariables->latestCompletedXid = latestXid; + + /* + * Any transactions that were in-progress were effectively aborted, so + * advance xactCompletionCount. + */ + TransamVariables->xactCompletionCount++; + /* * Reset lastOverflowedXid. Currently, lastOverflowedXid has no use after * the call of this function. But do this for unification with what @@ -4516,8 +4530,18 @@ ExpireAllKnownAssignedTransactionIds(void) void ExpireOldKnownAssignedTransactionIds(TransactionId xid) { + TransactionId latestXid; + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + /* As in ProcArrayEndTransaction, advance latestCompletedXid */ + latestXid = xid; + TransactionIdRetreat(latestXid); + MaintainLatestCompletedXidRecovery(latestXid); + + /* ... and xactCompletionCount */ + TransamVariables->xactCompletionCount++; + /* * Reset lastOverflowedXid if we know all transactions that have been * possibly running are being gone. Not doing so could cause an incorrect From c18dc72bed336c9e761358ecf8cbcafeb3a2425e Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Tue, 25 Mar 2025 05:49:47 +0200 Subject: [PATCH 276/796] postgres_fdw: Avoid pulling up restrict infos from subqueries Semi-join joins below left/right join are deparsed as subqueries. Thus, we can't refer to subqueries vars from upper relations. This commit avoids pulling conditions from them. Reported-by: Robins Tharakan Bug: #18852 Discussion: https://postgr.es/m/CAEP4nAzryLd3gwcUpFBAG9MWyDfMRX8ZjuyY2XXjyC_C6k%2B_Zw%40mail.gmail.com Author: Alexander Pyhalov Reviewed-by: Alexander Korotkov Backpatch-through: 17 --- .../postgres_fdw/expected/postgres_fdw.out | 38 +++++++++++++++++++ contrib/postgres_fdw/postgres_fdw.c | 32 ++++++++++++---- contrib/postgres_fdw/sql/postgres_fdw.sql | 17 +++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 9f75fed3f5d30..602067c4e474f 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -4926,6 +4926,44 @@ SELECT ft2.*, ft4.* FROM ft2 INNER JOIN ----+----+----+----+----+----+----+----+----+----+---- (0 rows) +-- Semi-join conditions shouldn't pop up as left/right join clauses. +SET enable_material TO off; +EXPLAIN (verbose, costs off) +SELECT x1.c1 FROM + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x1 + RIGHT JOIN + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x2 + ON (x1.c1 = x2.c1) +ORDER BY x1.c1 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft2.c1 + Relations: ((public.ft2 ft2_1) LEFT JOIN ((public.ft2) SEMI JOIN (public.ft4))) SEMI JOIN (public.ft4 ft4_1) + Remote SQL: SELECT s9.c1 FROM ("S 1"."T 1" r6 LEFT JOIN (SELECT r4."C 1" FROM "S 1"."T 1" r4 WHERE ((r4.c2 < 10)) AND EXISTS (SELECT NULL FROM "S 1"."T 3" r5 WHERE ((r4."C 1" = r5.c1)))) s9(c1) ON (((s9.c1 = r6."C 1")))) WHERE ((r6.c2 < 10)) AND EXISTS (SELECT NULL FROM "S 1"."T 3" r7 WHERE ((r6."C 1" = r7.c1))) ORDER BY s9.c1 ASC NULLS LAST LIMIT 10::bigint +(4 rows) + +SELECT x1.c1 FROM + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x1 + RIGHT JOIN + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x2 + ON (x1.c1 = x2.c1) +ORDER BY x1.c1 LIMIT 10; + c1 +---- + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + 18 + 20 +(10 rows) + +RESET enable_material; -- Can't push down semi-join with inner rel vars in targetlist EXPLAIN (verbose, costs off) SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index fc65d81e2177a..6f030e4f31b48 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -5953,17 +5953,33 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, break; case JOIN_LEFT: - fpinfo->joinclauses = list_concat(fpinfo->joinclauses, - fpinfo_i->remote_conds); - fpinfo->remote_conds = list_concat(fpinfo->remote_conds, - fpinfo_o->remote_conds); + + /* + * When semi-join is involved in the inner or outer part of the + * left join, it's deparsed as a subquery, and we can't refer to + * its vars on the upper level. + */ + if (bms_is_empty(fpinfo_i->hidden_subquery_rels)) + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo_i->remote_conds); + if (bms_is_empty(fpinfo_o->hidden_subquery_rels)) + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_o->remote_conds); break; case JOIN_RIGHT: - fpinfo->joinclauses = list_concat(fpinfo->joinclauses, - fpinfo_o->remote_conds); - fpinfo->remote_conds = list_concat(fpinfo->remote_conds, - fpinfo_i->remote_conds); + + /* + * When semi-join is involved in the inner or outer part of the + * right join, it's deparsed as a subquery, and we can't refer to + * its vars on the upper level. + */ + if (bms_is_empty(fpinfo_o->hidden_subquery_rels)) + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo_o->remote_conds); + if (bms_is_empty(fpinfo_i->hidden_subquery_rels)) + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_i->remote_conds); break; case JOIN_SEMI: diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index db69434188581..8acfb78f471cb 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1455,6 +1455,23 @@ SELECT ft2.*, ft4.* FROM ft2 INNER JOIN WHERE ft2.c1 > 900 ORDER BY ft2.c1 LIMIT 10; +-- Semi-join conditions shouldn't pop up as left/right join clauses. +SET enable_material TO off; +EXPLAIN (verbose, costs off) +SELECT x1.c1 FROM + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x1 + RIGHT JOIN + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x2 + ON (x1.c1 = x2.c1) +ORDER BY x1.c1 LIMIT 10; +SELECT x1.c1 FROM + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x1 + RIGHT JOIN + (SELECT * FROM ft2 WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = ft2.c1 AND ft2.c2 < 10)) x2 + ON (x1.c1 = x2.c1) +ORDER BY x1.c1 LIMIT 10; +RESET enable_material; + -- Can't push down semi-join with inner rel vars in targetlist EXPLAIN (verbose, costs off) SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE From bf25e2b13f5f93eefb90f73d79ede1cfd7324035 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Tue, 25 Mar 2025 12:48:48 +0200 Subject: [PATCH 277/796] postgres_fdw: Remove redundant check in semijoin_target_ok() If a var belongs to the innerrel of the joinrel, it's not possible that it belongs to the outerrel. This commit removes the redundant check from the if-clause but keeps it as an assertion. Discussion: https://postgr.es/m/flat/CAHewXN=8aW4hd_W71F7Ua4+_w0=bppuvvTEBFBF6G0NuSXLwUw@mail.gmail.com Author: Tender Wang Reviewed-by: Alexander Pyhalov Backpatch-through: 17 --- contrib/postgres_fdw/postgres_fdw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6f030e4f31b48..7c4b91e01fd72 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -5758,8 +5758,7 @@ semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, if (!IsA(var, Var)) continue; - if (bms_is_member(var->varno, innerrel->relids) && - !bms_is_member(var->varno, outerrel->relids)) + if (bms_is_member(var->varno, innerrel->relids)) { /* * The planner can create semi-join, which refers to inner rel @@ -5767,6 +5766,7 @@ semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, * exists() subquery, so can't handle references to inner rel in * the target list. */ + Assert(!bms_is_member(var->varno, outerrel->relids)); ok = false; break; } From 44aea5d7125833fe724976cfe14b977ae67e5399 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 25 Mar 2025 20:03:56 -0400 Subject: [PATCH 278/796] Fix order of -I switches for building pg_regress.o. We need the -I switch for libpq_srcdir to come before any -I switches injected by configure. Otherwise there is a risk of pulling in a mismatched version of libpq_fe.h from someplace like /usr/local/include, if the platform has another Postgres version installed there. This evidently accounts for today's buildfarm failures on "anaconda". In principle the -I switch for src/port/ is at similar hazard, and has been for a very long time. But the only .h files we keep there are pg_config_paths.h and pthread-win32.h, neither of which get installed on Unix-ish systems, so the odds of picking up a conflicting header seem pretty small. That doubtless accounts for the lack of prior reports. Back-patch to v17 where pg_regress acquired a build dependency on libpq_fe.h. We could go back further to fix the hazard for src/port/ in older branches, but it seems unlikely to be worth troubling over. Reported-by: Nathan Bossart Author: Tom Lane Discussion: https://postgr.es/m/Z-MhRzoc7t-nPUQG@nathan Backpatch-through: 17 --- src/test/regress/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 9003435aabeef..090e49ea55b6e 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -40,7 +40,7 @@ pg_regress$(X): pg_regress.o pg_regress_main.o $(WIN32RES) | submake-libpgport # dependencies ensure that path changes propagate pg_regress.o: pg_regress.c $(top_builddir)/src/port/pg_config_paths.h -pg_regress.o: override CPPFLAGS += -I$(top_builddir)/src/port -I$(libpq_srcdir) $(EXTRADEFS) +pg_regress.o: override CPPFLAGS := -I$(top_builddir)/src/port -I$(libpq_srcdir) $(EXTRADEFS) $(CPPFLAGS) # note: because of the submake dependency, this rule's action is really a no-op $(top_builddir)/src/port/pg_config_paths.h: | submake-libpgport From f8bd0d01cba8f43a6374c5b640fb6c19c746ba49 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Wed, 26 Mar 2025 17:46:51 +0900 Subject: [PATCH 279/796] Fix integer-overflow problem in scram_SaltedPassword() Setting the iteration count for SCRAM secret generation to INT_MAX will cause an infinite loop in scram_SaltedPassword() due to integer overflow, as the loop uses the "i <= iterations" comparison. To fix, use "i < iterations" instead. Back-patch to v16 where the user-settable GUC scram_iterations has been added. Author: Kevin K Biju Reviewed-by: Richard Guo Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CAM45KeEMm8hnxdTOxA98qhfZ9CzGDdgy3mxgJmy0c+2WwjA6Zg@mail.gmail.com --- src/common/scram-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/scram-common.c b/src/common/scram-common.c index 51cc12e8c3e33..c36ed7124c2a2 100644 --- a/src/common/scram-common.c +++ b/src/common/scram-common.c @@ -74,7 +74,7 @@ scram_SaltedPassword(const char *password, memcpy(result, Ui_prev, key_length); /* Subsequent iterations */ - for (i = 2; i <= iterations; i++) + for (i = 1; i < iterations; i++) { #ifndef FRONTEND /* From 1868572a14ec6180913e54983e95c605d2a0f3b5 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Wed, 26 Mar 2025 16:50:13 +0100 Subject: [PATCH 280/796] Keep the decompressed filter in brin_bloom_union The brin_bloom_union() function combines two BRIN summaries, by merging one filter into the other. With bloom, we have to decompress the filters first, but the function failed to update the summary to store the merged filter. As a consequence, the index may be missing some of the data, and return false negatives. This issue exists since BRIN bloom indexes were introduced in Postgres 14, but at that point the union function was called only when two sessions happened to summarize a range concurrently, which is rare. It got much easier to hit in 17, as parallel builds use the union function to merge summaries built by workers. Fixed by storing a pointer to the decompressed filter, and freeing the original one. Free the second filter too, if it was decompressed. The freeing is not strictly necessary, because the union is called in short-lived contexts, but it's tidy. Backpatch to 14, where BRIN bloom indexes were introduced. Reported by Arseniy Mukhin, investigation and fix by me. Reported-by: Arseniy Mukhin Discussion: https://postgr.es/m/18855-1cf1c8bcc22150e6%40postgresql.org Backpatch-through: 14 --- src/backend/access/brin/brin_bloom.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c index 89c4b02ed9ee5..354cc545843a9 100644 --- a/src/backend/access/brin/brin_bloom.c +++ b/src/backend/access/brin/brin_bloom.c @@ -693,6 +693,17 @@ brin_bloom_union(PG_FUNCTION_ARGS) /* update the number of bits set in the filter */ filter_a->nbits_set = pg_popcount((const char *) filter_a->data, nbytes); + /* if we decompressed filter_a, update the summary */ + if (PointerGetDatum(filter_a) != col_a->bv_values[0]) + { + pfree(DatumGetPointer(col_a->bv_values[0])); + col_a->bv_values[0] = PointerGetDatum(filter_a); + } + + /* also free filter_b, if it was decompressed */ + if (PointerGetDatum(filter_b) != col_b->bv_values[0]) + pfree(filter_b); + PG_RETURN_VOID(); } From 5aca67060c052b69421c7a69808d714337fa25cf Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 27 Mar 2025 10:20:45 +0900 Subject: [PATCH 281/796] doc: Correct description of values used in FSM for indexes The implementation of FSM for indexes is simpler than heap, where 0 is used to track if a page is in-use and (BLCKSZ - 1) if a page is free. One comment in indexfsm.c and one description in the documentation of pg_freespacemap were incorrect about that. Author: Alex Friedman Discussion: https://postgr.es/m/71eef655-c192-453f-ac45-2772fec2cb04@gmail.com Backpatch-through: 13 --- doc/src/sgml/pgfreespacemap.sgml | 2 +- src/backend/storage/freespace/indexfsm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/pgfreespacemap.sgml b/doc/src/sgml/pgfreespacemap.sgml index 829ad60f32fdc..3774a9f8c6b29 100644 --- a/doc/src/sgml/pgfreespacemap.sgml +++ b/doc/src/sgml/pgfreespacemap.sgml @@ -67,7 +67,7 @@ For indexes, what is tracked is entirely-unused pages, rather than free space within pages. Therefore, the values are not meaningful, just - whether a page is full or empty. + whether a page is in-use or empty. diff --git a/src/backend/storage/freespace/indexfsm.c b/src/backend/storage/freespace/indexfsm.c index 35fb41ea7d604..ea0557801c1de 100644 --- a/src/backend/storage/freespace/indexfsm.c +++ b/src/backend/storage/freespace/indexfsm.c @@ -16,7 +16,7 @@ * This is similar to the FSM used for heap, in freespace.c, but instead * of tracking the amount of free space on pages, we only track whether * pages are completely free or in-use. We use the same FSM implementation - * as for heaps, using BLCKSZ - 1 to denote used pages, and 0 for unused. + * as for heaps, using 0 to denote used pages, and (BLCKSZ - 1) for unused. * *------------------------------------------------------------------------- */ From 76630fed2697a8d8e332eedd750da86c81b26f6c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 27 Mar 2025 13:20:23 -0400 Subject: [PATCH 282/796] Prevent assertion failure in contrib/pg_freespacemap. Applying pg_freespacemap() to a relation lacking storage (such as a view) caused an assertion failure, although there was no ill effect in non-assert builds. Add an error check for that case. Bug: #18866 Reported-by: Robins Tharakan Author: Tender Wang Reviewed-by: Euler Taveira Discussion: https://postgr.es/m/18866-d68926d0f1c72d44@postgresql.org Backpatch-through: 13 --- contrib/pg_freespacemap/pg_freespacemap.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/pg_freespacemap/pg_freespacemap.c b/contrib/pg_freespacemap/pg_freespacemap.c index b82cab2d97ef4..2de3c764e7940 100644 --- a/contrib/pg_freespacemap/pg_freespacemap.c +++ b/contrib/pg_freespacemap/pg_freespacemap.c @@ -11,6 +11,7 @@ #include "access/relation.h" #include "funcapi.h" #include "storage/freespace.h" +#include "utils/rel.h" PG_MODULE_MAGIC; @@ -30,6 +31,13 @@ pg_freespace(PG_FUNCTION_ARGS) rel = relation_open(relid, AccessShareLock); + if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" does not have storage", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + if (blkno < 0 || blkno > MaxBlockNumber) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), From 87766a0479e03818765ee1297a8524326f534012 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Thu, 27 Mar 2025 22:57:34 +0100 Subject: [PATCH 283/796] Fix guc_malloc calls for consistency and OOM checks check_createrole_self_grant and check_synchronized_standby_slots were allocating memory on a LOG elevel without checking if the allocation succeeded or not, which would have led to a segfault on allocation failure. On top of that, a number of callsites were using the ERROR level, relying on erroring out rather than returning false to allow the GUC machinery handle it gracefully. Other callsites used WARNING instead of LOG. While neither being not wrong, this changes all check_ functions do it consistently with LOG. init_custom_variable gets a promoted elevel to FATAL to keep the guc_malloc error handling in line with the rest of the error handling in that function which already call FATAL. If we encounter an OOM in this callsite there is no graceful handling to be had, better to error out hard. Backpatch the fix to check_createrole_self_grant down to v16 and the fix to check_synchronized_standby_slots down to v17 where they were introduced. Author: Daniel Gustafsson Reported-by: Nikita Reviewed-by: Tom Lane Bug: #18845 Discussion: https://postgr.es/m/18845-582c6e10247377ec@postgresql.org Backpatch-through: 16 --- src/backend/commands/user.c | 2 ++ src/backend/replication/slot.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index e204eb5e5d145..aed01cf4cae9e 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -2553,6 +2553,8 @@ check_createrole_self_grant(char **newval, void **extra, GucSource source) list_free(elemlist); result = (unsigned *) guc_malloc(LOG, sizeof(unsigned)); + if (!result) + return false; *result = options; *extra = result; diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 780d22afbca51..a1d4768623f55 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -2521,6 +2521,8 @@ check_synchronized_standby_slots(char **newval, void **extra, GucSource source) /* GUC extra value must be guc_malloc'd, not palloc'd */ config = (SyncStandbySlotsConfigData *) guc_malloc(LOG, size); + if (!config) + return false; /* Transform the data into SyncStandbySlotsConfigData */ config->nslotnames = list_length(elemlist); From b34c93f31f8952db9902ffba726970dde632c158 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Sat, 29 Mar 2025 09:50:14 +0000 Subject: [PATCH 284/796] Fix MERGE with DO NOTHING actions into a partitioned table. ExecInitPartitionInfo() duplicates much of the logic in ExecInitMerge(), except that it failed to handle DO NOTHING actions. This would cause an "unknown action in MERGE WHEN clause" error if a MERGE with any DO NOTHING actions attempted to insert into a partition not already initialised by ExecInitModifyTable(). Bug: #18871 Reported-by: Alexander Lakhin Author: Tender Wang Reviewed-by: Gurjeet Singh Discussion: https://postgr.es/m/18871-b44e3c96de3bd2e8%40postgresql.org Backpatch-through: 15 --- src/backend/executor/execPartition.c | 4 +++- src/backend/executor/nodeModifyTable.c | 2 +- src/test/regress/expected/merge.out | 17 +++++++++++++++++ src/test/regress/sql/merge.sql | 13 +++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 7651886229114..a3602b816871e 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -872,7 +872,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, * reference and make copy for this relation, converting stuff that * references attribute numbers to match this relation's. * - * This duplicates much of the logic in ExecInitMerge(), so something + * This duplicates much of the logic in ExecInitMerge(), so if something * changes there, look here too. */ if (node && node->operation == CMD_MERGE) @@ -952,6 +952,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, NULL); break; case CMD_DELETE: + case CMD_NOTHING: + /* Nothing to do */ break; default: diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 02be41845accd..d84e6dba45ac4 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3600,7 +3600,7 @@ ExecInitMerge(ModifyTableState *mtstate, EState *estate) case CMD_NOTHING: break; default: - elog(ERROR, "unknown operation"); + elog(ERROR, "unknown action in MERGE WHEN clause"); break; } } diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index 521d70a891986..e9c9b15d3c80d 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -2058,6 +2058,23 @@ SELECT * FROM pa_target ORDER BY tid; 15 | 1500 | initial (8 rows) +ROLLBACK; +-- bug #18871: ExecInitPartitionInfo()'s handling of DO NOTHING actions +BEGIN; +TRUNCATE pa_target; +MERGE INTO pa_target t + USING (VALUES (10, 100)) AS s(sid, delta) + ON t.tid = s.sid + WHEN NOT MATCHED THEN + INSERT VALUES (1, 10, 'inserted by merge') + WHEN MATCHED THEN + DO NOTHING; +SELECT * FROM pa_target ORDER BY tid, val; + tid | balance | val +-----+---------+------------------- + 1 | 10 | inserted by merge +(1 row) + ROLLBACK; DROP TABLE pa_target CASCADE; -- The target table is partitioned in the same way, but this time by attaching diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index 5ddcca84f8292..556777e4f4b51 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -1269,6 +1269,19 @@ MERGE INTO pa_target t SELECT * FROM pa_target ORDER BY tid; ROLLBACK; +-- bug #18871: ExecInitPartitionInfo()'s handling of DO NOTHING actions +BEGIN; +TRUNCATE pa_target; +MERGE INTO pa_target t + USING (VALUES (10, 100)) AS s(sid, delta) + ON t.tid = s.sid + WHEN NOT MATCHED THEN + INSERT VALUES (1, 10, 'inserted by merge') + WHEN MATCHED THEN + DO NOTHING; +SELECT * FROM pa_target ORDER BY tid, val; +ROLLBACK; + DROP TABLE pa_target CASCADE; -- The target table is partitioned in the same way, but this time by attaching From 3d4f99f682b1b6135698756a3a55af1d5c092088 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Apr 2025 16:49:51 -0400 Subject: [PATCH 285/796] Fix detection and handling of strchrnul() for macOS 15.4. As of 15.4, macOS has strchrnul(), but access to it is blocked behind a check for MACOSX_DEPLOYMENT_TARGET >= 15.4. But our does-it-link configure check finds it, so we try to use it, and fail with the present default deployment target (namely 15.0). This accounts for today's buildfarm failures on indri and sifaka. This is the identical problem that we faced some years ago when Apple introduced preadv and pwritev in the same way. We solved that in commit f014b1b9b by using AC_CHECK_DECLS instead of AC_CHECK_FUNCS to check the functions' availability. So do the same now for strchrnul(). Interestingly, we already had a workaround for "the link check doesn't agree with " cases with glibc, which we no longer need since only the header declaration is being checked. Testing this revealed that the meson version of this check has never worked, because it failed to use "-Werror=unguarded-availability-new". (Apparently nobody's tried to build with meson on macOS versions that lack preadv/pwritev as standard.) Adjust that while at it. Also, we had never put support for "-Werror=unguarded-availability-new" into v13, but we need that now. Co-authored-by: Tom Lane Co-authored-by: Peter Eisentraut Discussion: https://postgr.es/m/385134.1743523038@sss.pgh.pa.us Backpatch-through: 13 --- configure | 14 +++++++++++++- configure.ac | 2 +- meson.build | 21 ++++++++++++++++++--- src/include/pg_config.h.in | 7 ++++--- src/port/snprintf.c | 29 +++++++++++++---------------- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/configure b/configure index 2fd0f0c0f4457..f3a18ef73a7f4 100755 --- a/configure +++ b/configure @@ -15419,7 +15419,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strchrnul strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15963,6 +15963,18 @@ cat >>confdefs.h <<_ACEOF #define HAVE_DECL_PWRITEV $ac_have_decl _ACEOF +ac_fn_c_check_decl "$LINENO" "strchrnul" "ac_cv_have_decl_strchrnul" "#include +" +if test "x$ac_cv_have_decl_strchrnul" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_STRCHRNUL $ac_have_decl +_ACEOF + # This is probably only present on macOS, but may as well check always ac_fn_c_check_decl "$LINENO" "F_FULLFSYNC" "ac_cv_have_decl_F_FULLFSYNC" "#include diff --git a/configure.ac b/configure.ac index 83cfa523ad01c..82ca2b69dfe18 100644 --- a/configure.ac +++ b/configure.ac @@ -1799,7 +1799,6 @@ AC_CHECK_FUNCS(m4_normalize([ pthread_is_threaded_np setproctitle setproctitle_fast - strchrnul strsignal syncfs sync_file_range @@ -1839,6 +1838,7 @@ AC_CHECK_DECLS([strlcat, strlcpy, strnlen]) # won't handle deployment target restrictions on macOS AC_CHECK_DECLS([preadv], [], [], [#include ]) AC_CHECK_DECLS([pwritev], [], [], [#include ]) +AC_CHECK_DECLS([strchrnul], [], [], [#include ]) # This is probably only present on macOS, but may as well check always AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include ]) diff --git a/meson.build b/meson.build index 7c6483055443b..933290e85d576 100644 --- a/meson.build +++ b/meson.build @@ -2478,6 +2478,7 @@ decl_checks = [ decl_checks += [ ['preadv', 'sys/uio.h'], ['pwritev', 'sys/uio.h'], + ['strchrnul', 'string.h'], ] # Check presence of some optional LLVM functions. @@ -2494,8 +2495,23 @@ foreach c : decl_checks args = c.get(2, {}) varname = 'HAVE_DECL_' + func.underscorify().to_upper() - found = cc.has_header_symbol(header, func, - args: test_c_args, include_directories: postgres_inc, + found = cc.compiles(''' +#include <@0@> + +int main() +{ +#ifndef @1@ + (void) @1@; +#endif + +return 0; +} +'''.format(header, func), + name: 'test whether @0@ is declared'.format(func), + # need to add cflags_warn to get at least + # -Werror=unguarded-availability-new if applicable + args: test_c_args + cflags_warn, + include_directories: postgres_inc, kwargs: args) cdata.set10(varname, found, description: '''Define to 1 if you have the declaration of `@0@', and to 0 if you @@ -2733,7 +2749,6 @@ func_checks = [ ['shm_unlink', {'dependencies': [rt_dep], 'define': false}], ['shmget', {'dependencies': [cygipc_dep], 'define': false}], ['socket', {'dependencies': [socket_dep], 'define': false}], - ['strchrnul'], ['strerror_r', {'dependencies': [thread_dep]}], ['strlcat'], ['strlcpy'], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index aa94e038953cf..026c54859df37 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -115,6 +115,10 @@ don't. */ #undef HAVE_DECL_PWRITEV +/* Define to 1 if you have the declaration of `strchrnul', and to 0 if you + don't. */ +#undef HAVE_DECL_STRCHRNUL + /* Define to 1 if you have the declaration of `strlcat', and to 0 if you don't. */ #undef HAVE_DECL_STRLCAT @@ -396,9 +400,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H -/* Define to 1 if you have the `strchrnul' function. */ -#undef HAVE_STRCHRNUL - /* Define to 1 if you have the `strerror_r' function. */ #undef HAVE_STRERROR_R diff --git a/src/port/snprintf.c b/src/port/snprintf.c index 884f0262dd15c..14fb1783866c5 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -338,13 +338,22 @@ static void leading_pad(int zpad, int signvalue, int *padlen, static void trailing_pad(int padlen, PrintfTarget *target); /* - * If strchrnul exists (it's a glibc-ism), it's a good bit faster than the - * equivalent manual loop. If it doesn't exist, provide a replacement. + * If strchrnul exists (it's a glibc-ism, but since adopted by some other + * platforms), it's a good bit faster than the equivalent manual loop. + * Use it if possible, and if it doesn't exist, use this replacement. * * Note: glibc declares this as returning "char *", but that would require * casting away const internally, so we don't follow that detail. + * + * Note: macOS has this too as of Sequoia 15.4, but it's hidden behind + * a deployment-target check that causes compile errors if the deployment + * target isn't high enough. So !HAVE_DECL_STRCHRNUL may mean "yes it's + * declared, but it doesn't compile". To avoid failing in that scenario, + * use a macro to avoid matching 's name. */ -#ifndef HAVE_STRCHRNUL +#if !HAVE_DECL_STRCHRNUL + +#define strchrnul pg_strchrnul static inline const char * strchrnul(const char *s, int c) @@ -354,19 +363,7 @@ strchrnul(const char *s, int c) return s; } -#else - -/* - * glibc's declares strchrnul only if _GNU_SOURCE is defined. - * While we typically use that on glibc platforms, configure will set - * HAVE_STRCHRNUL whether it's used or not. Fill in the missing declaration - * so that this file will compile cleanly with or without _GNU_SOURCE. - */ -#ifndef _GNU_SOURCE -extern char *strchrnul(const char *s, int c); -#endif - -#endif /* HAVE_STRCHRNUL */ +#endif /* !HAVE_DECL_STRCHRNUL */ /* From 87fd270979f3dab72de095b21cf651453233e1b5 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 2 Apr 2025 11:57:27 +1300 Subject: [PATCH 286/796] Fix planner's failure to identify multiple hashable ScalarArrayOpExprs 50e17ad28 (v14) and 29f45e299 (v15) made it so the planner could identify IN and NOT IN clauses which have Const lists as right-hand arguments and when an appropriate hash function is available for the data types, mark the ScalarArrayOpExpr as hashable so the executor could execute it more optimally by building and probing a hash table during expression evaluation. These commits both worked correctly when there was only a single ScalarArrayOpExpr in the given expression being processed by the planner, but when there were multiple, only the first was checked and any subsequent ones were not identified, which resulted in less optimal expression evaluation during query execution for all but the first found ScalarArrayOpExpr. Backpatch to 14, where 50e17ad28 was introduced. Author: David Geier Discussion: https://postgr.es/m/29a76f51-97b0-4c07-87b7-ec8e3b5345c9@gmail.com Backpatch-through: 14 --- src/backend/optimizer/util/clauses.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 8e39795e24509..2a599f84086fc 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2326,7 +2326,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context) /* Looks good. Fill in the hash functions */ saop->hashfuncid = lefthashfunc; } - return true; + return false; } } else /* !saop->useOr */ @@ -2364,7 +2364,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context) */ saop->negfuncid = get_opcode(negator); } - return true; + return false; } } } From 4178eb8538a6eb71555ad1c07d4bf4f2a07684fb Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 2 Apr 2025 14:03:48 +1300 Subject: [PATCH 287/796] Doc: add information about partition locking The documentation around locking of partitions for the executor startup phase of run-time partition pruning wasn't clear about which partitions were being locked. Fix that. Reviewed-by: Tender Wang Discussion: https://postgr.es/m/CAApHDvp738G75HfkKcfXaf3a8s%3D6mmtOLh46tMD0D2hAo1UCzA%40mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/ddl.sgml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index c150dd217f9e2..bef1275871198 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4986,7 +4986,9 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; It is possible to determine the number of partitions which were removed during this phase by observing the Subplans Removed property in the - EXPLAIN output. + EXPLAIN output. It's important to note that any + partitions removed by the partition pruning done at this stage are + still locked at the beginning of execution. From 4e2e2fceee4a00333ad82f75307869552cf8442a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 2 Apr 2025 14:34:24 +0200 Subject: [PATCH 288/796] Fix code comment The changes made in commit d2b4b4c2259 contained incorrect comments: They said that certain forward declarations were necessary to "avoid including pathnodes.h here", but the file is itself pathnodes.h! So change the comment to just say it's a forward declaration in one case, and in the other case we don't need the declaration at all because it already appeared earlier in the file. --- src/include/nodes/pathnodes.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 50b09781f25ca..576f1c7e9b911 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1101,8 +1101,7 @@ typedef struct IndexOptInfo IndexOptInfo; #define HAVE_INDEXOPTINFO_TYPEDEF 1 #endif -struct IndexPath; /* avoid including pathnodes.h here */ -struct PlannerInfo; /* avoid including pathnodes.h here */ +struct IndexPath; /* forward declaration */ struct IndexOptInfo { From 1a418b1e198467a0f6f36751cb23b88372646bc5 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 2 Apr 2025 11:13:01 -0400 Subject: [PATCH 289/796] Need to do CommandCounterIncrement after StoreAttrMissingVal. Without this, an additional change to the same pg_attribute row within the same command will fail. This is possible at least with ALTER TABLE ADD COLUMN on a multiple-inheritance-pathway structure. (Another potential hazard is that immediately-following operations might not see the missingval.) Introduced by 95f650674, which split the former coding that used a single pg_attribute update to change both atthasdef and atthasmissing/attmissingval into two updates, but missed that this should entail two CommandCounterIncrements as well. Like that fix, back-patch through v13. Reported-by: Alexander Lakhin Author: Tender Wang Reviewed-by: Tom Lane Discussion: https://postgr.es/m/025a3ffa-5eff-4a88-97fb-8f583b015965@gmail.com Backpatch-through: 13 --- src/backend/commands/tablecmds.c | 2 ++ src/test/regress/expected/inherit.out | 15 ++++++++++++++- src/test/regress/sql/inherit.sql | 3 ++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 9b5a44ba4f2c4..e618154f08bb6 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7345,6 +7345,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, if (!missingIsNull) { StoreAttrMissingVal(rel, attribute->attnum, missingval); + /* Make the additional catalog change visible */ + CommandCounterIncrement(); has_missing = true; } FreeExecutorState(estate); diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 85240a9b0bbf2..689f4242a1ff6 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1093,17 +1093,30 @@ CREATE TABLE inhta (); CREATE TABLE inhtb () INHERITS (inhta); CREATE TABLE inhtc () INHERITS (inhtb); CREATE TABLE inhtd () INHERITS (inhta, inhtb, inhtc); -ALTER TABLE inhta ADD COLUMN i int; +ALTER TABLE inhta ADD COLUMN i int, ADD COLUMN j bigint DEFAULT 1; NOTICE: merging definition of column "i" for child "inhtd" NOTICE: merging definition of column "i" for child "inhtd" +NOTICE: merging definition of column "j" for child "inhtd" +NOTICE: merging definition of column "j" for child "inhtd" \d+ inhta Table "public.inhta" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- i | integer | | | | plain | | + j | bigint | | | 1 | plain | | Child tables: inhtb, inhtd +\d+ inhtd + Table "public.inhtd" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + i | integer | | | | plain | | + j | bigint | | | 1 | plain | | +Inherits: inhta, + inhtb, + inhtc + DROP TABLE inhta, inhtb, inhtc, inhtd; -- Test for renaming in diamond inheritance CREATE TABLE inht2 (x int) INHERITS (inht1); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 51251b0e5116e..572512cba011d 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -377,8 +377,9 @@ CREATE TABLE inhta (); CREATE TABLE inhtb () INHERITS (inhta); CREATE TABLE inhtc () INHERITS (inhtb); CREATE TABLE inhtd () INHERITS (inhta, inhtb, inhtc); -ALTER TABLE inhta ADD COLUMN i int; +ALTER TABLE inhta ADD COLUMN i int, ADD COLUMN j bigint DEFAULT 1; \d+ inhta +\d+ inhtd DROP TABLE inhta, inhtb, inhtc, inhtd; -- Test for renaming in diamond inheritance From 4e01976241e202c1c141494271dc9cf4ac70050d Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 2 Apr 2025 14:25:17 -0400 Subject: [PATCH 290/796] Remove HeapBitmapScan's skip_fetch optimization The optimization does not take the removal of TIDs by a concurrent vacuum into account. The concurrent vacuum can remove dead TIDs and make pages ALL_VISIBLE while those dead TIDs are referenced in the bitmap. This can lead to a skip_fetch scan returning too many tuples. It likely would be possible to implement this optimization safely, but we don't have the necessary infrastructure in place. Nor is it clear that it's worth building that infrastructure, given how limited the skip_fetch optimization is. In the backbranches we just disable the optimization by always passing need_tuples=true to table_beginscan_bm(). We can't perform API/ABI changes in the backbranches and we want to make the change as minimal as possible. Author: Matthias van de Meent Reported-By: Konstantin Knizhnik Discussion: https://postgr.es/m/CAEze2Wg3gXXZTr6_rwC+s4-o2ZVFB5F985uUSgJTsECx6AmGcQ@mail.gmail.com Backpatch-through: 13 --- src/backend/executor/nodeBitmapHeapscan.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c index 6b48a6d8350fb..3d26d96cadd71 100644 --- a/src/backend/executor/nodeBitmapHeapscan.c +++ b/src/backend/executor/nodeBitmapHeapscan.c @@ -185,8 +185,21 @@ BitmapHeapNext(BitmapHeapScanState *node) */ if (!scan) { - bool need_tuples = false; + bool need_tuples = true; + /* + * Unfortunately it turns out that the below optimization does not + * take the removal of TIDs by a concurrent vacuum into + * account. The concurrent vacuum can remove dead TIDs and make + * pages ALL_VISIBLE while those dead TIDs are referenced in the + * bitmap. This would lead to a !need_tuples scan returning too + * many tuples. + * + * In the back-branches, we therefore simply disable the + * optimization. Removing all the relevant code would be too + * invasive (and a major backpatching pain). + */ +#ifdef NOT_ANYMORE /* * We can potentially skip fetching heap pages if we do not need * any columns of the table, either for checking non-indexable @@ -197,6 +210,7 @@ BitmapHeapNext(BitmapHeapScanState *node) */ need_tuples = (node->ss.ps.plan->qual != NIL || node->ss.ps.plan->targetlist != NIL); +#endif scan = table_beginscan_bm(node->ss.ss_currentRelation, node->ss.ps.state->es_snapshot, From edafe501287c416d378d2652406fb1d8af5f1311 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 2 Apr 2025 16:17:43 -0400 Subject: [PATCH 291/796] Remove unnecessary type violation in tsvectorrecv(). compareentry() is declared to work on WordEntryIN structs, but tsvectorrecv() is using it in two places to work on WordEntry structs. This is almost okay, since WordEntry is the first field of WordEntryIN. But on machines with 8-byte pointers, WordEntryIN will have a larger alignment spec than WordEntry, and it's at least theoretically possible that the compiler could generate code that depends on the larger alignment. Given the lack of field reports, this may be just a hypothetical bug that upsets nothing except sanitizer tools. Or it may be real on certain hardware but nobody's tried to use tsvectorrecv() on such hardware. In any case we should fix it, and the fix is trivial: just change compareentry() so that it works on WordEntry without any mention of WordEntryIN. We can also get rid of the quite-useless intermediate function WordEntryCMP. Bug: #18875 Reported-by: Alexander Lakhin Author: Tom Lane Discussion: https://postgr.es/m/18875-07a29c49c825a608@postgresql.org Backpatch-through: 13 --- src/backend/utils/adt/tsvector.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/tsvector.c b/src/backend/utils/adt/tsvector.c index 4c6a15757a73e..638c4dc097def 100644 --- a/src/backend/utils/adt/tsvector.c +++ b/src/backend/utils/adt/tsvector.c @@ -25,7 +25,7 @@ typedef struct { - WordEntry entry; /* must be first! */ + WordEntry entry; /* must be first, see compareentry */ WordEntryPos *pos; int poslen; /* number of elements in pos */ } WordEntryIN; @@ -79,16 +79,19 @@ uniquePos(WordEntryPos *a, int l) return res + 1 - a; } -/* Compare two WordEntryIN values for qsort */ +/* + * Compare two WordEntry structs for qsort_arg. This can also be used on + * WordEntryIN structs, since those have WordEntry as their first field. + */ static int compareentry(const void *va, const void *vb, void *arg) { - const WordEntryIN *a = (const WordEntryIN *) va; - const WordEntryIN *b = (const WordEntryIN *) vb; + const WordEntry *a = (const WordEntry *) va; + const WordEntry *b = (const WordEntry *) vb; char *BufferStr = (char *) arg; - return tsCompareString(&BufferStr[a->entry.pos], a->entry.len, - &BufferStr[b->entry.pos], b->entry.len, + return tsCompareString(&BufferStr[a->pos], a->len, + &BufferStr[b->pos], b->len, false); } @@ -167,12 +170,6 @@ uniqueentry(WordEntryIN *a, int l, char *buf, int *outbuflen) return res + 1 - a; } -static int -WordEntryCMP(WordEntry *a, WordEntry *b, char *buf) -{ - return compareentry(a, b, buf); -} - Datum tsvectorin(PG_FUNCTION_ARGS) @@ -511,7 +508,7 @@ tsvectorrecv(PG_FUNCTION_ARGS) datalen += lex_len; - if (i > 0 && WordEntryCMP(&vec->entries[i], + if (i > 0 && compareentry(&vec->entries[i], &vec->entries[i - 1], STRPTR(vec)) <= 0) needSort = true; From 22210a2fe3521e72669941144fbbccfce8afea9f Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Thu, 3 Apr 2025 10:30:02 -0700 Subject: [PATCH 292/796] Restrict copying of invalidated replication slots. Previously, invalidated logical and physical replication slots could be copied using the pg_copy_logical_replication_slot and pg_copy_physical_replication_slot functions. Replication slots that were invalidated for reasons other than WAL removal retained their restart_lsn. This meant that a new slot copied from an invalidated slot could have a restart_lsn pointing to a WAL segment that might have already been removed. This commit restricts the copying of invalidated replication slots. Backpatch to v16, where slots could retain their restart_lsn when invalidated for reasons other than WAL removal. For v15 and earlier, this check is not required since slots can only be invalidated due to WAL removal, and existing checks already handle this issue. Author: Shlok Kyal Reviewed-by: vignesh C Reviewed-by: Zhijie Hou Reviewed-by: Peter Smith Reviewed-by: Masahiko Sawada Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/CANhcyEU65aH0VYnLiu%3DOhNNxhnhNhwcXBeT-jvRe1OiJTo_Ayg%40mail.gmail.com Backpatch-through: 16 --- doc/src/sgml/func.sgml | 6 ++++-- src/backend/replication/slotfuncs.c | 21 +++++++++++++++++++ .../t/035_standby_logical_decoding.pl | 9 ++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index ebd42096795c4..f441ec43314d7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29095,7 +29095,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The copied physical slot starts to reserve WAL from the same LSN as the source slot. temporary is optional. If temporary - is omitted, the same value as the source slot is used. + is omitted, the same value as the source slot is used. Copy of an + invalidated slot is not allowed. @@ -29120,7 +29121,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The failover option of the source logical slot is not copied and is set to false by default. This is to avoid the risk of being unable to continue logical replication - after failover to standby where the slot is being synchronized. + after failover to standby where the slot is being synchronized. Copy of + an invalidated slot is not allowed. diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 43c3cc7336b62..01e98bc1ceff7 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -681,6 +681,13 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("cannot copy a replication slot that doesn't reserve WAL"))); + /* Cannot copy an invalidated replication slot */ + if (first_slot_contents.data.invalidated != RS_INVAL_NONE) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot copy invalidated replication slot \"%s\"", + NameStr(*src_name))); + /* Overwrite params from optional arguments */ if (PG_NARGS() >= 3) temporary = PG_GETARG_BOOL(2); @@ -782,6 +789,20 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) NameStr(*src_name)), errhint("Retry when the source replication slot's confirmed_flush_lsn is valid."))); + /* + * Copying an invalid slot doesn't make sense. Note that the source + * slot can become invalid after we create the new slot and copy the + * data of source slot. This is possible because the operations in + * InvalidateObsoleteReplicationSlots() are not serialized with this + * function. Even though we can't detect such a case here, the copied + * slot will become invalid in the next checkpoint cycle. + */ + if (second_slot_contents.data.invalidated != RS_INVAL_NONE) + ereport(ERROR, + errmsg("cannot copy replication slot \"%s\"", + NameStr(*src_name)), + errdetail("The source replication slot was invalidated during the copy operation.")); + /* Install copied values again */ SpinLockAcquire(&MyReplicationSlot->mutex); MyReplicationSlot->effective_xmin = copy_effective_xmin; diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index aeb79f51e7131..4eca17885d6ea 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -583,6 +583,15 @@ sub wait_until_vacuum_can_remove "can no longer get changes from replication slot \"vacuum_full_activeslot\"" ); +# Attempt to copy an invalidated logical replication slot +($result, $stdout, $stderr) = $node_standby->psql( + 'postgres', + qq[select pg_copy_logical_replication_slot('vacuum_full_inactiveslot', 'vacuum_full_inactiveslot_copy');], + replication => 'database'); +ok( $stderr =~ + /ERROR: cannot copy invalidated replication slot "vacuum_full_inactiveslot"/, + "invalidated slot cannot be copied"); + # Turn hot_standby_feedback back on change_hot_standby_feedback_and_wait_for_xmins(1, 1); From 3ea4f92e88feff9976021022654b792c3c0f941e Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Fri, 4 Apr 2025 13:09:06 +0900 Subject: [PATCH 293/796] Fix logical decoding regression tests to correctly check slot existence. The regression tests for logical decoding verify whether a logical slot exists or has been dropped. Previously, these tests attempted to retrieve "slot_name" from the result of slot(), but since "slot_name" was not included in the result, slot()->{'slot_name'} always returned undef, leading to incorrect behavior. This commit fixes the issue by checking the "plugin" field in the result of slot() instead, ensuring the tests properly verify slot existence. Back-patch to all supported versions. Author: Hayato Kuroda Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/OSCPR01MB149667EC4E738769CA80B7EA5F5AE2@OSCPR01MB14966.jpnprd01.prod.outlook.com Backpatch-through: 13 --- src/test/recovery/t/006_logical_decoding.pl | 8 ++++---- src/test/recovery/t/010_logical_decoding_timelines.pl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl index b95d95c06f942..75fd8261ce15f 100644 --- a/src/test/recovery/t/006_logical_decoding.pl +++ b/src/test/recovery/t/006_logical_decoding.pl @@ -158,8 +158,8 @@ is($node_primary->psql('postgres', 'DROP DATABASE otherdb'), 3, 'dropping a DB with active logical slots fails'); $pg_recvlogical->kill_kill; - is($node_primary->slot('otherdb_slot')->{'slot_name'}, - undef, 'logical slot still exists'); + is($node_primary->slot('otherdb_slot')->{'plugin'}, + 'test_decoding', 'logical slot still exists'); } $node_primary->poll_query_until('otherdb', @@ -168,8 +168,8 @@ is($node_primary->psql('postgres', 'DROP DATABASE otherdb'), 0, 'dropping a DB with inactive logical slots succeeds'); -is($node_primary->slot('otherdb_slot')->{'slot_name'}, - undef, 'logical slot was actually dropped with DB'); +is($node_primary->slot('otherdb_slot')->{'plugin'}, + '', 'logical slot was actually dropped with DB'); # Test logical slot advancing and its durability. # Passing failover=true (last arg) should not have any impact on advancing. diff --git a/src/test/recovery/t/010_logical_decoding_timelines.pl b/src/test/recovery/t/010_logical_decoding_timelines.pl index afcd5241aa665..7685b98589bc1 100644 --- a/src/test/recovery/t/010_logical_decoding_timelines.pl +++ b/src/test/recovery/t/010_logical_decoding_timelines.pl @@ -94,8 +94,8 @@ 'postgres', q[SELECT 1 FROM pg_database WHERE datname = 'dropme']), '', 'dropped DB dropme on standby'); -is($node_primary->slot('dropme_slot')->{'slot_name'}, - undef, 'logical slot was actually dropped on standby'); +is($node_primary->slot('dropme_slot')->{'plugin'}, + '', 'logical slot was actually dropped on standby'); # Back to testing failover... $node_primary->safe_psql('postgres', From 7483dc3c353f3c902ac620ca6087e0a038ab4e10 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Fri, 4 Apr 2025 13:32:46 +0900 Subject: [PATCH 294/796] Fix logical decoding test to correctly check slot removal on standby. The regression test for logical decoding verifies whether a logical slot is correctly dropped on a standby when its associated database is dropped. However, the test mistakenly retrieved slot information from the primary instead of the standby, causing incorrect behavior. This commit fixes the issue by ensuring the test correctly checks the slot on the standby. Back-patch to all supported versions. Author: Hayato Kuroda Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/1fdfd020-a509-403c-bd8f-a04664aba148@oss.nttdata.com Backpatch-through: 13 --- src/test/recovery/t/010_logical_decoding_timelines.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/recovery/t/010_logical_decoding_timelines.pl b/src/test/recovery/t/010_logical_decoding_timelines.pl index 7685b98589bc1..40f4fb0d91821 100644 --- a/src/test/recovery/t/010_logical_decoding_timelines.pl +++ b/src/test/recovery/t/010_logical_decoding_timelines.pl @@ -94,7 +94,7 @@ 'postgres', q[SELECT 1 FROM pg_database WHERE datname = 'dropme']), '', 'dropped DB dropme on standby'); -is($node_primary->slot('dropme_slot')->{'plugin'}, +is($node_replica->slot('dropme_slot')->{'plugin'}, '', 'logical slot was actually dropped on standby'); # Back to testing failover... From f0f70f9b8f8785a10292fd0af9f9bec617043b85 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Fri, 4 Apr 2025 09:47:36 +0200 Subject: [PATCH 295/796] doc: Clarify the system value for sslrootcert The documentation for the special value "system" for sslrootcert could be misinterpreted to mean the default operating system CA store, which it may be, but it's defined to be the default CA store of the SSL lib used. Backpatch down to v16 where support for the system value was added. Author: Daniel Gustafsson Reviewed-by: George MacKerron Discussion: https://postgr.es/m/B3CBBAA3-6EA3-4AB7-8619-4BBFAB93DDB4@yesql.se Backpatch-through: 16 --- doc/src/sgml/libpq.sgml | 2 +- doc/src/sgml/runtime.sgml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index e3b74af96a64b..789a6ec24865d 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1999,7 +1999,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname The special value system may be specified instead, in - which case the system's trusted CA roots will be loaded. The exact + which case the trusted CA roots from the SSL implementation will be loaded. The exact locations of these root certificates differ by SSL implementation and platform. For OpenSSL in particular, the locations may be further modified by the SSL_CERT_DIR diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 893b30eb31a94..25ee5be5b1b64 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1986,7 +1986,8 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 sslmode=verify-ca or verify-full and have the appropriate root certificate file installed (). Alternatively the - system CA pool can be used using sslrootcert=system; in + system CA pool, as defined + by the SSL implementation, can be used using sslrootcert=system; in this case, sslmode=verify-full is forced for safety, since it is generally trivial to obtain certificates which are signed by a public CA. From 19213cc74b21c28cddd5eda5ce2e92b820be38d5 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Fri, 4 Apr 2025 13:49:00 +0300 Subject: [PATCH 296/796] Relax assertion in finding correct GiST parent Commit 28d3c2ddcf introduced an assertion that if the memorized downlink location in the insertion stack isn't valid, the parent's LSN should've changed too. Turns out that was too strict. In gistFindCorrectParent(), if we walk right, we update the parent's block number and clear its memorized 'downlinkoffnum'. That triggered the assertion on next call to gistFindCorrectParent(), if the parent needed to be split too. Relax the assertion, so that it's OK if downlinkOffnum is InvalidOffsetNumber. Backpatch to v13-, all supported versions. The assertion was added in commit 28d3c2ddcf in v12. Reported-by: Alexander Lakhin Reviewed-by: Tender Wang Discussion: https://www.postgresql.org/message-id/18396-03cac9beb2f7aac3@postgresql.org --- src/backend/access/gist/gist.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index ed4ffa63a7720..e49abbcb18afa 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -1043,12 +1043,19 @@ gistFindCorrectParent(Relation r, GISTInsertStack *child, bool is_build) /* * The page has changed since we looked. During normal operation, every * update of a page changes its LSN, so the LSN we memorized should have - * changed too. During index build, however, we don't WAL-log the changes - * until we have built the index, so the LSN doesn't change. There is no - * concurrent activity during index build, but we might have changed the - * parent ourselves. + * changed too. + * + * During index build, however, we don't WAL-log the changes until we have + * built the index, so the LSN doesn't change. There is no concurrent + * activity during index build, but we might have changed the parent + * ourselves. + * + * We will also get here if child->downlinkoffnum is invalid. That happens + * if 'parent' had been updated by an earlier call to this function on its + * grandchild, which had to move right. */ - Assert(parent->lsn != PageGetLSN(parent->page) || is_build); + Assert(parent->lsn != PageGetLSN(parent->page) || is_build || + child->downlinkoffnum == InvalidOffsetNumber); /* * Scan the page to re-find the downlink. If the page was split, it might From 0b835152ef5efae28026f29bb5712978660876ce Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 4 Apr 2025 20:11:48 -0400 Subject: [PATCH 297/796] Repair misbehavior with duplicate entries in FK SET column lists. Since v15 we've had an option to apply a foreign key constraint's ON DELETE SET DEFAULT or SET NULL action to just some of the referencing columns. There was not a check for duplicate entries in the list of columns-to-set, though. That caused a potential memory stomp in CreateConstraintEntry(), which incautiously assumed that the list of columns-to-set couldn't be longer than the number of key columns. Even after fixing that, the case doesn't work because you get an error like "multiple assignments to same column" from the SQL command that is generated to do the update. We could either raise an error for duplicate columns or silently suppress the dups, and after a bit of thought I chose to do the latter. This is motivated by the fact that duplicates in the FK column list are legal, so it's not real clear why duplicates in the columns-to-set list shouldn't be. Of course there's no need to actually set the column more than once. I left in the fix in CreateConstraintEntry() too, just because it didn't seem like such low-level code ought to be making assumptions about what it's handed. Bug: #18879 Reported-by: Yu Liang Author: Tom Lane Discussion: https://postgr.es/m/18879-259fc59d072bd4d7@postgresql.org Backpatch-through: 15 --- src/backend/catalog/pg_constraint.c | 3 +- src/backend/commands/tablecmds.c | 35 ++++++++++++++++++----- src/test/regress/expected/foreign_key.out | 3 +- src/test/regress/sql/foreign_key.sql | 3 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 3baf9231ed0c8..b6fd46b019fee 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -118,8 +118,9 @@ CreateConstraintEntry(const char *constraintName, if (foreignNKeys > 0) { Datum *fkdatums; + int nkeys = Max(foreignNKeys, numFkDeleteSetCols); - fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum)); + fkdatums = (Datum *) palloc(nkeys * sizeof(Datum)); for (i = 0; i < foreignNKeys; i++) fkdatums[i] = Int16GetDatum(foreignKey[i]); confkeyArray = construct_array_builtin(fkdatums, foreignNKeys, INT2OID); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e618154f08bb6..4846f6a8eaf98 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -512,8 +512,8 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo * Relation rel, Constraint *fkconstraint, bool recurse, bool recursing, LOCKMODE lockmode); -static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, - int numfksetcols, const int16 *fksetcolsattnums, +static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, + int numfksetcols, int16 *fksetcolsattnums, List *fksetcols); static ObjectAddress addFkConstraint(addFkConstraintSides fkside, char *constraintname, @@ -9716,9 +9716,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel), fkconstraint->fk_del_set_cols, fkdelsetcols, NULL); - validateFkOnDeleteSetColumns(numfks, fkattnum, - numfkdelsetcols, fkdelsetcols, - fkconstraint->fk_del_set_cols); + numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum, + numfkdelsetcols, + fkdelsetcols, + fkconstraint->fk_del_set_cols); /* * If the attribute list for the referenced table was omitted, lookup the @@ -10039,17 +10040,23 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * validateFkOnDeleteSetColumns * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...) * column lists are valid. + * + * If there are duplicates in the fksetcolsattnums[] array, this silently + * removes the dups. The new count of numfksetcols is returned. */ -void +static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, - int numfksetcols, const int16 *fksetcolsattnums, + int numfksetcols, int16 *fksetcolsattnums, List *fksetcols) { + int numcolsout = 0; + for (int i = 0; i < numfksetcols; i++) { int16 setcol_attnum = fksetcolsattnums[i]; bool seen = false; + /* Make sure it's in fkattnums[] */ for (int j = 0; j < numfks; j++) { if (fkattnums[j] == setcol_attnum) @@ -10067,7 +10074,21 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col))); } + + /* Now check for dups */ + seen = false; + for (int j = 0; j < numcolsout; j++) + { + if (fksetcolsattnums[j] == setcol_attnum) + { + seen = true; + break; + } + } + if (!seen) + fksetcolsattnums[numcolsout++] = setcol_attnum; } + return numcolsout; } /* diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index a7fe9407d25c2..eac9af9a858ba 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -772,7 +772,8 @@ CREATE TABLE FKTABLE ( fk_id_del_set_null int, fk_id_del_set_default int DEFAULT 0, FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null), - FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default) + -- this tests handling of duplicate entries in SET DEFAULT column list + FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default) ); SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid; pg_get_constraintdef diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 6aa675c72864a..2d7584b49fe47 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -473,7 +473,8 @@ CREATE TABLE FKTABLE ( fk_id_del_set_null int, fk_id_del_set_default int DEFAULT 0, FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null), - FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default) + -- this tests handling of duplicate entries in SET DEFAULT column list + FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default) ); SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid; From a5338f57a21f4040bd41e6b8a171ddf000e05813 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 5 Apr 2025 12:13:35 -0400 Subject: [PATCH 298/796] Avoid double transformation of json_array()'s subquery. transformJsonArrayQueryConstructor() applied transformStmt() to the same subquery tree twice. While this causes no issue in many cases, there are some where it causes a coredump, thanks to the parser's habit of scribbling on its input. Fix by making a copy before the first transformation (compare 0f43083d1). This is quite brute-force, but then so is the whole business of transforming the input twice. Per discussion in the bug thread, this implementation of json_array() parsing should be replaced completely. But that will take some work and will surely not be back-patchable, so for the moment let's take the easy way out. Oversight in 7081ac46a. Back-patch to v16 where that came in. Bug: #18877 Reported-by: Yu Liang Author: Tom Lane Discussion: https://postgr.es/m/18877-c3c3ad75845833bb@postgresql.org Backpatch-through: 16 --- src/backend/parser/parse_expr.c | 2 +- src/test/regress/expected/sqljson.out | 6 ++++++ src/test/regress/sql/sqljson.sql | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2258701f3013a..8423542021f30 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -3764,7 +3764,7 @@ transformJsonArrayQueryConstructor(ParseState *pstate, /* Transform query only for counting target list entries. */ qpstate = make_parsestate(pstate); - query = transformStmt(qpstate, ctor->query); + query = transformStmt(qpstate, copyObject(ctor->query)); if (count_nonjunk_tlist_entries(query->targetList) != 1) ereport(ERROR, diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 17a889eb8ef94..67b68dfc2325d 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -747,6 +747,12 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i [1, 2, 3] (1 row) +SELECT JSON_ARRAY(WITH x AS (SELECT 1) VALUES (TRUE)); + json_array +------------ + [true] +(1 row) + -- Should fail SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); ERROR: subquery must return only one column diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 3fd6ac260b8ed..343d344d2707f 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -199,6 +199,8 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL) --SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL); --SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb); SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); +SELECT JSON_ARRAY(WITH x AS (SELECT 1) VALUES (TRUE)); + -- Should fail SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); From c35454620bc313ec75926a748b661330aef6e83e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 5 Apr 2025 15:01:33 -0400 Subject: [PATCH 299/796] Fix parse_cte.c's failure to examine sub-WITHs in DML statements. makeDependencyGraphWalker thought that only SelectStmt nodes could contain a WithClause. Which was true in our original implementation of WITH, but astonishingly we missed updating this code when we added the ability to attach WITH to INSERT/UPDATE/DELETE (and later MERGE). Moreover, since it was coded to deliberately block recursion to a WithClause, even updating raw_expression_tree_walker didn't save it. The upshot of this was that we didn't see references to outer CTE names appearing within an inner WITH, and would neither complain about disallowed recursion nor account for such references when sorting CTEs into a usable order. The lack of complaints about this is perhaps not so surprising, because typical usage of WITH wouldn't hit either case. Still, it's pretty broken; failing to detect recursion here leads to assert failures or worse later on. Fix by factoring out the processing of sub-WITHs into a new function WalkInnerWith, and invoking that for all the statement types that can have WITH. Bug: #18878 Reported-by: Yu Liang Author: Tom Lane Discussion: https://postgr.es/m/18878-a26fa5ab6be2f2cf@postgresql.org Backpatch-through: 13 --- src/backend/parser/parse_cte.c | 152 +++++++++++++++++++++-------- src/test/regress/expected/with.out | 8 ++ src/test/regress/sql/with.sql | 7 ++ 3 files changed, 124 insertions(+), 43 deletions(-) diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index de9ae9b48348d..6ad9259823dd0 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -88,6 +88,7 @@ static void analyzeCTE(ParseState *pstate, CommonTableExpr *cte); /* Dependency processing functions */ static void makeDependencyGraph(CteState *cstate); static bool makeDependencyGraphWalker(Node *node, CteState *cstate); +static void WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate); static void TopologicalSort(ParseState *pstate, CteItem *items, int numitems); /* Recursion validity checker functions */ @@ -725,58 +726,69 @@ makeDependencyGraphWalker(Node *node, CteState *cstate) if (IsA(node, SelectStmt)) { SelectStmt *stmt = (SelectStmt *) node; - ListCell *lc; if (stmt->withClause) { - if (stmt->withClause->recursive) - { - /* - * In the RECURSIVE case, all query names of the WITH are - * visible to all WITH items as well as the main query. So - * push them all on, process, pop them all off. - */ - cstate->innerwiths = lcons(stmt->withClause->ctes, - cstate->innerwiths); - foreach(lc, stmt->withClause->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + /* Examine the WITH clause and the SelectStmt */ + WalkInnerWith(node, stmt->withClause, cstate); + /* We're done examining the SelectStmt */ + return false; + } + /* if no WITH clause, just fall through for normal processing */ + } + else if (IsA(node, InsertStmt)) + { + InsertStmt *stmt = (InsertStmt *) node; - (void) makeDependencyGraphWalker(cte->ctequery, cstate); - } - (void) raw_expression_tree_walker(node, - makeDependencyGraphWalker, - (void *) cstate); - cstate->innerwiths = list_delete_first(cstate->innerwiths); - } - else - { - /* - * In the non-RECURSIVE case, query names are visible to the - * WITH items after them and to the main query. - */ - cstate->innerwiths = lcons(NIL, cstate->innerwiths); - foreach(lc, stmt->withClause->ctes) - { - CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); - ListCell *cell1; + if (stmt->withClause) + { + /* Examine the WITH clause and the InsertStmt */ + WalkInnerWith(node, stmt->withClause, cstate); + /* We're done examining the InsertStmt */ + return false; + } + /* if no WITH clause, just fall through for normal processing */ + } + else if (IsA(node, DeleteStmt)) + { + DeleteStmt *stmt = (DeleteStmt *) node; - (void) makeDependencyGraphWalker(cte->ctequery, cstate); - /* note that recursion could mutate innerwiths list */ - cell1 = list_head(cstate->innerwiths); - lfirst(cell1) = lappend((List *) lfirst(cell1), cte); - } - (void) raw_expression_tree_walker(node, - makeDependencyGraphWalker, - (void *) cstate); - cstate->innerwiths = list_delete_first(cstate->innerwiths); - } - /* We're done examining the SelectStmt */ + if (stmt->withClause) + { + /* Examine the WITH clause and the DeleteStmt */ + WalkInnerWith(node, stmt->withClause, cstate); + /* We're done examining the DeleteStmt */ return false; } /* if no WITH clause, just fall through for normal processing */ } - if (IsA(node, WithClause)) + else if (IsA(node, UpdateStmt)) + { + UpdateStmt *stmt = (UpdateStmt *) node; + + if (stmt->withClause) + { + /* Examine the WITH clause and the UpdateStmt */ + WalkInnerWith(node, stmt->withClause, cstate); + /* We're done examining the UpdateStmt */ + return false; + } + /* if no WITH clause, just fall through for normal processing */ + } + else if (IsA(node, MergeStmt)) + { + MergeStmt *stmt = (MergeStmt *) node; + + if (stmt->withClause) + { + /* Examine the WITH clause and the MergeStmt */ + WalkInnerWith(node, stmt->withClause, cstate); + /* We're done examining the MergeStmt */ + return false; + } + /* if no WITH clause, just fall through for normal processing */ + } + else if (IsA(node, WithClause)) { /* * Prevent raw_expression_tree_walker from recursing directly into a @@ -790,6 +802,60 @@ makeDependencyGraphWalker(Node *node, CteState *cstate) (void *) cstate); } +/* + * makeDependencyGraphWalker's recursion into a statement having a WITH clause. + * + * This subroutine is concerned with updating the innerwiths list correctly + * based on the visibility rules for CTE names. + */ +static void +WalkInnerWith(Node *stmt, WithClause *withClause, CteState *cstate) +{ + ListCell *lc; + + if (withClause->recursive) + { + /* + * In the RECURSIVE case, all query names of the WITH are visible to + * all WITH items as well as the main query. So push them all on, + * process, pop them all off. + */ + cstate->innerwiths = lcons(withClause->ctes, cstate->innerwiths); + foreach(lc, withClause->ctes) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + + (void) makeDependencyGraphWalker(cte->ctequery, cstate); + } + (void) raw_expression_tree_walker(stmt, + makeDependencyGraphWalker, + (void *) cstate); + cstate->innerwiths = list_delete_first(cstate->innerwiths); + } + else + { + /* + * In the non-RECURSIVE case, query names are visible to the WITH + * items after them and to the main query. + */ + cstate->innerwiths = lcons(NIL, cstate->innerwiths); + foreach(lc, withClause->ctes) + { + CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); + ListCell *cell1; + + (void) makeDependencyGraphWalker(cte->ctequery, cstate); + /* note that recursion could mutate innerwiths list */ + cell1 = list_head(cstate->innerwiths); + lfirst(cell1) = lappend((List *) lfirst(cell1), cte); + } + (void) raw_expression_tree_walker(stmt, + makeDependencyGraphWalker, + (void *) cstate); + cstate->innerwiths = list_delete_first(cstate->innerwiths); + } +} + /* * Sort by dependencies, using a standard topological sort operation */ diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 460d0ea2b1c47..0a8b48d5e93b0 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -2084,6 +2084,14 @@ WITH RECURSIVE x(n) AS ( ERROR: ORDER BY in a recursive query is not implemented LINE 3: ORDER BY (SELECT n FROM x)) ^ +-- and this +WITH RECURSIVE x(n) AS ( + WITH sub_cte AS (SELECT * FROM x) + DELETE FROM graph RETURNING f) + SELECT * FROM x; +ERROR: recursive query "x" must not contain data-modifying statements +LINE 1: WITH RECURSIVE x(n) AS ( + ^ CREATE TEMPORARY TABLE y (a INTEGER); INSERT INTO y SELECT generate_series(1, 10); -- LEFT JOIN diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index bcf0242fa4064..6d55c7731a9f4 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -949,6 +949,13 @@ WITH RECURSIVE x(n) AS ( ORDER BY (SELECT n FROM x)) SELECT * FROM x; +-- and this +WITH RECURSIVE x(n) AS ( + WITH sub_cte AS (SELECT * FROM x) + DELETE FROM graph RETURNING f) + SELECT * FROM x; + + CREATE TEMPORARY TABLE y (a INTEGER); INSERT INTO y SELECT generate_series(1, 10); From 80745146d6fc351c446f34fe8245b5948e0f6d30 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Sun, 6 Apr 2025 09:13:43 -0700 Subject: [PATCH 300/796] Fix unintentional 'NULL' string literal in pg_upgrade. Introduced in 2a083ab807. Note: backport of commit 945126234b, which was missed at the time. Discussion: https://postgr.es/m/e852442da35b4f31acc600ed98bbee0f12e65e0c.camel@j-davis.com Reviewed-by: Michael Paquier Backpatch-through: 16 --- src/bin/pg_upgrade/pg_upgrade.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index 4fcd4bac153c0..bb5a223e1c6a1 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -408,7 +408,6 @@ set_locale_and_encoding(void) char *datcollate_literal; char *datctype_literal; char *datlocale_literal = NULL; - char *datlocale_src; DbLocaleInfo *locale = old_cluster.template0; prep_status("Setting locale and encoding for new cluster"); @@ -422,10 +421,13 @@ set_locale_and_encoding(void) datctype_literal = PQescapeLiteral(conn_new_template1, locale->db_ctype, strlen(locale->db_ctype)); - datlocale_src = locale->db_locale ? locale->db_locale : "NULL"; - datlocale_literal = PQescapeLiteral(conn_new_template1, - datlocale_src, - strlen(datlocale_src)); + + if (locale->db_locale) + datlocale_literal = PQescapeLiteral(conn_new_template1, + locale->db_locale, + strlen(locale->db_locale)); + else + datlocale_literal = "NULL"; /* update template0 in new cluster */ if (GET_MAJOR_VERSION(new_cluster.major_version) >= 1700) @@ -469,7 +471,8 @@ set_locale_and_encoding(void) PQfreemem(datcollate_literal); PQfreemem(datctype_literal); - PQfreemem(datlocale_literal); + if (locale->db_locale) + PQfreemem(datlocale_literal); PQfinish(conn_new_template1); From d1f11bc66c05270039d8af6d4423f7af61a8f1df Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Mon, 7 Apr 2025 00:03:18 +0200 Subject: [PATCH 301/796] doc: Clarify project naming Clarify the project naming in the history section of the docs to match the recent license preamble changes. Backpatch to all supported versions. Author: Dave Page Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CA+OCxozLzK2+Jc14XZyWXSp6L9Ot+3efwXUE35FJG=fsbib2EA@mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/history.sgml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/history.sgml b/doc/src/sgml/history.sgml index 3bf60fc563b09..070cfb8c94bec 100644 --- a/doc/src/sgml/history.sgml +++ b/doc/src/sgml/history.sgml @@ -197,11 +197,10 @@ - Many people continue to refer to - PostgreSQL as Postgres - (now rarely in all capital letters) because of tradition or because - it is easier to pronounce. This usage is widely accepted as a - nickname or alias. + Postgres is still considered an official + project name, both because of tradition and because people find it + easier to pronounce Postgres than + PostgreSQL. From ce6faf5e94be59f6abe2231e4c32eed4d6612dbc Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 8 Apr 2025 07:58:47 +0900 Subject: [PATCH 302/796] Flush the IO statistics of active WAL senders more frequently WAL senders do not flush their statistics until they exit, limiting the monitoring possible for live processes. This is penalizing when WAL senders are running for a long time, like in streaming or logical replication setups, because it is not possible to know the amount of IO they generate while running. This commit makes WAL senders more aggressive with their statistics flush, using an internal of 1 second, with the flush timing calculated based on the existing GetCurrentTimestamp() done before the sleeps done to wait for some activity. Note that the sleep done for logical and physical WAL senders happens in two different code paths, so the stats flushes need to happen in these two places. One test is added for the physical WAL sender case, and one for the logical WAL sender case. This can be done in a stable fashion by relying on the WAL generated by the TAP tests in combination with a stats reset while a server is running, but only on HEAD as WAL data has been added to pg_stat_io in a051e71e28a1. This issue exists since a9c70b46dbe and the introduction of pg_stat_io, so backpatch down to v16. Author: Bertrand Drouvot Reviewed-by: vignesh C Reviewed-by: Xuneng Zhou Discussion: https://postgr.es/m/Z73IsKBceoVd4t55@ip-10-97-1-34.eu-west-3.compute.internal Backpatch-through: 16 --- src/backend/replication/walsender.c | 34 +++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index c3181e3295ea3..45adf1936e8a5 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -90,10 +90,14 @@ #include "utils/guc.h" #include "utils/memutils.h" #include "utils/pg_lsn.h" +#include "utils/pgstat_internal.h" #include "utils/ps_status.h" #include "utils/timeout.h" #include "utils/timestamp.h" +/* Minimum interval used by walsender for stats flushes, in ms */ +#define WALSENDER_STATS_FLUSH_INTERVAL 1000 + /* * Maximum data payload in a WAL data message. Must be >= XLOG_BLCKSZ. * @@ -1820,6 +1824,7 @@ WalSndWaitForWal(XLogRecPtr loc) int wakeEvents; uint32 wait_event = 0; static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr; + TimestampTz last_flush = 0; /* * Fast path to avoid acquiring the spinlock in case we already know we @@ -1840,6 +1845,7 @@ WalSndWaitForWal(XLogRecPtr loc) { bool wait_for_standby_at_stop = false; long sleeptime; + TimestampTz now; /* Clear any already-pending wakeups */ ResetLatch(MyLatch); @@ -1950,7 +1956,8 @@ WalSndWaitForWal(XLogRecPtr loc) * new WAL to be generated. (But if we have nothing to send, we don't * want to wake on socket-writable.) */ - sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); + now = GetCurrentTimestamp(); + sleeptime = WalSndComputeSleeptime(now); wakeEvents = WL_SOCKET_READABLE; @@ -1959,6 +1966,14 @@ WalSndWaitForWal(XLogRecPtr loc) Assert(wait_event != 0); + /* Report IO statistics, if needed */ + if (TimestampDifferenceExceeds(last_flush, now, + WALSENDER_STATS_FLUSH_INTERVAL)) + { + pgstat_flush_io(false); + last_flush = now; + } + WalSndWait(wakeEvents, sleeptime, wait_event); } @@ -2766,6 +2781,8 @@ WalSndCheckTimeOut(void) static void WalSndLoop(WalSndSendDataCallback send_data) { + TimestampTz last_flush = 0; + /* * Initialize the last reply timestamp. That enables timeout processing * from hereon. @@ -2860,6 +2877,9 @@ WalSndLoop(WalSndSendDataCallback send_data) * WalSndWaitForWal() handle any other blocking; idle receivers need * its additional actions. For physical replication, also block if * caught up; its send_data does not block. + * + * The IO statistics are reported in WalSndWaitForWal() for the + * logical WAL senders. */ if ((WalSndCaughtUp && send_data != XLogSendLogical && !streamingDoneSending) || @@ -2867,6 +2887,7 @@ WalSndLoop(WalSndSendDataCallback send_data) { long sleeptime; int wakeEvents; + TimestampTz now; if (!streamingDoneReceiving) wakeEvents = WL_SOCKET_READABLE; @@ -2877,11 +2898,20 @@ WalSndLoop(WalSndSendDataCallback send_data) * Use fresh timestamp, not last_processing, to reduce the chance * of reaching wal_sender_timeout before sending a keepalive. */ - sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); + now = GetCurrentTimestamp(); + sleeptime = WalSndComputeSleeptime(now); if (pq_is_send_pending()) wakeEvents |= WL_SOCKET_WRITEABLE; + /* Report IO statistics, if needed */ + if (TimestampDifferenceExceeds(last_flush, now, + WALSENDER_STATS_FLUSH_INTERVAL)) + { + pgstat_flush_io(false); + last_flush = now; + } + /* Sleep until something happens or we time out */ WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN); } From 87f5da0dc1cde2c8c283f5a2e120309ad2a98121 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Mon, 7 Apr 2025 21:33:41 -0400 Subject: [PATCH 303/796] Fix PG 17 [NOT] NULL optimization bug for domains A PG 17 optimization allowed columns with NOT NULL constraints to skip table scans for IS NULL queries, and to skip IS NOT NULL checks for IS NOT NULL queries. This didn't work for domain types, since domain types don't follow the IS NULL/IS NOT NULL constraint logic. To fix, disable this optimization for domains for PG 17+. Reported-by: Jan Behrens Diagnosed-by: Tom Lane Discussion: https://postgr.es/m/Z37p0paENWWUarj-@momjian.us Backpatch-through: 17 --- doc/src/sgml/ref/create_domain.sgml | 3 ++- src/backend/optimizer/plan/initsplan.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/create_domain.sgml b/doc/src/sgml/ref/create_domain.sgml index ce55520348620..c111285a69c6d 100644 --- a/doc/src/sgml/ref/create_domain.sgml +++ b/doc/src/sgml/ref/create_domain.sgml @@ -283,7 +283,8 @@ CREATE TABLE us_snail_addy ( The syntax NOT NULL in this command is a PostgreSQL extension. (A standard-conforming - way to write the same would be CHECK (VALUE IS NOT + way to write the same for non-composite data types would be + CHECK (VALUE IS NOT NULL). However, per , such constraints are best avoided in practice anyway.) The NULL constraint is a diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 3b21f8ae5968d..0d748973a4eb2 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -2758,6 +2758,13 @@ restriction_is_always_true(PlannerInfo *root, if (nulltest->nulltesttype != IS_NOT_NULL) return false; + /* + * Empty rows can appear NULL in some contexts and NOT NULL in others, + * so avoid this optimization for row expressions. + */ + if (nulltest->argisrow) + return false; + return expr_is_nonnullable(root, nulltest->arg); } @@ -2816,6 +2823,13 @@ restriction_is_always_false(PlannerInfo *root, if (nulltest->nulltesttype != IS_NULL) return false; + /* + * Empty rows can appear NULL in some contexts and NOT NULL in others, + * so avoid this optimization for row expressions. + */ + if (nulltest->argisrow) + return false; + return expr_is_nonnullable(root, nulltest->arg); } From f86be01bf9a721f77d0fdf91107599b748920abd Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Tue, 8 Apr 2025 09:23:07 +0530 Subject: [PATCH 304/796] Stabilize 035_standby_logical_decoding.pl. Some tests try to invalidate logical slots on the standby server by running VACUUM on the primary. The problem is that xl_running_xacts was getting generated and replayed before the VACUUM command, leading to the advancement of the active slot's catalog_xmin. Due to this, active slots were not getting invalidated, leading to test failures. We fix it by skipping the generation of xl_running_xacts for the required tests with the help of injection points. As the required interface for injection points was not present in back branches, we fixed the failing tests in them by disallowing the slot to become active for the required cases (where rows_removed conflict could be generated). Author: Hayato Kuroda Reviewed-by: Bertrand Drouvot Reviewed-by: Amit Kapila Backpatch-through: 16, where it was introduced Discussion: https://postgr.es/m/Z6oQXc8LmiTLfwLA@ip-10-97-1-34.eu-west-3.compute.internal --- .../t/035_standby_logical_decoding.pl | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index 4eca17885d6ea..58c4402e80e6c 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -205,9 +205,6 @@ sub reactive_slots_change_hfs_and_wait_for_xmins change_hot_standby_feedback_and_wait_for_xmins($hsf, $invalidated); - $handle = - make_slot_active($node_standby, $slot_prefix, 1, \$stdout, \$stderr); - # reset stat: easier to check for confl_active_logicalslot in pg_stat_database_conflicts $node_standby->psql('testdb', q[select pg_stat_reset();]); } @@ -215,7 +212,7 @@ sub reactive_slots_change_hfs_and_wait_for_xmins # Check invalidation in the logfile and in pg_stat_database_conflicts sub check_for_invalidation { - my ($slot_prefix, $log_start, $test_name) = @_; + my ($slot_prefix, $log_start, $test_name, $checks_active_slot) = @_; my $active_slot = $slot_prefix . 'activeslot'; my $inactive_slot = $slot_prefix . 'inactiveslot'; @@ -231,13 +228,17 @@ sub check_for_invalidation $log_start), "activeslot slot invalidation is logged $test_name"); - # Verify that pg_stat_database_conflicts.confl_active_logicalslot has been updated - ok( $node_standby->poll_query_until( - 'postgres', - "select (confl_active_logicalslot = 1) from pg_stat_database_conflicts where datname = 'testdb'", - 't'), - 'confl_active_logicalslot updated' - ) or die "Timed out waiting confl_active_logicalslot to be updated"; + if ($checks_active_slot) + { + # Verify that pg_stat_database_conflicts.confl_active_logicalslot has + # been updated + ok( $node_standby->poll_query_until( + 'postgres', + "select (confl_active_logicalslot = 1) from pg_stat_database_conflicts where datname = 'testdb'", + 't'), + 'confl_active_logicalslot updated' + ) or die "Timed out waiting confl_active_logicalslot to be updated"; + } } # Launch $sql query, wait for a new snapshot that has a newer horizon and @@ -250,7 +251,11 @@ sub check_for_invalidation # seeing a xl_running_xacts that would advance an active replication slot's # catalog_xmin. Advancing the active replication slot's catalog_xmin # would break some tests that expect the active slot to conflict with -# the catalog xmin horizon. +# the catalog xmin horizon. Even with the above precaution, there is a risk +# of xl_running_xacts record being logged and replayed before the VACUUM +# command, leading to the test failure. So, we ensured that replication slots +# are not activated for tests that can invalidate slots due to 'rows_removed' +# conflict reason. sub wait_until_vacuum_can_remove { my ($vac_option, $sql, $to_vac) = @_; @@ -532,11 +537,8 @@ sub wait_until_vacuum_can_remove $node_subscriber->stop; ################################################## -# Recovery conflict: Invalidate conflicting slots, including in-use slots +# Recovery conflict: Invalidate conflicting slots # Scenario 1: hot_standby_feedback off and vacuum FULL -# -# In passing, ensure that replication slot stats are not removed when the -# active slot is invalidated. ################################################## # One way to produce recovery conflict is to create/drop a relation and @@ -550,10 +552,6 @@ sub wait_until_vacuum_can_remove $node_primary->safe_psql('testdb', qq[INSERT INTO decoding_test(x,y) SELECT 100,'100';]); -$node_standby->poll_query_until('testdb', - qq[SELECT total_txns > 0 FROM pg_stat_replication_slots WHERE slot_name = 'vacuum_full_activeslot'] -) or die "replication slot stats of vacuum_full_activeslot not updated"; - # This should trigger the conflict wait_until_vacuum_can_remove( 'full', 'CREATE TABLE conflict_test(x integer, y text); @@ -562,19 +560,11 @@ sub wait_until_vacuum_can_remove $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('vacuum_full_', 1, 'with vacuum FULL on pg_class'); +check_for_invalidation('vacuum_full_', 1, 'with vacuum FULL on pg_class', 0); # Verify reason for conflict is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('vacuum_full_', 'rows_removed'); -# Ensure that replication slot stats are not removed after invalidation. -is( $node_standby->safe_psql( - 'testdb', - qq[SELECT total_txns > 0 FROM pg_stat_replication_slots WHERE slot_name = 'vacuum_full_activeslot'] - ), - 't', - 'replication slot stats not removed after invalidation'); - $handle = make_slot_active($node_standby, 'vacuum_full_', 0, \$stdout, \$stderr); @@ -639,7 +629,7 @@ sub wait_until_vacuum_can_remove "invalidated logical slots do not lead to retaining WAL"); ################################################## -# Recovery conflict: Invalidate conflicting slots, including in-use slots +# Recovery conflict: Invalidate conflicting slots # Scenario 2: conflict due to row removal with hot_standby_feedback off. ################################################## @@ -660,7 +650,7 @@ sub wait_until_vacuum_can_remove $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('row_removal_', $logstart, 'with vacuum on pg_class'); +check_for_invalidation('row_removal_', $logstart, 'with vacuum on pg_class', 0); # Verify reason for conflict is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('row_removal_', 'rows_removed'); @@ -696,7 +686,7 @@ sub wait_until_vacuum_can_remove # Check invalidation in the logfile and in pg_stat_database_conflicts check_for_invalidation('shared_row_removal_', $logstart, - 'with vacuum on pg_authid'); + 'with vacuum on pg_authid', 0); # Verify reason for conflict is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('shared_row_removal_', 'rows_removed'); @@ -720,6 +710,10 @@ sub wait_until_vacuum_can_remove reactive_slots_change_hfs_and_wait_for_xmins('shared_row_removal_', 'no_conflict_', 0, 1); +# As this scenario is not expected to produce any conflict, so activate the slot. +# See comments atop wait_until_vacuum_can_remove(). +make_slot_active($node_standby, 'no_conflict_', 1, \$stdout, \$stderr); + # This should not trigger a conflict wait_until_vacuum_can_remove( '', 'CREATE TABLE conflict_test(x integer, y text); @@ -763,7 +757,7 @@ sub wait_until_vacuum_can_remove $node_standby->restart; ################################################## -# Recovery conflict: Invalidate conflicting slots, including in-use slots +# Recovery conflict: Invalidate conflicting slots # Scenario 5: conflict due to on-access pruning. ################################################## @@ -788,7 +782,7 @@ sub wait_until_vacuum_can_remove $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('pruning_', $logstart, 'with on-access pruning'); +check_for_invalidation('pruning_', $logstart, 'with on-access pruning', 0); # Verify reason for conflict is 'rows_removed' in pg_replication_slots check_slots_conflict_reason('pruning_', 'rows_removed'); @@ -832,7 +826,7 @@ sub wait_until_vacuum_can_remove $node_primary->wait_for_replay_catchup($node_standby); # Check invalidation in the logfile and in pg_stat_database_conflicts -check_for_invalidation('wal_level_', $logstart, 'due to wal_level'); +check_for_invalidation('wal_level_', $logstart, 'due to wal_level', 1); # Verify reason for conflict is 'wal_level_insufficient' in pg_replication_slots check_slots_conflict_reason('wal_level_', 'wal_level_insufficient'); From 724a8960a4609573bca49548a99800ca7e2f78e4 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Wed, 9 Apr 2025 07:23:39 -0700 Subject: [PATCH 305/796] Fix test races between syscache-update-pruned.spec and autovacuum. This spec fails ~3% of my Valgrind runs, and the spec has failed on Valgrind buildfarm member skink at a similar rate. Two problems contributed to that: - A competing buffer pin triggered VACUUM's lazy_scan_noprune() path, causing "tuples missed: 1 dead from 1 pages not removed due to cleanup lock contention". FREEZE fixes that. - The spec ran lazy VACUUM immediately after VACUUM FULL. The spec implicitly assumed lazy VACUUM prunes the one tuple that VACUUM FULL made dead. First wait for old snapshots, making that assumption reliable. This also adds two forms of defense in depth: - Wait for snapshots using shared catalog pruning rules (VISHORIZON_SHARED). This avoids the removable cutoff moving backward when an XID-bearing autoanalyze process runs in another database. That may never happen in this test, but it's cheap insurance. - Use lazy VACUUM option DISABLE_PAGE_SKIPPING. Commit c2dc1a79767a0f947e1145f82eb65dfe4360d25f did this for a related requirement in other tests, but I suspect FREEZE is necessary and sufficient in all these tests. Back-patch to v17, where the test first appeared. Reported-by: Andres Freund Discussion: https://postgr.es/m/sv3taq4e6ea4qckimien3nxp3sz4b6cw6sfcy4nhwl52zpur4g@h6i6tohxmizu Backpatch-through: 17 --- .../expected/syscache-update-pruned.out | 6 ++-- .../expected/syscache-update-pruned_1.out | 6 ++-- .../injection_points/regress_injection.c | 17 +++++++---- .../specs/syscache-update-pruned.spec | 28 +++++++++++++++---- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/test/modules/injection_points/expected/syscache-update-pruned.out b/src/test/modules/injection_points/expected/syscache-update-pruned.out index 5dc5a1ddc5e4b..9a9683bb49625 100644 --- a/src/test/modules/injection_points/expected/syscache-update-pruned.out +++ b/src/test/modules/injection_points/expected/syscache-update-pruned.out @@ -7,7 +7,7 @@ step at2: FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); @@ -30,7 +30,7 @@ step at2: FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); @@ -61,7 +61,7 @@ step mkrels4: step r3: ROLLBACK; step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); diff --git a/src/test/modules/injection_points/expected/syscache-update-pruned_1.out b/src/test/modules/injection_points/expected/syscache-update-pruned_1.out index b18857c902eb7..64c39d708bd5f 100644 --- a/src/test/modules/injection_points/expected/syscache-update-pruned_1.out +++ b/src/test/modules/injection_points/expected/syscache-update-pruned_1.out @@ -7,7 +7,7 @@ step at2: FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); @@ -29,7 +29,7 @@ step at2: FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); @@ -59,7 +59,7 @@ step mkrels4: step r3: ROLLBACK; step waitprunable4: CALL vactest.wait_prunable(); -step vac4: VACUUM pg_class; +step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); diff --git a/src/test/modules/injection_points/regress_injection.c b/src/test/modules/injection_points/regress_injection.c index 422f4168935fb..7bba1c97d0f26 100644 --- a/src/test/modules/injection_points/regress_injection.c +++ b/src/test/modules/injection_points/regress_injection.c @@ -17,7 +17,9 @@ #include "access/table.h" #include "fmgr.h" #include "miscadmin.h" +#include "postmaster/autovacuum.h" #include "storage/procarray.h" +#include "utils/rel.h" #include "utils/xid8.h" /* @@ -28,11 +30,12 @@ * that. For the causes of backward movement, see * postgr.es/m/CAEze2Wj%2BV0kTx86xB_YbyaqTr5hnE_igdWAwuhSyjXBYscf5-Q%40mail.gmail.com * and the header comment for ComputeXidHorizons(). One can assume this - * doesn't move backward if one arranges for concurrent activity not to reach - * AbortTransaction() and not to allocate an XID while connected to another - * database. Non-runningcheck tests can control most concurrent activity, - * except autovacuum and the isolationtester control connection. Neither - * allocates XIDs, and AbortTransaction() in those would justify test failure. + * doesn't move backward if one (a) passes a shared catalog as the argument + * and (b) arranges for concurrent activity not to reach AbortTransaction(). + * Non-runningcheck tests can control most concurrent activity, except + * autovacuum and the isolationtester control connection. AbortTransaction() + * in those would justify test failure. Seeing autoanalyze can allocate an + * XID in any database, (a) ensures we'll consistently not ignore those XIDs. */ PG_FUNCTION_INFO_V1(removable_cutoff); Datum @@ -47,6 +50,10 @@ removable_cutoff(PG_FUNCTION_ARGS) if (!PG_ARGISNULL(0)) rel = table_open(PG_GETARG_OID(0), AccessShareLock); + if (!rel->rd_rel->relisshared && autovacuum_start_daemon) + elog(WARNING, + "removable_cutoff(non-shared-rel) can move backward under autovacuum=on"); + /* * No lock or snapshot necessarily prevents oldestXid from advancing past * "xid" while this function runs. That concerns us only in that we must diff --git a/src/test/modules/injection_points/specs/syscache-update-pruned.spec b/src/test/modules/injection_points/specs/syscache-update-pruned.spec index b48e897431e39..086d084de2577 100644 --- a/src/test/modules/injection_points/specs/syscache-update-pruned.spec +++ b/src/test/modules/injection_points/specs/syscache-update-pruned.spec @@ -53,10 +53,12 @@ setup barrier := pg_current_xact_id(); -- autovacuum worker RelationCacheInitializePhase3() or the -- isolationtester control connection might hold a snapshot that - -- limits pruning. Sleep until that clears. + -- limits pruning. Sleep until that clears. See comments at + -- removable_cutoff() for why we pass a shared catalog rather than + -- pg_class, the table we'll prune. LOOP ROLLBACK; -- release MyProc->xmin, which could be the oldest - cutoff := removable_cutoff('pg_class'); + cutoff := removable_cutoff('pg_database'); EXIT WHEN cutoff >= barrier; RAISE LOG 'removable cutoff %; waiting for %', cutoff, barrier; PERFORM pg_sleep(.1); @@ -64,9 +66,24 @@ setup END $$; } -setup { CALL vactest.wait_prunable(); -- maximize next two VACUUMs } +# Eliminate HEAPTUPLE_DEAD and HEAPTUPLE_RECENTLY_DEAD from pg_class. +# Minimize free space. +# +# If we kept HEAPTUPLE_RECENTLY_DEAD, step vac4 could prune what we missed, +# breaking some permutation assumptions. Specifically, the next pg_class +# tuple could end up in free space we failed to liberate here, instead of +# going in the specific free space vac4 intended to liberate for it. +setup { CALL vactest.wait_prunable(); -- maximize VACUUM FULL } setup { VACUUM FULL pg_class; -- reduce free space } -setup { VACUUM FREEZE pg_class; -- populate fsm etc. } +# Remove the one tuple that VACUUM FULL makes dead, a tuple pertaining to +# pg_class itself. Populate the FSM for pg_class. +# +# wait_prunable waits for snapshots that would thwart pruning, while FREEZE +# waits for buffer pins that would thwart pruning. DISABLE_PAGE_SKIPPING +# isn't actually needed, but other pruning-dependent tests use it. If those +# tests remove it, remove it here. +setup { CALL vactest.wait_prunable(); -- maximize lazy VACUUM } +setup { VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; -- fill fsm etc. } setup { SELECT FROM vactest.mkrels('orig', 1, 49); @@ -115,7 +132,8 @@ step r3 { ROLLBACK; } # Non-blocking actions. session s4 step waitprunable4 { CALL vactest.wait_prunable(); } -step vac4 { VACUUM pg_class; } +# Eliminate HEAPTUPLE_DEAD. See above discussion of FREEZE. +step vac4 { VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; } # Reuse the lp that s1 is waiting to change. I've observed reuse at the 1st # or 18th CREATE, so create excess. step mkrels4 { From 632cbc9420eb9dc52e4c47561e0003f06d5ba4cd Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 10 Apr 2025 12:57:10 +0530 Subject: [PATCH 306/796] Fix data loss in logical replication. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Data loss can happen when the DDLs like ALTER PUBLICATION ... ADD TABLE ... or ALTER TYPE ... that don't take a strong lock on table happens concurrently to DMLs on the tables involved in the DDL. This happens because logical decoding doesn't distribute invalidations to concurrent transactions and those transactions use stale cache data to decode the changes. The problem becomes bigger because we keep using the stale cache even after those in-progress transactions are finished and skip the changes required to be sent to the client. This commit fixes the issue by distributing invalidation messages from catalog-modifying transactions to all concurrent in-progress transactions. This allows the necessary rebuild of the catalog cache when decoding new changes after concurrent DDL. We observed performance regression primarily during frequent execution of *publication DDL* statements that modify the published tables. The regression is minor or nearly nonexistent for DDLs that do not affect the published tables or occur infrequently, making this a worthwhile cost to resolve a longstanding data loss issue. An alternative approach considered was to take a strong lock on each affected table during publication modification. However, this would only address issues related to publication DDLs (but not the ALTER TYPE ...) and require locking every relation in the database for publications created as FOR ALL TABLES, which is impractical. The bug exists in all supported branches, but we are backpatching till 14. The fix for 13 requires somewhat bigger changes than this fix, so the fix for that branch is still under discussion. Reported-by: hubert depesz lubaczewski Reported-by: Tomas Vondra Author: Shlok Kyal Author: Hayato Kuroda Reviewed-by: Zhijie Hou Reviewed-by: Masahiko Sawada Reviewed-by: Amit Kapila Tested-by: Benoit Lobréau Backpatch-through: 14 Discussion: https://postgr.es/m/de52b282-1166-1180-45a2-8d8917ca74c6@enterprisedb.com Discussion: https://postgr.es/m/CAD21AoAenVqiMjpN-PvGHL1N9DWnHSq673bfgr6phmBUzx=kLQ@mail.gmail.com --- contrib/test_decoding/Makefile | 2 +- .../expected/invalidation_distrubution.out | 20 ++++++ contrib/test_decoding/meson.build | 1 + .../specs/invalidation_distrubution.spec | 32 +++++++++ .../replication/logical/reorderbuffer.c | 23 +++++++ src/backend/replication/logical/snapbuild.c | 67 +++++++++++++++---- src/include/replication/reorderbuffer.h | 4 ++ 7 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 contrib/test_decoding/expected/invalidation_distrubution.out create mode 100644 contrib/test_decoding/specs/invalidation_distrubution.spec diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile index a4ba1a509aec2..eef707706746e 100644 --- a/contrib/test_decoding/Makefile +++ b/contrib/test_decoding/Makefile @@ -9,7 +9,7 @@ REGRESS = ddl xact rewrite toast permissions decoding_in_xact \ ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \ oldest_xmin snapshot_transfer subxact_without_top concurrent_stream \ twophase_snapshot slot_creation_error catalog_change_snapshot \ - skip_snapshot_restore + skip_snapshot_restore invalidation_distrubution REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf diff --git a/contrib/test_decoding/expected/invalidation_distrubution.out b/contrib/test_decoding/expected/invalidation_distrubution.out new file mode 100644 index 0000000000000..ad0a944cbf303 --- /dev/null +++ b/contrib/test_decoding/expected/invalidation_distrubution.out @@ -0,0 +1,20 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_insert_tbl1 s1_begin s1_insert_tbl1 s2_alter_pub_add_tbl s1_commit s1_insert_tbl1 s2_get_binary_changes +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_pub_add_tbl: ALTER PUBLICATION pub ADD TABLE tbl1; +step s1_commit: COMMIT; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_get_binary_changes: SELECT count(data) FROM pg_logical_slot_get_binary_changes('isolation_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub') WHERE get_byte(data, 0) = 73; +count +----- + 1 +(1 row) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build index f643dc81a2c84..b31c433681d6f 100644 --- a/contrib/test_decoding/meson.build +++ b/contrib/test_decoding/meson.build @@ -63,6 +63,7 @@ tests += { 'twophase_snapshot', 'slot_creation_error', 'skip_snapshot_restore', + 'invalidation_distrubution', ], 'regress_args': [ '--temp-config', files('logical.conf'), diff --git a/contrib/test_decoding/specs/invalidation_distrubution.spec b/contrib/test_decoding/specs/invalidation_distrubution.spec new file mode 100644 index 0000000000000..decbed627e327 --- /dev/null +++ b/contrib/test_decoding/specs/invalidation_distrubution.spec @@ -0,0 +1,32 @@ +# Test that catalog cache invalidation messages are distributed to ongoing +# transactions, ensuring they can access the updated catalog content after +# processing these messages. +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'pgoutput'); + CREATE TABLE tbl1(val1 integer, val2 integer); + CREATE PUBLICATION pub; +} + +teardown +{ + DROP TABLE tbl1; + DROP PUBLICATION pub; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s1" +setup { SET synchronous_commit=on; } + +step "s1_begin" { BEGIN; } +step "s1_insert_tbl1" { INSERT INTO tbl1 (val1, val2) VALUES (1, 1); } +step "s1_commit" { COMMIT; } + +session "s2" +setup { SET synchronous_commit=on; } + +step "s2_alter_pub_add_tbl" { ALTER PUBLICATION pub ADD TABLE tbl1; } +step "s2_get_binary_changes" { SELECT count(data) FROM pg_logical_slot_get_binary_changes('isolation_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub') WHERE get_byte(data, 0) = 73; } + +# Expect to get one insert change. LOGICAL_REP_MSG_INSERT = 'I' +permutation "s1_insert_tbl1" "s1_begin" "s1_insert_tbl1" "s2_alter_pub_add_tbl" "s1_commit" "s1_insert_tbl1" "s2_get_binary_changes" diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 9c742e96eb33a..03eb005c39d75 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -5337,3 +5337,26 @@ ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data, *cmax = ent->cmax; return true; } + +/* + * Count invalidation messages of specified transaction. + * + * Returns number of messages, and msgs is set to the pointer of the linked + * list for the messages. + */ +uint32 +ReorderBufferGetInvalidations(ReorderBuffer *rb, TransactionId xid, + SharedInvalidationMessage **msgs) +{ + ReorderBufferTXN *txn; + + txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, + false); + + if (txn == NULL) + return 0; + + *msgs = txn->invalidations; + + return txn->ninvalidations; +} diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index ae676145e602e..110e0b0a0449e 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -300,7 +300,7 @@ static void SnapBuildFreeSnapshot(Snapshot snap); static void SnapBuildSnapIncRefcount(Snapshot snap); -static void SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn); +static void SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid); static inline bool SnapBuildXidHasCatalogChanges(SnapBuild *builder, TransactionId xid, uint32 xinfo); @@ -859,15 +859,15 @@ SnapBuildProcessNewCid(SnapBuild *builder, TransactionId xid, } /* - * Add a new Snapshot to all transactions we're decoding that currently are - * in-progress so they can see new catalog contents made by the transaction - * that just committed. This is necessary because those in-progress - * transactions will use the new catalog's contents from here on (at the very - * least everything they do needs to be compatible with newer catalog - * contents). + * Add a new Snapshot and invalidation messages to all transactions we're + * decoding that currently are in-progress so they can see new catalog contents + * made by the transaction that just committed. This is necessary because those + * in-progress transactions will use the new catalog's contents from here on + * (at the very least everything they do needs to be compatible with newer + * catalog contents). */ static void -SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn) +SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid) { dlist_iter txn_i; ReorderBufferTXN *txn; @@ -875,7 +875,8 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn) /* * Iterate through all toplevel transactions. This can include * subtransactions which we just don't yet know to be that, but that's - * fine, they will just get an unnecessary snapshot queued. + * fine, they will just get an unnecessary snapshot and invalidations + * queued. */ dlist_foreach(txn_i, &builder->reorder->toplevel_by_lsn) { @@ -888,6 +889,14 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn) * transaction which in turn implies we don't yet need a snapshot at * all. We'll add a snapshot when the first change gets queued. * + * Similarly, we don't need to add invalidations to a transaction whose + * base snapshot is not yet set. Once a base snapshot is built, it will + * include the xids of committed transactions that have modified the + * catalog, thus reflecting the new catalog contents. The existing + * catalog cache will have already been invalidated after processing + * the invalidations in the transaction that modified catalogs, + * ensuring that a fresh cache is constructed during decoding. + * * NB: This works correctly even for subtransactions because * ReorderBufferAssignChild() takes care to transfer the base snapshot * to the top-level transaction, and while iterating the changequeue @@ -897,13 +906,13 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn) continue; /* - * We don't need to add snapshot to prepared transactions as they - * should not see the new catalog contents. + * We don't need to add snapshot or invalidations to prepared + * transactions as they should not see the new catalog contents. */ if (rbtxn_prepared(txn) || rbtxn_skip_prepared(txn)) continue; - elog(DEBUG2, "adding a new snapshot to %u at %X/%X", + elog(DEBUG2, "adding a new snapshot and invalidations to %u at %X/%X", txn->xid, LSN_FORMAT_ARGS(lsn)); /* @@ -913,6 +922,33 @@ SnapBuildDistributeNewCatalogSnapshot(SnapBuild *builder, XLogRecPtr lsn) SnapBuildSnapIncRefcount(builder->snapshot); ReorderBufferAddSnapshot(builder->reorder, txn->xid, lsn, builder->snapshot); + + /* + * Add invalidation messages to the reorder buffer of in-progress + * transactions except the current committed transaction, for which we + * will execute invalidations at the end. + * + * It is required, otherwise, we will end up using the stale catcache + * contents built by the current transaction even after its decoding, + * which should have been invalidated due to concurrent catalog + * changing transaction. + */ + if (txn->xid != xid) + { + uint32 ninvalidations; + SharedInvalidationMessage *msgs = NULL; + + ninvalidations = ReorderBufferGetInvalidations(builder->reorder, + xid, &msgs); + + if (ninvalidations > 0) + { + Assert(msgs != NULL); + + ReorderBufferAddInvalidations(builder->reorder, txn->xid, lsn, + ninvalidations, msgs); + } + } } } @@ -1184,8 +1220,11 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid, /* refcount of the snapshot builder for the new snapshot */ SnapBuildSnapIncRefcount(builder->snapshot); - /* add a new catalog snapshot to all currently running transactions */ - SnapBuildDistributeNewCatalogSnapshot(builder, lsn); + /* + * Add a new catalog snapshot and invalidations messages to all + * currently running transactions. + */ + SnapBuildDistributeSnapshotAndInval(builder, lsn, xid); } } diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h index 7de50462dcfa1..4c56f219fd8fd 100644 --- a/src/include/replication/reorderbuffer.h +++ b/src/include/replication/reorderbuffer.h @@ -729,6 +729,10 @@ extern TransactionId *ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb); extern void ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr); +extern uint32 ReorderBufferGetInvalidations(ReorderBuffer *rb, + TransactionId xid, + SharedInvalidationMessage **msgs); + extern void StartupReorderBuffer(void); #endif From 15667a8d985d57a5adc006cbf8d182af63281ad1 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 10 Apr 2025 14:49:10 -0400 Subject: [PATCH 307/796] Doc: remove long-obsolete advice about generated constraint names. It's been twenty years since we generated constraint names that look like "$N". So this advice about double-quoting such names is well past its sell-by date, and now it merely seems confusing. Reported-by: Yaroslav Saburov Author: "David G. Johnston" Discussion: https://postgr.es/m/174393459040.678.17810152410419444783@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/ddl.sgml | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index bef1275871198..44578e51a8288 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1681,9 +1681,6 @@ ALTER TABLE products ALTER COLUMN product_no SET NOT NULL; ALTER TABLE products DROP CONSTRAINT some_name; - (If you are dealing with a generated constraint name like $2, - don't forget that you'll need to double-quote it to make it a valid - identifier.) From e2c33253aeb12aae0f37214bd9717a5d00543610 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 11 Apr 2025 10:02:15 +0900 Subject: [PATCH 308/796] Fix race with synchronous_standby_names at startup synchronous_standby_names cannot be reloaded safely by backends, and the checkpointer is in charge of updating a state in shared memory if the GUC is enabled in WalSndCtl, to let the backends know if they should wait or not for a given LSN. This provides a strict control on the timing of the waiting queues if the GUC is enabled or disabled, then reloaded. The checkpointer is also in charge of waking up the backends that could be waiting for a LSN when the GUC is disabled. This logic had a race condition at startup, where it would be possible for backends to not wait for a LSN even if synchronous_standby_names is enabled. This would cause visibility issues with transactions that we should be waiting for but they were not. The problem lasts until the checkpointer does its initial update of the shared memory state when it loads synchronous_standby_names. In order to take care of this problem, the shared memory state in WalSndCtl is extended to detect if it has been initialized by the checkpointer, and not only check if synchronous_standby_names is defined. In WalSndCtlData, sync_standbys_defined is renamed to sync_standbys_status, a bits8 able to know about two states: - If the shared memory state has been initialized. This flag is set by the checkpointer at startup once, and never removed. - If synchronous_standby_names is known as defined in the shared memory state. This is the same as the previous sync_standbys_defined in WalSndCtl. This method gives a way for backends to decide what they should do until the shared memory area is initialized, and they now ultimately fall back to a check on the GUC value in this case, which is the best thing that can be done. Fortunately, SyncRepUpdateSyncStandbysDefined() is called immediately by the checkpointer when this process starts, so the window is very narrow. It is possible to enlarge the problematic window by making the checkpointer wait at the beginning of SyncRepUpdateSyncStandbysDefined() with a hardcoded sleep for example, and doing so has showed that a 2PC visibility test is indeed failing. On machines slow enough, this bug would cause spurious failures. In 17~, we have looked at the possibility of adding an injection point to have a reproducible test, but as the problematic window happens at early startup, we would need to invent a way to make an injection point optionally persistent across restarts when attached, something that would be fine for this case as it would involve the checkpointer. This issue is quite old, and can be reproduced on all the stable branches. Author: Melnikov Maksim Co-authored-by: Michael Paquier Discussion: https://postgr.es/m/163fcbec-900b-4b07-beaa-d2ead8634bec@postgrespro.ru Backpatch-through: 13 --- src/backend/replication/syncrep.c | 97 +++++++++++++++++---- src/backend/replication/walsender.c | 6 +- src/include/replication/walsender_private.h | 23 ++++- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index fa5988c824ea0..e6df5281289ec 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -161,16 +161,23 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) * sync replication standby names defined. * * Since this routine gets called every commit time, it's important to - * exit quickly if sync replication is not requested. So we check - * WalSndCtl->sync_standbys_defined flag without the lock and exit - * immediately if it's false. If it's true, we need to check it again - * later while holding the lock, to check the flag and operate the sync - * rep queue atomically. This is necessary to avoid the race condition - * described in SyncRepUpdateSyncStandbysDefined(). On the other hand, if - * it's false, the lock is not necessary because we don't touch the queue. + * exit quickly if sync replication is not requested. + * + * We check WalSndCtl->sync_standbys_status flag without the lock and exit + * immediately if SYNC_STANDBY_INIT is set (the checkpointer has + * initialized this data) but SYNC_STANDBY_DEFINED is missing (no sync + * replication requested). + * + * If SYNC_STANDBY_DEFINED is set, we need to check the status again later + * while holding the lock, to check the flag and operate the sync rep + * queue atomically. This is necessary to avoid the race condition + * described in SyncRepUpdateSyncStandbysDefined(). On the other hand, if + * SYNC_STANDBY_DEFINED is not set, the lock is not necessary because we + * don't touch the queue. */ if (!SyncRepRequested() || - !((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_defined) + ((((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_status) & + (SYNC_STANDBY_INIT | SYNC_STANDBY_DEFINED)) == SYNC_STANDBY_INIT) return; /* Cap the level for anything other than commit to remote flush only. */ @@ -186,16 +193,52 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) Assert(MyProc->syncRepState == SYNC_REP_NOT_WAITING); /* - * We don't wait for sync rep if WalSndCtl->sync_standbys_defined is not - * set. See SyncRepUpdateSyncStandbysDefined. + * We don't wait for sync rep if SYNC_STANDBY_DEFINED is not set. See + * SyncRepUpdateSyncStandbysDefined(). * * Also check that the standby hasn't already replied. Unlikely race * condition but we'll be fetching that cache line anyway so it's likely * to be a low cost check. + * + * If the sync standby data has not been initialized yet + * (SYNC_STANDBY_INIT is not set), fall back to a check based on the LSN, + * then do a direct GUC check. */ - if (!WalSndCtl->sync_standbys_defined || - lsn <= WalSndCtl->lsn[mode]) + if (WalSndCtl->sync_standbys_status & SYNC_STANDBY_INIT) + { + if ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) == 0 || + lsn <= WalSndCtl->lsn[mode]) + { + LWLockRelease(SyncRepLock); + return; + } + } + else if (lsn <= WalSndCtl->lsn[mode]) { + /* + * The LSN is older than what we need to wait for. The sync standby + * data has not been initialized yet, but we are OK to not wait + * because we know that there is no point in doing so based on the + * LSN. + */ + LWLockRelease(SyncRepLock); + return; + } + else if (!SyncStandbysDefined()) + { + /* + * If we are here, the sync standby data has not been initialized yet, + * and the LSN is newer than what need to wait for, so we have fallen + * back to the best thing we could do in this case: a check on + * SyncStandbysDefined() to see if the GUC is set or not. + * + * When the GUC has a value, we wait until the checkpointer updates + * the status data because we cannot be sure yet if we should wait or + * not. Here, the GUC has *no* value, we are sure that there is no + * point to wait; this matters for example when initializing a + * cluster, where we should never wait, and no sync standbys is the + * default behavior. + */ LWLockRelease(SyncRepLock); return; } @@ -912,7 +955,7 @@ SyncRepWakeQueue(bool all, int mode) /* * The checkpointer calls this as needed to update the shared - * sync_standbys_defined flag, so that backends don't remain permanently wedged + * sync_standbys_status flag, so that backends don't remain permanently wedged * if synchronous_standby_names is unset. It's safe to check the current value * without the lock, because it's only ever updated by one process. But we * must take the lock to change it. @@ -922,7 +965,8 @@ SyncRepUpdateSyncStandbysDefined(void) { bool sync_standbys_defined = SyncStandbysDefined(); - if (sync_standbys_defined != WalSndCtl->sync_standbys_defined) + if (sync_standbys_defined != + ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_DEFINED) != 0)) { LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); @@ -946,7 +990,30 @@ SyncRepUpdateSyncStandbysDefined(void) * backend that hasn't yet reloaded its config might go to sleep on * the queue (and never wake up). This prevents that. */ - WalSndCtl->sync_standbys_defined = sync_standbys_defined; + WalSndCtl->sync_standbys_status = SYNC_STANDBY_INIT | + (sync_standbys_defined ? SYNC_STANDBY_DEFINED : 0); + + LWLockRelease(SyncRepLock); + } + else if ((WalSndCtl->sync_standbys_status & SYNC_STANDBY_INIT) == 0) + { + LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); + + /* + * Note that there is no need to wake up the queues here. We would + * reach this path only if SyncStandbysDefined() returns false, or it + * would mean that some backends are waiting with the GUC set. See + * SyncRepWaitForLSN(). + */ + Assert(!SyncStandbysDefined()); + + /* + * Even if there is no sync standby defined, let the readers of this + * information know that the sync standby data has been initialized. + * This can just be done once, hence the previous check on + * SYNC_STANDBY_INIT to avoid useless work. + */ + WalSndCtl->sync_standbys_status |= SYNC_STANDBY_INIT; LWLockRelease(SyncRepLock); } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 45adf1936e8a5..e0257cc49b3ad 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1697,13 +1697,13 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId * When skipping empty transactions in synchronous replication, we send a * keepalive message to avoid delaying such transactions. * - * It is okay to check sync_standbys_defined flag without lock here as in - * the worst case we will just send an extra keepalive message when it is + * It is okay to check sync_standbys_status without lock here as in the + * worst case we will just send an extra keepalive message when it is * really not required. */ if (skipped_xact && SyncRepRequested() && - ((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_defined) + (((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_status & SYNC_STANDBY_DEFINED)) { WalSndKeepalive(false, lsn); diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index cf32ac2488a8d..443644bae1b73 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -103,11 +103,11 @@ typedef struct XLogRecPtr lsn[NUM_SYNC_REP_WAIT_MODE]; /* - * Are any sync standbys defined? Waiting backends can't reload the - * config file safely, so checkpointer updates this value as needed. - * Protected by SyncRepLock. + * Status of data related to the synchronous standbys. Waiting backends + * can't reload the config file safely, so checkpointer updates this value + * as needed. Protected by SyncRepLock. */ - bool sync_standbys_defined; + bits8 sync_standbys_status; /* used as a registry of physical / logical walsenders to wake */ ConditionVariable wal_flush_cv; @@ -123,6 +123,21 @@ typedef struct WalSnd walsnds[FLEXIBLE_ARRAY_MEMBER]; } WalSndCtlData; +/* Flags for WalSndCtlData->sync_standbys_status */ + +/* + * Is the synchronous standby data initialized from the GUC? This is set the + * first time synchronous_standby_names is processed by the checkpointer. + */ +#define SYNC_STANDBY_INIT (1 << 0) + +/* + * Is the synchronous standby data defined? This is set when + * synchronous_standby_names has some data, after being processed by the + * checkpointer. + */ +#define SYNC_STANDBY_DEFINED (1 << 1) + extern PGDLLIMPORT WalSndCtlData *WalSndCtl; From 5375beb2136d03fa9d3ba29b3111488f14a529fb Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 12 Apr 2025 12:27:46 -0400 Subject: [PATCH 309/796] Fix GIN's shimTriConsistentFn to not corrupt its input. Commit 0f21db36d made an assumption that GIN triConsistentFns would not modify their input entryRes[] arrays. But in fact, the "shim" triConsistentFn that we use for opclasses that don't supply their own did exactly that, potentially leading to wrong answers from a GIN index search. Through bad luck, none of the test cases that we have for such opclasses exposed the bug. One response to this could be that the assumption of consistency check functions not modifying entryRes[] arrays is a bad one, but it still seems reasonable to me. Notably, shimTriConsistentFn is itself assuming that with respect to the underlying boolean consistentFn, so it's sure being self-centered in supposing that it gets to do so. Fortunately, it's quite simple to fix shimTriConsistentFn to restore the entry-time state of entryRes[], so let's do that instead. This issue doesn't affect any core GIN opclasses, since they all supply their own triConsistentFns. It does affect contrib modules btree_gin, hstore, and intarray. Along the way, I (tgl) noticed that shimTriConsistentFn failed to pick up on a "recheck" flag returned by its first call to the boolean consistentFn. This may be only a latent problem, since it would be unlikely for a consistentFn to set recheck for the all-false case and not any other cases. (Indeed, none of our contrib modules do that.) Nonetheless, it's formally wrong. Reported-by: Vinod Sridharan Author: Vinod Sridharan Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAFMdLD7XzsXfi1+DpTqTgrD8XU0i2C99KuF=5VHLWjx4C1pkcg@mail.gmail.com Backpatch-through: 13 --- contrib/intarray/expected/_int.out | 42 ++++++++++++++++++++++++++++++ contrib/intarray/sql/_int.sql | 7 +++++ src/backend/access/gin/ginlogic.c | 20 ++++++++++---- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out index b39ab82d43d50..d0e68d0447fb7 100644 --- a/contrib/intarray/expected/_int.out +++ b/contrib/intarray/expected/_int.out @@ -492,6 +492,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + SET enable_seqscan = off; -- not all of these would use index by default CREATE INDEX text_idx on test__int using gist ( a gist__int_ops ); SELECT count(*) from test__int WHERE a && '{23,50}'; @@ -566,6 +572,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + INSERT INTO test__int SELECT array(SELECT x FROM generate_series(1, 1001) x); -- should fail ERROR: input array is too big (199 maximum allowed, 1001 current), use gist__intbig_ops opclass instead DROP INDEX text_idx; @@ -648,6 +660,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 0)); ERROR: value 0 out of bounds for option "siglen" @@ -728,6 +746,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); SELECT count(*) from test__int WHERE a && '{23,50}'; @@ -802,6 +826,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); SELECT count(*) from test__int WHERE a && '{23,50}'; @@ -876,6 +906,12 @@ SELECT count(*) from test__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + DROP INDEX text_idx; -- Repeat the same queries with an extended data set. The data set is the -- same that we used before, except that each element in the array is @@ -968,4 +1004,10 @@ SELECT count(*) from more__int WHERE a @@ '!20 & !21'; 6344 (1 row) +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; + count +------- + 12 +(1 row) + RESET enable_seqscan; diff --git a/contrib/intarray/sql/_int.sql b/contrib/intarray/sql/_int.sql index 2d4ed1c9ae24f..5668ab4070453 100644 --- a/contrib/intarray/sql/_int.sql +++ b/contrib/intarray/sql/_int.sql @@ -107,6 +107,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; SET enable_seqscan = off; -- not all of these would use index by default @@ -124,6 +125,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; INSERT INTO test__int SELECT array(SELECT x FROM generate_series(1, 1001) x); -- should fail @@ -144,6 +146,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 0)); @@ -162,6 +165,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); @@ -178,6 +182,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; DROP INDEX text_idx; CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); @@ -194,6 +199,7 @@ SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from test__int WHERE a @@ '20 | !21'; SELECT count(*) from test__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; DROP INDEX text_idx; @@ -229,6 +235,7 @@ SELECT count(*) from more__int WHERE a @> '{20,23}' or a @> '{50,68}'; SELECT count(*) from more__int WHERE a @@ '(20&23)|(50&68)'; SELECT count(*) from more__int WHERE a @@ '20 | !21'; SELECT count(*) from more__int WHERE a @@ '!20 & !21'; +SELECT count(*) from test__int WHERE a @@ '!2733 & (2738 | 254)'; RESET enable_seqscan; diff --git a/src/backend/access/gin/ginlogic.c b/src/backend/access/gin/ginlogic.c index 28caf91f85736..aab0ebd566b33 100644 --- a/src/backend/access/gin/ginlogic.c +++ b/src/backend/access/gin/ginlogic.c @@ -140,7 +140,9 @@ shimBoolConsistentFn(GinScanKey key) * every combination is O(n^2), so this is only feasible for a small number of * MAYBE inputs. * - * NB: This function modifies the key->entryRes array! + * NB: This function modifies the key->entryRes array. For now that's okay + * so long as we restore the entry-time contents before returning. This may + * need revisiting if we ever invent multithreaded GIN scans, though. */ static GinTernaryValue shimTriConsistentFn(GinScanKey key) @@ -149,7 +151,7 @@ shimTriConsistentFn(GinScanKey key) int maybeEntries[MAX_MAYBE_ENTRIES]; int i; bool boolResult; - bool recheck = false; + bool recheck; GinTernaryValue curResult; /* @@ -169,8 +171,8 @@ shimTriConsistentFn(GinScanKey key) } /* - * If none of the inputs were MAYBE, so we can just call consistent - * function as is. + * If none of the inputs were MAYBE, we can just call the consistent + * function as-is. */ if (nmaybe == 0) return directBoolConsistentFn(key); @@ -179,6 +181,7 @@ shimTriConsistentFn(GinScanKey key) for (i = 0; i < nmaybe; i++) key->entryRes[maybeEntries[i]] = GIN_FALSE; curResult = directBoolConsistentFn(key); + recheck = key->recheckCurItem; for (;;) { @@ -200,13 +203,20 @@ shimTriConsistentFn(GinScanKey key) recheck |= key->recheckCurItem; if (curResult != boolResult) - return GIN_MAYBE; + { + curResult = GIN_MAYBE; + break; + } } /* TRUE with recheck is taken to mean MAYBE */ if (curResult == GIN_TRUE && recheck) curResult = GIN_MAYBE; + /* We must restore the original state of the entryRes array */ + for (i = 0; i < nmaybe; i++) + key->entryRes[maybeEntries[i]] = GIN_MAYBE; + return curResult; } From f8a9bd6918a748533f00dbe9e04e0eb80a5a6851 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 15 Apr 2025 15:27:08 +0200 Subject: [PATCH 310/796] pg_combinebackup: Fix incorrect code documentation The code comment for parse_oid accidentally used the wrong parameter when referring to the location of the last backup. Also, while there, improve sentence wording by removing a superfluous word. Backpatch to v17 where pg_combinebackup was addedd Author: Amul Sul Reviewed-by: Daniel Gustafsson Reviewed-by: Robert Haas Discussion: https://postgr.es/m/CAAJ_b95ecWgzcS4K3Dx0E_Yp-SLwK5JBasFgioKMSjhQLw9xvg@mail.gmail.com Backpatch-through: 17 --- src/bin/pg_combinebackup/pg_combinebackup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/pg_combinebackup/pg_combinebackup.c b/src/bin/pg_combinebackup/pg_combinebackup.c index a29ed23f3e449..98a422c580088 100644 --- a/src/bin/pg_combinebackup/pg_combinebackup.c +++ b/src/bin/pg_combinebackup/pg_combinebackup.c @@ -804,7 +804,7 @@ parse_oid(char *s, Oid *result) * Copy files from the input directory to the output directory, reconstructing * full files from incremental files as required. * - * If processing is a user-defined tablespace, the tsoid should be the OID + * If processing a user-defined tablespace, the tsoid should be the OID * of that tablespace and input_directory and output_directory should be the * toplevel input and output directories for that tablespace. Otherwise, * tsoid should be InvalidOid and input_directory and output_directory should @@ -816,7 +816,7 @@ parse_oid(char *s, Oid *result) * * n_prior_backups is the number of prior backups that we have available. * This doesn't count the very last backup, which is referenced by - * output_directory, just the older ones. prior_backup_dirs is an array of + * input_directory, just the older ones. prior_backup_dirs is an array of * the locations of those previous backups. */ static void From 9b526ba9bd082dfc39c1c96ec94bbfa4f917e1bd Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 15 Apr 2025 23:15:06 +0900 Subject: [PATCH 311/796] doc: Fix missing whitespace in pg_restore documentation. Previously, a space was missing between "" and "for" in the pg_restore documentation. This commit fixes the typo by adding the missing whitespace. Back-patch to v17 where the typo was added. Author: Lele Gaifax Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/87lds3ysm0.fsf@metapensiero.it Backpatch-through: 17 --- doc/src/sgml/ref/pg_restore.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 2e3ba80258177..0f23067d78489 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -198,7 +198,7 @@ PostgreSQL documentation or included from restore. The patterns are interpreted according to the same rules as / for including objects in schemas, - /for excluding objects in schemas, + / for excluding objects in schemas, / for restoring named functions, / for restoring named indexes, / for restoring named tables From 8b96ed659b895b208b783d5fef57251d68b7254d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 15 Apr 2025 12:08:34 -0400 Subject: [PATCH 312/796] Fix failure for generated column with a not-null domain constraint. If a GENERATED column is declared to have a domain data type where the domain's constraints disallow null values, INSERT commands failed because we built a targetlist that included coercing a null constant to the domain's type. The failure occurred even when the generated value would have been perfectly OK. This is adjacent to the issues fixed in 0da39aa76, but we didn't notice for lack of testing a domain with such a constraint. We aren't going to use the result of the targetlist entry for the generated column --- ExecComputeStoredGenerated will overwrite it. So it's not really necessary that it have the exact datatype of the generated column. This patch fixes the problem by changing the targetlist entry to be a null Const of the domain's base type, which should be sufficiently legal. (We do have to tweak ExecCheckPlanOutput to accept the situation, though.) This has been broken since we implemented generated columns. However, this patch only applies easily as far back as v14, partly because I (tgl) only carried 0da39aa76 back that far, but mostly because v14 significantly refactored the handling of INSERT/UPDATE targetlists. Given the lack of field complaints and the short remaining support lifetime of v13, I judge the cost-benefit ratio not good for devising a version that would work in v13. Reported-by: jian he Author: jian he Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CACJufxG59tip2+9h=rEv-ykOFjt0cbsPVchhi0RTij8bABBA0Q@mail.gmail.com Backpatch-through: 14 --- src/backend/executor/nodeModifyTable.c | 44 ++++++++++++++------ src/backend/optimizer/prep/preptlist.c | 53 ++++++++++++++++++------- src/test/regress/expected/generated.out | 5 +++ src/test/regress/sql/generated.sql | 5 +++ 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index d84e6dba45ac4..d499f8422e8cc 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -205,33 +205,53 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList) attr = TupleDescAttr(resultDesc, attno); attno++; - if (!attr->attisdropped) + /* + * Special cases here should match planner's expand_insert_targetlist. + */ + if (attr->attisdropped) { - /* Normal case: demand type match */ - if (exprType((Node *) tle->expr) != attr->atttypid) + /* + * For a dropped column, we can't check atttypid (it's likely 0). + * In any case the planner has most likely inserted an INT4 null. + * What we insist on is just *some* NULL constant. + */ + if (!IsA(tle->expr, Const) || + !((Const *) tle->expr)->constisnull) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("table row type and query-specified row type do not match"), - errdetail("Table has type %s at ordinal position %d, but query expects %s.", - format_type_be(attr->atttypid), - attno, - format_type_be(exprType((Node *) tle->expr))))); + errdetail("Query provides a value for a dropped column at ordinal position %d.", + attno))); } - else + else if (attr->attgenerated) { /* - * For a dropped column, we can't check atttypid (it's likely 0). - * In any case the planner has most likely inserted an INT4 null. - * What we insist on is just *some* NULL constant. + * For a generated column, the planner will have inserted a null + * of the column's base type (to avoid possibly failing on domain + * not-null constraints). It doesn't seem worth insisting on that + * exact type though, since a null value is type-independent. As + * above, just insist on *some* NULL constant. */ if (!IsA(tle->expr, Const) || !((Const *) tle->expr)->constisnull) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("table row type and query-specified row type do not match"), - errdetail("Query provides a value for a dropped column at ordinal position %d.", + errdetail("Query provides a value for a generated column at ordinal position %d.", attno))); } + else + { + /* Normal case: demand type match */ + if (exprType((Node *) tle->expr) != attr->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table has type %s at ordinal position %d, but query expects %s.", + format_type_be(attr->atttypid), + attno, + format_type_be(exprType((Node *) tle->expr))))); + } } if (attno != resultDesc->natts) ereport(ERROR, diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 72426765d3fe0..35358b085d506 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -44,6 +44,7 @@ #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "utils/lsyscache.h" #include "utils/rel.h" static List *expand_insert_targetlist(PlannerInfo *root, List *tlist, @@ -419,9 +420,8 @@ expand_insert_targetlist(PlannerInfo *root, List *tlist, Relation rel) * * INSERTs should insert NULL in this case. (We assume the * rewriter would have inserted any available non-NULL default - * value.) Also, if the column isn't dropped, apply any domain - * constraints that might exist --- this is to catch domain NOT - * NULL. + * value.) Also, normally we must apply any domain constraints + * that might exist --- this is to catch domain NOT NULL. * * When generating a NULL constant for a dropped column, we label * it INT4 (any other guaranteed-to-exist datatype would do as @@ -431,21 +431,17 @@ expand_insert_targetlist(PlannerInfo *root, List *tlist, Relation rel) * representation is datatype-independent. This could perhaps * confuse code comparing the finished plan to the target * relation, however. + * + * Another exception is that if the column is generated, the value + * we produce here will be ignored, and we don't want to risk + * throwing an error. So in that case we *don't* want to apply + * domain constraints, so we must produce a NULL of the base type. + * Again, code comparing the finished plan to the target relation + * must account for this. */ Node *new_expr; - if (!att_tup->attisdropped) - { - new_expr = coerce_null_to_domain(att_tup->atttypid, - att_tup->atttypmod, - att_tup->attcollation, - att_tup->attlen, - att_tup->attbyval); - /* Must run expression preprocessing on any non-const nodes */ - if (!IsA(new_expr, Const)) - new_expr = eval_const_expressions(root, new_expr); - } - else + if (att_tup->attisdropped) { /* Insert NULL for dropped column */ new_expr = (Node *) makeConst(INT4OID, @@ -456,6 +452,33 @@ expand_insert_targetlist(PlannerInfo *root, List *tlist, Relation rel) true, /* isnull */ true /* byval */ ); } + else if (att_tup->attgenerated) + { + /* Generated column, insert a NULL of the base type */ + Oid baseTypeId = att_tup->atttypid; + int32 baseTypeMod = att_tup->atttypmod; + + baseTypeId = getBaseTypeAndTypmod(baseTypeId, &baseTypeMod); + new_expr = (Node *) makeConst(baseTypeId, + baseTypeMod, + att_tup->attcollation, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + } + else + { + /* Normal column, insert a NULL of the column datatype */ + new_expr = coerce_null_to_domain(att_tup->atttypid, + att_tup->atttypmod, + att_tup->attcollation, + att_tup->attlen, + att_tup->attbyval); + /* Must run expression preprocessing on any non-const nodes */ + if (!IsA(new_expr, Const)) + new_expr = eval_const_expressions(root, new_expr); + } new_tle = makeTargetEntry((Expr *) new_expr, attrno, diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index 499072e14ca87..a1f67abb688cf 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -759,6 +759,11 @@ CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * INSERT INTO gtest24 (a) VALUES (4); -- ok INSERT INTO gtest24 (a) VALUES (6); -- error ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check" +CREATE DOMAIN gtestdomainnn AS int CHECK (VALUE IS NOT NULL); +CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24nn (a) VALUES (4); -- ok +INSERT INTO gtest24nn (a) VALUES (NULL); -- error +ERROR: value for domain gtestdomainnn violates check constraint "gtestdomainnn_check" -- typed tables (currently not supported) CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index cb55d77821f21..ba59325da87ee 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -387,6 +387,11 @@ CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * INSERT INTO gtest24 (a) VALUES (4); -- ok INSERT INTO gtest24 (a) VALUES (6); -- error +CREATE DOMAIN gtestdomainnn AS int CHECK (VALUE IS NOT NULL); +CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24nn (a) VALUES (4); -- ok +INSERT INTO gtest24nn (a) VALUES (NULL); -- error + -- typed tables (currently not supported) CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); From c9f5866adc9f3feafa9553ad806a3f7a3300ec02 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 16 Apr 2025 13:31:44 -0400 Subject: [PATCH 313/796] Fix pg_dump --clean with partitioned indexes. We'd try to drop the partitions of a partitioned index separately, which is disallowed by the backend, leading to an error during restore. While the error is harmless, it causes problems if you try to use --single-transaction mode. Fortunately, there seems no need to do a DROP at all, since the partition will go away silently when we drop either the parent index or the partition's table. So just make the DROP conditional on not being a partition. Reported-by: jian he Author: jian he Reviewed-by: Pavel Stehule Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CACJufxF0QSdkjFKF4di-JGWN6CSdQYEAhGPmQJJCdkSZtd=oLg@mail.gmail.com Backpatch-through: 13 --- src/bin/pg_dump/pg_dump.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 85adb5dee7654..9ed1a856fa3d3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -16933,7 +16933,17 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) qindxname); } - appendPQExpBuffer(delq, "DROP INDEX %s;\n", qqindxname); + /* + * If this index is a member of a partitioned index, the backend will + * not allow us to drop it separately, so don't try. It will go away + * automatically when we drop either the index's table or the + * partitioned index. (If, in a selective restore with --clean, we + * drop neither of those, then this index will not be dropped either. + * But that's fine, and even if you think it's not, the backend won't + * let us do differently.) + */ + if (indxinfo->parentidx == 0) + appendPQExpBuffer(delq, "DROP INDEX %s;\n", qqindxname); if (indxinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId, @@ -16986,11 +16996,15 @@ dumpIndexAttach(Archive *fout, const IndexAttachInfo *attachinfo) fmtQualifiedDumpable(attachinfo->partitionIdx)); /* - * There is no point in creating a drop query as the drop is done by - * index drop. (If you think to change this, see also - * _printTocEntry().) Although this object doesn't really have - * ownership as such, set the owner field anyway to ensure that the - * command is run by the correct role at restore time. + * There is no need for a dropStmt since the drop is done implicitly + * when we drop either the index's table or the partitioned index. + * Moreover, since there's no ALTER INDEX DETACH PARTITION command, + * there's no way to do it anyway. (If you think to change this, + * consider also what to do with --if-exists.) + * + * Although this object doesn't really have ownership as such, set the + * owner field anyway to ensure that the command is run by the correct + * role at restore time. */ ArchiveEntry(fout, attachinfo->dobj.catId, attachinfo->dobj.dumpId, ARCHIVE_OPTS(.tag = attachinfo->dobj.name, From 9fe488aa963f220e5d9806e9b820917d6d5886c6 Mon Sep 17 00:00:00 2001 From: Tatsuo Ishii Date: Fri, 18 Apr 2025 09:35:35 +0900 Subject: [PATCH 314/796] Doc: fix missing comma at the end of a line. Backpatch to 17, where the line was added. Reported by Noboru Saito while he was working on translating the file into Japanese. Discussion: https://postgr.es/m/20250417.203047.1321297410457834775.ishii%40postgresql.org Reported-by: Noboru Saito Reviewed-by: Daniel Gustafs Backpatch-through: 17 --- doc/src/sgml/libpq.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 789a6ec24865d..ab296b6f87b72 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -5070,7 +5070,7 @@ unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length); , , , - + , , and respectively. From 2f4a82c394e6e51541b909d692587ffbacc686d0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 19 Apr 2025 16:37:42 -0400 Subject: [PATCH 315/796] Be more wary of corrupt data in pageinspect's heap_page_items(). The original intent in heap_page_items() was to return nulls, not throw an error or crash, if an item was sufficiently corrupt that we couldn't safely extract data from it. However, commit d6061f83a utterly missed that memo, and not only put in an un-length-checked copy of the tuple's data section, but also managed to break the check on sane nulls-bitmap length. Either mistake could possibly lead to a SIGSEGV crash if the tuple is corrupt. Bug: #18896 Reported-by: Dmitry Kovalenko Author: Dmitry Kovalenko Reviewed-by: Tom Lane Discussion: https://postgr.es/m/18896-add267b8e06663e3@postgresql.org Backpatch-through: 13 --- contrib/pageinspect/heapfuncs.c | 45 ++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c index 27225150a5a16..ff4ea66ef746c 100644 --- a/contrib/pageinspect/heapfuncs.c +++ b/contrib/pageinspect/heapfuncs.c @@ -213,11 +213,8 @@ heap_page_items(PG_FUNCTION_ARGS) lp_offset + lp_len <= raw_page_size) { HeapTupleHeader tuphdr; - bytea *tuple_data_bytea; - int tuple_data_len; /* Extract information from the tuple header */ - tuphdr = (HeapTupleHeader) PageGetItem(page, id); values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr)); @@ -229,31 +226,32 @@ heap_page_items(PG_FUNCTION_ARGS) values[9] = UInt32GetDatum(tuphdr->t_infomask); values[10] = UInt8GetDatum(tuphdr->t_hoff); - /* Copy raw tuple data into bytea attribute */ - tuple_data_len = lp_len - tuphdr->t_hoff; - tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ); - SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ); - memcpy(VARDATA(tuple_data_bytea), (char *) tuphdr + tuphdr->t_hoff, - tuple_data_len); - values[13] = PointerGetDatum(tuple_data_bytea); - /* * We already checked that the item is completely within the raw * page passed to us, with the length given in the line pointer. - * Let's check that t_hoff doesn't point over lp_len, before using - * it to access t_bits and oid. + * But t_hoff could be out of range, so check it before relying on + * it to fetch additional info. */ if (tuphdr->t_hoff >= SizeofHeapTupleHeader && tuphdr->t_hoff <= lp_len && tuphdr->t_hoff == MAXALIGN(tuphdr->t_hoff)) { + int tuple_data_len; + bytea *tuple_data_bytea; + + /* Copy null bitmask and OID, if present */ if (tuphdr->t_infomask & HEAP_HASNULL) { - int bits_len; - - bits_len = - BITMAPLEN(HeapTupleHeaderGetNatts(tuphdr)) * BITS_PER_BYTE; - values[11] = CStringGetTextDatum(bits_to_text(tuphdr->t_bits, bits_len)); + int bitmaplen; + + bitmaplen = BITMAPLEN(HeapTupleHeaderGetNatts(tuphdr)); + /* better range-check the attribute count, too */ + if (bitmaplen <= tuphdr->t_hoff - SizeofHeapTupleHeader) + values[11] = + CStringGetTextDatum(bits_to_text(tuphdr->t_bits, + bitmaplen * BITS_PER_BYTE)); + else + nulls[11] = true; } else nulls[11] = true; @@ -262,11 +260,22 @@ heap_page_items(PG_FUNCTION_ARGS) values[12] = HeapTupleHeaderGetOidOld(tuphdr); else nulls[12] = true; + + /* Copy raw tuple data into bytea attribute */ + tuple_data_len = lp_len - tuphdr->t_hoff; + tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ); + SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ); + if (tuple_data_len > 0) + memcpy(VARDATA(tuple_data_bytea), + (char *) tuphdr + tuphdr->t_hoff, + tuple_data_len); + values[13] = PointerGetDatum(tuple_data_bytea); } else { nulls[11] = true; nulls[12] = true; + nulls[13] = true; } } else From 4608eb06a2837e47021e912c6df1be80c4d88efa Mon Sep 17 00:00:00 2001 From: David Rowley Date: Sun, 20 Apr 2025 22:12:37 +1200 Subject: [PATCH 316/796] Fix issue with ORDER BY / DISTINCT aggregates and FILTER 1349d2790 added support so that aggregate functions with an ORDER BY or DISTINCT clause could make use of presorted inputs to avoid an implicit sort within nodeAgg.c. That commit failed to consider that a FILTER clause may exist that filters rows before the aggregate function arguments are evaluated. That can be problematic if an aggregate argument contains an expression which could error out during evaluation. It's perfectly valid to want to have a FILTER clause which eliminates such values, and with the pre-sorted path added in 1349d2790, it was possible that the planner would produce a plan with a Sort node above the Aggregate to perform the sort on the aggregate's arguments long before the Aggregate node would filter out the non-matching values. Here we fix this by inspecting ORDER BY / DISTINCT aggregate functions which have a FILTER clause to see if the aggregate's arguments are anything more complex than a Var or a Const. Evaluating these isn't going to cause an error. If we find any non-Var, non-Const parameters then the planner will now opt to perform the sort in the Aggregate node for these aggregates, i.e. disable the presorted aggregate optimization. An alternative fix would have been to completely disallow the presorted optimization for Aggrefs with any FILTER clause, but that wasn't done as that could cause large performance regressions for queries that see significant gains from 1349d2790 due to presorted results coming in from an Index Scan. Backpatch to 16, where 1349d2790 was introduced Author: David Rowley Reported-by: Kaimeh Diagnosed-by: Tom Lane Discussion: https://postgr.es/m/CAK-%2BJz9J%3DQ06-M7cDJoPNeYbz5EZDqkjQbJnmRyQyzkbRGsYkA%40mail.gmail.com Backpatch-through: 16 --- src/backend/optimizer/plan/planner.c | 51 ++++++++++++++++++++++-- src/test/regress/expected/aggregates.out | 37 +++++++++++++++++ src/test/regress/expected/sqljson.out | 24 +++++------ src/test/regress/sql/aggregates.sql | 19 +++++++++ 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6eeeb0e1d03c6..72a26695f06b6 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -3280,10 +3280,53 @@ adjust_group_pathkeys_for_groupagg(PlannerInfo *root) if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) continue; - /* only add aggregates with a DISTINCT or ORDER BY */ - if (aggref->aggdistinct != NIL || aggref->aggorder != NIL) - unprocessed_aggs = bms_add_member(unprocessed_aggs, - foreach_current_index(lc)); + /* Skip unless there's a DISTINCT or ORDER BY clause */ + if (aggref->aggdistinct == NIL && aggref->aggorder == NIL) + continue; + + /* Additional safety checks are needed if there's a FILTER clause */ + if (aggref->aggfilter != NULL) + { + ListCell *lc2; + bool allow_presort = true; + + /* + * When the Aggref has a FILTER clause, it's possible that the + * filter removes rows that cannot be sorted because the + * expression to sort by results in an error during its + * evaluation. This is a problem for presorting as that happens + * before the FILTER, whereas without presorting, the Aggregate + * node will apply the FILTER *before* sorting. So that we never + * try to sort anything that might error, here we aim to skip over + * any Aggrefs with arguments with expressions which, when + * evaluated, could cause an ERROR. Vars and Consts are ok. There + * may be more cases that should be allowed, but more thought + * needs to be given. Err on the side of caution. + */ + foreach(lc2, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc2); + Expr *expr = tle->expr; + + while (IsA(expr, RelabelType)) + expr = (Expr *) (castNode(RelabelType, expr))->arg; + + /* Common case, Vars and Consts are ok */ + if (IsA(expr, Var) || IsA(expr, Const)) + continue; + + /* Unsupported. Don't try to presort for this Aggref */ + allow_presort = false; + break; + } + + /* Skip unsupported Aggrefs */ + if (!allow_presort) + continue; + } + + unprocessed_aggs = bms_add_member(unprocessed_aggs, + foreach_current_index(lc)); } /* diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 30af83f23ccdb..9b97fcf40b54a 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1586,6 +1586,43 @@ select sum(two order by two) from tenk1; (2 rows) reset enable_presorted_aggregate; +-- +-- Test cases with FILTER clause +-- +-- Ensure we presort when the aggregate contains plain Vars +explain (costs off) +select sum(two order by two) filter (where two > 1) from tenk1; + QUERY PLAN +------------------------------- + Aggregate + -> Sort + Sort Key: two + -> Seq Scan on tenk1 +(4 rows) + +-- Ensure we presort for RelabelType'd Vars +explain (costs off) +select string_agg(distinct f1, ',') filter (where length(f1) > 1) +from varchar_tbl; + QUERY PLAN +------------------------------------- + Aggregate + -> Sort + Sort Key: f1 + -> Seq Scan on varchar_tbl +(4 rows) + +-- Ensure we don't presort when the aggregate's argument contains an +-- explicit cast. +explain (costs off) +select string_agg(distinct f1::varchar(2), ',') filter (where length(f1) > 1) +from varchar_tbl; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on varchar_tbl +(2 rows) + -- -- Test combinations of DISTINCT and/or ORDER BY -- diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 67b68dfc2325d..9e52914a18263 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -835,22 +835,22 @@ SELECT FROM (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar); -[ RECORD 1 ]--------------------+------------------------------------------------------------------------------------------------------------------------- -no_options | [1, 2, 3, 4, 5] -returning_jsonb | [1, 2, 3, 4, 5] -absent_on_null | [1, 2, 3, 4, 5] -absentonnull_returning_jsonb | [1, 2, 3, 4, 5] -null_on_null | [1, 2, 3, 4, 5, null, null, null, null] -nullonnull_returning_jsonb | [1, 2, 3, 4, 5, null, null, null, null] -row_no_options | [{"bar":1}, + - | {"bar":2}, + +no_options | [3, 1, 5, 2, 4] +returning_jsonb | [3, 1, 5, 2, 4] +absent_on_null | [3, 1, 5, 2, 4] +absentonnull_returning_jsonb | [3, 1, 5, 2, 4] +null_on_null | [null, 3, 1, null, null, 5, 2, 4, null] +nullonnull_returning_jsonb | [null, 3, 1, null, null, 5, 2, 4, null] +row_no_options | [{"bar":null}, + | {"bar":3}, + - | {"bar":4}, + - | {"bar":5}, + - | {"bar":null}, + + | {"bar":1}, + | {"bar":null}, + | {"bar":null}, + + | {"bar":5}, + + | {"bar":2}, + + | {"bar":4}, + | {"bar":null}] -row_returning_jsonb | [{"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 5}, {"bar": null}, {"bar": null}, {"bar": null}, {"bar": null}] +row_returning_jsonb | [{"bar": null}, {"bar": 3}, {"bar": 1}, {"bar": null}, {"bar": null}, {"bar": 5}, {"bar": 2}, {"bar": 4}, {"bar": null}] row_filtered_agg | [{"bar":3}, + | {"bar":4}, + | {"bar":5}] diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 675b23add5962..fe4d89aec6abe 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -595,6 +595,25 @@ explain (costs off) select sum(two order by two) from tenk1; reset enable_presorted_aggregate; +-- +-- Test cases with FILTER clause +-- + +-- Ensure we presort when the aggregate contains plain Vars +explain (costs off) +select sum(two order by two) filter (where two > 1) from tenk1; + +-- Ensure we presort for RelabelType'd Vars +explain (costs off) +select string_agg(distinct f1, ',') filter (where length(f1) > 1) +from varchar_tbl; + +-- Ensure we don't presort when the aggregate's argument contains an +-- explicit cast. +explain (costs off) +select string_agg(distinct f1::varchar(2), ',') filter (where length(f1) > 1) +from varchar_tbl; + -- -- Test combinations of DISTINCT and/or ORDER BY -- From e77b8755bf54010ba107d47d0087d81ea521a4d7 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Sun, 20 Apr 2025 08:28:48 -0700 Subject: [PATCH 317/796] Avoid ERROR at ON COMMIT DELETE ROWS after relhassubclass=f. Commit 7102070329d8147246d2791321f9915c3b5abf31 fixed a similar bug, but it missed the case of database-wide ANALYZE ("use_own_xacts" mode). Commit a07e03fd8fa7daf4d1356f7cb501ffe784ea6257 changed consequences from silent discard of a pg_class stats (relpages et al.) update to ERROR "tuple to be updated was already modified". Losing a relpages update of an ON COMMIT DELETE ROWS table was negligible, but a COMMIT-time error isn't negligible. Back-patch to v13 (all supported versions). Reported-by: Richard Guo Discussion: https://postgr.es/m/CAMbWs4-XwMKMKJ_GT=p3_-_=j9rQSEs1FbDFUnW9zHuKPsPNEQ@mail.gmail.com Backpatch-through: 13 --- src/backend/commands/vacuum.c | 2 ++ src/test/regress/expected/maintain_every.out | 33 ++++++++++++++++++++ src/test/regress/parallel_schedule | 4 +++ src/test/regress/sql/maintain_every.sql | 26 +++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 src/test/regress/expected/maintain_every.out create mode 100644 src/test/regress/sql/maintain_every.sql diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 79fe26a932550..9f3af7a22c82e 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -642,6 +642,8 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, if (use_own_xacts) { PopActiveSnapshot(); + /* standard_ProcessUtility() does CCI if !use_own_xacts */ + CommandCounterIncrement(); CommitTransactionCommand(); } else diff --git a/src/test/regress/expected/maintain_every.out b/src/test/regress/expected/maintain_every.out new file mode 100644 index 0000000000000..dea1089c2499b --- /dev/null +++ b/src/test/regress/expected/maintain_every.out @@ -0,0 +1,33 @@ +-- Test maintenance commands that visit every eligible relation. Run as a +-- non-superuser, to skip other users' tables. +CREATE ROLE regress_maintain; +SET ROLE regress_maintain; +-- Test database-wide ANALYZE ("use_own_xacts" mode) setting relhassubclass=f +-- for non-partitioning inheritance, w/ ON COMMIT DELETE ROWS building an +-- empty index. +CREATE TEMP TABLE past_inh_db_other (); -- need 2 tables for "use_own_xacts" +CREATE TEMP TABLE past_inh_db_parent () ON COMMIT DELETE ROWS; +CREATE TEMP TABLE past_inh_db_child () INHERITS (past_inh_db_parent); +CREATE INDEX ON past_inh_db_parent ((1)); +ANALYZE past_inh_db_parent; +SELECT reltuples, relhassubclass + FROM pg_class WHERE oid = 'past_inh_db_parent'::regclass; + reltuples | relhassubclass +-----------+---------------- + 0 | t +(1 row) + +DROP TABLE past_inh_db_child; +SET client_min_messages = error; -- hide WARNINGs for other users' tables +ANALYZE; +RESET client_min_messages; +SELECT reltuples, relhassubclass + FROM pg_class WHERE oid = 'past_inh_db_parent'::regclass; + reltuples | relhassubclass +-----------+---------------- + 0 | f +(1 row) + +DROP TABLE past_inh_db_parent, past_inh_db_other; +RESET ROLE; +DROP ROLE regress_maintain; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f53a526f7cd94..36aeea6bd3d56 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -91,6 +91,10 @@ test: select_parallel test: write_parallel test: vacuum_parallel +# Run this alone, because concurrent DROP TABLE would make non-superuser +# "ANALYZE;" fail with "relation with OID $n does not exist". +test: maintain_every + # no relation related tests can be put in this group test: publication subscription diff --git a/src/test/regress/sql/maintain_every.sql b/src/test/regress/sql/maintain_every.sql new file mode 100644 index 0000000000000..263e97272d596 --- /dev/null +++ b/src/test/regress/sql/maintain_every.sql @@ -0,0 +1,26 @@ +-- Test maintenance commands that visit every eligible relation. Run as a +-- non-superuser, to skip other users' tables. + +CREATE ROLE regress_maintain; +SET ROLE regress_maintain; + +-- Test database-wide ANALYZE ("use_own_xacts" mode) setting relhassubclass=f +-- for non-partitioning inheritance, w/ ON COMMIT DELETE ROWS building an +-- empty index. +CREATE TEMP TABLE past_inh_db_other (); -- need 2 tables for "use_own_xacts" +CREATE TEMP TABLE past_inh_db_parent () ON COMMIT DELETE ROWS; +CREATE TEMP TABLE past_inh_db_child () INHERITS (past_inh_db_parent); +CREATE INDEX ON past_inh_db_parent ((1)); +ANALYZE past_inh_db_parent; +SELECT reltuples, relhassubclass + FROM pg_class WHERE oid = 'past_inh_db_parent'::regclass; +DROP TABLE past_inh_db_child; +SET client_min_messages = error; -- hide WARNINGs for other users' tables +ANALYZE; +RESET client_min_messages; +SELECT reltuples, relhassubclass + FROM pg_class WHERE oid = 'past_inh_db_parent'::regclass; +DROP TABLE past_inh_db_parent, past_inh_db_other; + +RESET ROLE; +DROP ROLE regress_maintain; From 6b7b2395a6c443a6c0a0ea4a88ad4c07308c6f53 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Sun, 20 Apr 2025 08:28:48 -0700 Subject: [PATCH 318/796] Test restartpoints in archive recovery. v14 commit 1f95181b44c843729caaa688f74babe9403b5850 and its v13 equivalent caused timing-dependent failures in archive recovery, at restartpoints. The symptom was "invalid magic number 0000 in log segment X, offset 0", "unexpected pageaddr X in log segment Y, offset 0" [X < Y], or an assertion failure. Commit 3635a0a35aafd3bfa80b7a809bc6e91ccd36606a and predecessors back-patched v15 changes to fix that. This test reproduces the problem probabilistically, typically in less than 1000 iterations of the test. Hence, buildfarm and CI runs would have surfaced enough failures to get attention within a day. Reported-by: Arun Thirupathi Discussion: https://postgr.es/m/20250306193013.36.nmisch@google.com Backpatch-through: 13 --- src/test/recovery/meson.build | 1 + .../recovery/t/045_archive_restartpoint.pl | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/test/recovery/t/045_archive_restartpoint.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 7623cb1fe6334..25dd6e5014cf3 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -52,6 +52,7 @@ tests += { 't/041_checkpoint_at_promote.pl', 't/042_low_level_backup.pl', 't/043_no_contrecord_switch.pl', + 't/045_archive_restartpoint.pl', ], }, } diff --git a/src/test/recovery/t/045_archive_restartpoint.pl b/src/test/recovery/t/045_archive_restartpoint.pl new file mode 100644 index 0000000000000..b143bc4e1d4e7 --- /dev/null +++ b/src/test/recovery/t/045_archive_restartpoint.pl @@ -0,0 +1,57 @@ + +# Copyright (c) 2024-2025, PostgreSQL Global Development Group + +# Test restartpoints during archive recovery. +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $archive_max_mb = 320; +my $wal_segsize = 1; + +# Initialize primary node +my $node_primary = PostgreSQL::Test::Cluster->new('primary'); +$node_primary->init( + has_archiving => 1, + allows_streaming => 1, + extra => [ '--wal-segsize' => $wal_segsize ]); +$node_primary->start; +my $backup_name = 'my_backup'; +$node_primary->backup($backup_name); + +$node_primary->safe_psql('postgres', + ('DO $$BEGIN FOR i IN 1..' . $archive_max_mb / $wal_segsize) + . ' LOOP CHECKPOINT; PERFORM pg_switch_wal(); END LOOP; END$$;'); + +# Force archiving of WAL file containing recovery target +my $until_lsn = $node_primary->lsn('write'); +$node_primary->safe_psql('postgres', "SELECT pg_switch_wal()"); +$node_primary->stop; + +# Archive recovery +my $node_restore = PostgreSQL::Test::Cluster->new('restore'); +$node_restore->init_from_backup($node_primary, $backup_name, + has_restoring => 1); +$node_restore->append_conf('postgresql.conf', + "recovery_target_lsn = '$until_lsn'"); +$node_restore->append_conf('postgresql.conf', + 'recovery_target_action = pause'); +$node_restore->append_conf('postgresql.conf', + 'max_wal_size = ' . 2 * $wal_segsize); +$node_restore->append_conf('postgresql.conf', 'log_checkpoints = on'); + +$node_restore->start; + +# Wait until restore has replayed enough data +my $caughtup_query = + "SELECT '$until_lsn'::pg_lsn <= pg_last_wal_replay_lsn()"; +$node_restore->poll_query_until('postgres', $caughtup_query) + or die "Timed out while waiting for restore to catch up"; + +$node_restore->stop; +ok(1, 'restore caught up'); + +done_testing(); From b110c3d3adfc6417daba716a9adbc877749fa31e Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 22 Apr 2025 11:04:44 +1200 Subject: [PATCH 319/796] Doc: fix incorrect punctuation Author: Noboru Saito Discussion: https://postgr.es/m/CAAM3qnJtv5YbjpwDfVOYN2gZ9zGSLFM1UGJgptSXmwfifOZJFQ@mail.gmail.com Backpatch-through: 17 --- doc/src/sgml/monitoring.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index eabc4f5d172ed..d73c627382ace 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1148,7 +1148,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage Extensions can add Extension, - InjectionPoint. and LWLock events + InjectionPoint, and LWLock events to the lists shown in and . In some cases, the name of an LWLock assigned by an extension will not be From 07d59d04965ecf7368ced166982cdce875f10e95 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 22 Apr 2025 10:01:42 +0900 Subject: [PATCH 320/796] Rename injection point for invalidation messages at end of transaction This injection point was named "AtEOXact_Inval-with-transInvalInfo", not respecting the implied naming convention that injection points should use lower-case characters, with terms separated by dashes. All the other points defined in the tree follow this style, so let's be more consistent. Author: Hayato Kuroda Reviewed-by: Aleksander Alekseev Discussion: https://postgr.es/m/OSCPR01MB14966E14C1378DEE51FB7B7C5F5B32@OSCPR01MB14966.jpnprd01.prod.outlook.com Backpatch-through: 17 --- src/backend/utils/cache/inval.c | 2 +- .../expected/syscache-update-pruned.out | 12 ++++++------ .../expected/syscache-update-pruned_1.out | 12 ++++++------ .../specs/syscache-update-pruned.spec | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index 3ce4775b829b3..66e04f973f675 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -1032,7 +1032,7 @@ AtEOXact_Inval(bool isCommit) /* Must be at top of stack */ Assert(transInvalInfo->my_level == 1 && transInvalInfo->parent == NULL); - INJECTION_POINT("AtEOXact_Inval-with-transInvalInfo"); + INJECTION_POINT("transaction-end-process-inval"); if (isCommit) { diff --git a/src/test/modules/injection_points/expected/syscache-update-pruned.out b/src/test/modules/injection_points/expected/syscache-update-pruned.out index 9a9683bb49625..a6a4e8db996b1 100644 --- a/src/test/modules/injection_points/expected/syscache-update-pruned.out +++ b/src/test/modules/injection_points/expected/syscache-update-pruned.out @@ -10,8 +10,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> @@ -33,8 +33,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> @@ -64,8 +64,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> diff --git a/src/test/modules/injection_points/expected/syscache-update-pruned_1.out b/src/test/modules/injection_points/expected/syscache-update-pruned_1.out index 64c39d708bd5f..4dca2b86bc888 100644 --- a/src/test/modules/injection_points/expected/syscache-update-pruned_1.out +++ b/src/test/modules/injection_points/expected/syscache-update-pruned_1.out @@ -10,8 +10,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> @@ -32,8 +32,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> @@ -62,8 +62,8 @@ step waitprunable4: CALL vactest.wait_prunable(); step vac4: VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) pg_class; step grant1: GRANT SELECT ON vactest.orig50 TO PUBLIC; step wakeinval4: - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); step at2: <... completed> step wakeinval4: <... completed> diff --git a/src/test/modules/injection_points/specs/syscache-update-pruned.spec b/src/test/modules/injection_points/specs/syscache-update-pruned.spec index 086d084de2577..e3a4295bd12e8 100644 --- a/src/test/modules/injection_points/specs/syscache-update-pruned.spec +++ b/src/test/modules/injection_points/specs/syscache-update-pruned.spec @@ -117,7 +117,7 @@ session s2 setup { SELECT FROM injection_points_set_local(); SELECT FROM - injection_points_attach('AtEOXact_Inval-with-transInvalInfo', 'wait'); + injection_points_attach('transaction-end-process-inval', 'wait'); } step at2 { CREATE TRIGGER to_set_relhastriggers BEFORE UPDATE ON vactest.orig50 @@ -145,8 +145,8 @@ step wakegrant4 { } step at4 { ALTER TABLE vactest.child50 INHERIT vactest.orig50; } step wakeinval4 { - SELECT FROM injection_points_detach('AtEOXact_Inval-with-transInvalInfo'); - SELECT FROM injection_points_wakeup('AtEOXact_Inval-with-transInvalInfo'); + SELECT FROM injection_points_detach('transaction-end-process-inval'); + SELECT FROM injection_points_wakeup('transaction-end-process-inval'); } # Witness effects of steps at2 and/or at4. step inspect4 { From 3889866ee423a08d382eab7c4502654f76ed4210 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 22 Apr 2025 14:56:39 +1200 Subject: [PATCH 321/796] Doc: reword text explaining the --maintenance-db option The previous text was a little clumsy. Here we improve that. Author: David Rowley Reported-by: Noboru Saito Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/CAAM3qnJtv5YbjpwDfVOYN2gZ9zGSLFM1UGJgptSXmwfifOZJFQ@mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/ref/clusterdb.sgml | 5 ++--- doc/src/sgml/ref/reindexdb.sgml | 5 ++--- doc/src/sgml/ref/vacuumdb.sgml | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/ref/clusterdb.sgml b/doc/src/sgml/ref/clusterdb.sgml index d3145318b361f..0d2051bf6f188 100644 --- a/doc/src/sgml/ref/clusterdb.sgml +++ b/doc/src/sgml/ref/clusterdb.sgml @@ -248,9 +248,8 @@ PostgreSQL documentation - Specifies the name of the database to connect to to discover which - databases should be clustered, - when / is used. + When the / is used, connect + to this database to gather the list of databases to cluster. If not specified, the postgres database will be used, or if that does not exist, template1 will be used. This can be a connection diff --git a/doc/src/sgml/ref/reindexdb.sgml b/doc/src/sgml/ref/reindexdb.sgml index 98c3333228fb9..abcb041179bb9 100644 --- a/doc/src/sgml/ref/reindexdb.sgml +++ b/doc/src/sgml/ref/reindexdb.sgml @@ -352,9 +352,8 @@ PostgreSQL documentation - Specifies the name of the database to connect to to discover which - databases should be reindexed, - when / is used. + When the / is used, connect + to this database to gather the list of databases to reindex. If not specified, the postgres database will be used, or if that does not exist, template1 will be used. This can be a connection diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml index 66fccb30a2d26..80c04919a4bf3 100644 --- a/doc/src/sgml/ref/vacuumdb.sgml +++ b/doc/src/sgml/ref/vacuumdb.sgml @@ -544,9 +544,8 @@ PostgreSQL documentation - Specifies the name of the database to connect to to discover which - databases should be vacuumed, - when / is used. + When the / is used, connect + to this database to gather the list of databases to vacuum. If not specified, the postgres database will be used, or if that does not exist, template1 will be used. This can be a connection From 6da056fda350992d0e3547d6e7949ff3b9f14c37 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 22 Apr 2025 12:41:58 +0900 Subject: [PATCH 322/796] doc: Mention naming convention used by injection points All the injection points used in the tree have relied on an implied rule: their names should be made of lower-case characters, with dashes between the words used. This commit adds a light mention about that in the docs, encouraging the practice. Author: Hayato Kuroda Reviewed-by: Aleksander Alekseev Discussion: https://postgr.es/m/OSCPR01MB14966E14C1378DEE51FB7B7C5F5B32@OSCPR01MB14966.jpnprd01.prod.outlook.com Backpatch-through: 17 --- doc/src/sgml/xfunc.sgml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index f3a3e4e2f8f60..a8aa871918a72 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3615,7 +3615,8 @@ INJECTION_POINT(name); within the server code. After adding a new injection point the code needs to be compiled in order for that injection point to be available in the binary. Add-ins written in C-language can declare injection points in - their own code using the same macro. + their own code using the same macro. The injection point names should + use lower-case characters, with terms separated by dashes. From 4235642465b3158e30b5d3ecfb68b8d7249b2f53 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 23 Apr 2025 13:54:53 +0900 Subject: [PATCH 323/796] Remove assertion based on pending_since in pgstat_report_stat() This assertion, based on pending_since (timestamp used to prevent stats reports to be too frequent or should a partial flush happen), is reached when it is found that no data can be flushed but a previous call of pgstat_report_stat() determined that some stats data has been found as in need of a flush. So pending_since is set when some stats data is pending (in non-force mode) or if report attempts are too frequent, and reset to 0 once all stats have been flushed. Since 5cbbe70a9cc6, WAL senders have begun to report their stats on a periodic basis for IO stats in v16~ and backend stats on HEAD, creating some friction with the concurrent pgstat_report_stat() calls that can happen in the context of a WAL sender (shutdown callback doing a final report or backend-related code paths). This problem is the cause of spurious failures in the TAP tests. In theory, this assertion can be also reached in v15, even if that's very unlikely. For example, a process, say a background worker, could do periodic and direct stats flushes with concurrent calls of pgstat_report_stat() that could cause conflicting values of pending_since. This can be done with WAL or SLRU stats flushes using pgstat_flush_wal() or pgstat_slru_flush(). HEAD makes this situation easier to happen with custom cumulative stats. This commit removes the assertion altogether, per discussion, as it is more useful to keep the state of things as they are for the WAL sender. The assertion could use a special state based on for example am_walsender, but I doubt that this would be meaningful in the long run based on the other arguments raised while discussing this issue. Reported-by: Tom Lane Reported-by: Andres Freund Discussion: https://postgr.es/m/1489124.1744685908@sss.pgh.pa.us Discussion: https://postgr.es/m/dwrkeszz6czvtkxzr5mqlciy652zau5qqnm3cp5f3p2po74ppk@omg4g3cc6dgq Backpatch-through: 15 --- src/backend/utils/activity/pgstat.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 4dec9395c0bf5..c79d6db3dac9b 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -600,7 +600,6 @@ pgstat_report_stat(bool force) !have_slrustats && !pgstat_have_pending_wal()) { - Assert(pending_since == 0); return 0; } From 921fc2e1f57a00125a2be1891e1f1636e42c3bf2 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Wed, 23 Apr 2025 10:52:36 +0530 Subject: [PATCH 324/796] Fix an oversight in 3f28b2fcac. Commit 3f28b2fcac tried to ensure that the replication origin shouldn't be advanced in case of an ERROR in the apply worker, so that it can request the same data again after restart. However, it is possible that an ERROR was caught and handled by a (say PL/pgSQL) function, and the apply worker continues to apply further changes, in which case, we shouldn't reset the replication origin. Ensure to reset the origin only when the apply worker exits after an ERROR. Commit 3f28b2fcac added new function geterrlevel, which we removed in HEAD as part of this commit, but kept it in backbranches to avoid breaking any applications. A separate case can be made to have such a function even for HEAD. Reported-by: Shawn McCoy Author: Hayato Kuroda Reviewed-by: Masahiko Sawada Reviewed-by: vignesh C Reviewed-by: Amit Kapila Backpatch-through: 16, where it was introduced Discussion: https://postgr.es/m/CALsgZNCGARa2mcYNVTSj9uoPcJo-tPuWUGECReKpNgTpo31_Pw@mail.gmail.com --- src/backend/replication/logical/worker.c | 21 +++---- src/test/subscription/t/100_bugs.pl | 77 ++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 65e22306c4863..db09978697f39 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -416,6 +416,8 @@ static inline void reset_apply_error_context_info(void); static TransApplyAction get_transaction_apply_action(TransactionId xid, ParallelApplyWorkerInfo **winfo); +static void replorigin_reset(int code, Datum arg); + /* * Form the origin name for the subscription. * @@ -4441,6 +4443,14 @@ start_apply(XLogRecPtr origin_startpos) } PG_CATCH(); { + /* + * Reset the origin state to prevent the advancement of origin + * progress if we fail to apply. Otherwise, this will result in + * transaction loss as that transaction won't be sent again by the + * server. + */ + replorigin_reset(0, (Datum) 0); + if (MySubscription->disableonerr) DisableSubscriptionAndExit(); else @@ -4928,23 +4938,12 @@ void apply_error_callback(void *arg) { ApplyErrorCallbackArg *errarg = &apply_error_callback_arg; - int elevel; if (apply_error_callback_arg.command == 0) return; Assert(errarg->origin_name); - elevel = geterrlevel(); - - /* - * Reset the origin state to prevent the advancement of origin progress if - * we fail to apply. Otherwise, this will result in transaction loss as - * that transaction won't be sent again by the server. - */ - if (elevel >= ERROR) - replorigin_reset(0, (Datum) 0); - if (errarg->rel == NULL) { if (!TransactionIdIsValid(errarg->remote_xid)) diff --git a/src/test/subscription/t/100_bugs.pl b/src/test/subscription/t/100_bugs.pl index cb36ca7b16b62..3498a90e74160 100644 --- a/src/test/subscription/t/100_bugs.pl +++ b/src/test/subscription/t/100_bugs.pl @@ -490,4 +490,81 @@ $node_publisher->stop('fast'); $node_subscriber->stop('fast'); +# The bug was that when an ERROR was caught and handled by a (PL/pgSQL) +# function, the apply worker reset the replication origin but continued +# processing subsequent changes. So, we fail to update the replication origin +# during further apply operations. This can lead to the apply worker requesting +# the changes that have been applied again after restarting. + +$node_publisher->rotate_logfile(); +$node_publisher->start(); + +$node_subscriber->rotate_logfile(); +$node_subscriber->start(); + +# Set up a publication with a table +$node_publisher->safe_psql( + 'postgres', qq( + CREATE TABLE t1 (a int); + CREATE PUBLICATION regress_pub FOR TABLE t1; +)); + +# Set up a subscription which subscribes the publication +$node_subscriber->safe_psql( + 'postgres', qq( + CREATE TABLE t1 (a int); + CREATE SUBSCRIPTION regress_sub CONNECTION '$publisher_connstr' PUBLICATION regress_pub; +)); + +$node_subscriber->wait_for_subscription_sync($node_publisher, 'regress_sub'); + +# Create an AFTER INSERT trigger on the table that raises and subsequently +# handles an exception. Subsequent insertions will trigger this exception, +# causing the apply worker to invoke its error callback with an ERROR. However, +# since the error is caught within the trigger, the apply worker will continue +# processing changes. +$node_subscriber->safe_psql( + 'postgres', q{ +CREATE FUNCTION handle_exception_trigger() +RETURNS TRIGGER AS $$ +BEGIN + BEGIN + -- Raise an exception + RAISE EXCEPTION 'This is a test exception'; + EXCEPTION + WHEN OTHERS THEN + RETURN NEW; + END; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER silent_exception_trigger +AFTER INSERT OR UPDATE ON t1 +FOR EACH ROW +EXECUTE FUNCTION handle_exception_trigger(); + +ALTER TABLE t1 ENABLE ALWAYS TRIGGER silent_exception_trigger; +}); + +# Obtain current remote_lsn value to check its advancement later +my $remote_lsn = $node_subscriber->safe_psql('postgres', + "SELECT remote_lsn FROM pg_replication_origin_status os, pg_subscription s WHERE os.external_id = 'pg_' || s.oid AND s.subname = 'regress_sub'" +); + +# Insert a tuple to replicate changes +$node_publisher->safe_psql('postgres', "INSERT INTO t1 VALUES (1);"); +$node_publisher->wait_for_catchup('regress_sub'); + +# Confirms the origin can be advanced +$result = $node_subscriber->safe_psql('postgres', + "SELECT remote_lsn > '$remote_lsn' FROM pg_replication_origin_status os, pg_subscription s WHERE os.external_id = 'pg_' || s.oid AND s.subname = 'regress_sub'" +); +is($result, 't', + 'remote_lsn has advanced for apply worker raising an exception'); + +$node_publisher->stop('fast'); +$node_subscriber->stop('fast'); + done_testing(); From 2cb077ee03d2f0e41847d0d27238618381a4863d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 23 Apr 2025 16:04:42 -0400 Subject: [PATCH 325/796] Avoid possibly-theoretical OOM crash hazard in hash_create(). One place in hash_create() used DynaHashAlloc() as a convenient shorthand for MemoryContextAlloc(). That was fine when it was written, but it stopped being fine when 9c911ec06 changed DynaHashAlloc() to use MCXT_ALLOC_NO_OOM (mea culpa). Change the code to call plain MemoryContextAlloc() as intended. I think that this bug may be unreachable in practice, since we now always create AllocSets with some space already allocated, so that an OOM failure here for a non-shared hash table should be impossible (with a hash table name of reasonable length anyway). And there aren't enough shared hash tables to make a crash for one of those probable. Nonetheless it's clearly not operating as designed, so back-patch to v16 where 9c911ec06 came in. Reported-by: Maksim Korotkov Author: Tom Lane Discussion: https://postgr.es/m/219bdccd460510efaccf90b57e5e5ef2@postgrespro.ru Backpatch-through: 16 --- src/backend/utils/hash/dynahash.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index 145e058fe675b..45b8e8e919ebb 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -390,7 +390,8 @@ hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags) } /* Initialize the hash header, plus a copy of the table name */ - hashp = (HTAB *) DynaHashAlloc(sizeof(HTAB) + strlen(tabname) + 1); + hashp = (HTAB *) MemoryContextAlloc(CurrentDynaHashCxt, + sizeof(HTAB) + strlen(tabname) + 1); MemSet(hashp, 0, sizeof(HTAB)); hashp->tabname = (char *) (hashp + 1); From 04c829d64798fdcf43a9f7c5fe2718dbe08c4346 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Fri, 25 Apr 2025 12:32:00 +0530 Subject: [PATCH 326/796] Fix typo in test file name added in commit 4909b38af0. Author: Shlok Kyal Backpatch-through: 13 Discussion: https://postgr.es/m/CANhcyEXsObdjkjxEnq10aJumDpa5J6aiPzgTh_w4KCWRYHLw6Q@mail.gmail.com --- contrib/test_decoding/Makefile | 2 +- ...alidation_distrubution.out => invalidation_distribution.out} | 0 contrib/test_decoding/meson.build | 2 +- ...idation_distrubution.spec => invalidation_distribution.spec} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename contrib/test_decoding/expected/{invalidation_distrubution.out => invalidation_distribution.out} (100%) rename contrib/test_decoding/specs/{invalidation_distrubution.spec => invalidation_distribution.spec} (100%) diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile index eef707706746e..02e961f4d3144 100644 --- a/contrib/test_decoding/Makefile +++ b/contrib/test_decoding/Makefile @@ -9,7 +9,7 @@ REGRESS = ddl xact rewrite toast permissions decoding_in_xact \ ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \ oldest_xmin snapshot_transfer subxact_without_top concurrent_stream \ twophase_snapshot slot_creation_error catalog_change_snapshot \ - skip_snapshot_restore invalidation_distrubution + skip_snapshot_restore invalidation_distribution REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf diff --git a/contrib/test_decoding/expected/invalidation_distrubution.out b/contrib/test_decoding/expected/invalidation_distribution.out similarity index 100% rename from contrib/test_decoding/expected/invalidation_distrubution.out rename to contrib/test_decoding/expected/invalidation_distribution.out diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build index b31c433681d6f..03dd80b7f1967 100644 --- a/contrib/test_decoding/meson.build +++ b/contrib/test_decoding/meson.build @@ -63,7 +63,7 @@ tests += { 'twophase_snapshot', 'slot_creation_error', 'skip_snapshot_restore', - 'invalidation_distrubution', + 'invalidation_distribution', ], 'regress_args': [ '--temp-config', files('logical.conf'), diff --git a/contrib/test_decoding/specs/invalidation_distrubution.spec b/contrib/test_decoding/specs/invalidation_distribution.spec similarity index 100% rename from contrib/test_decoding/specs/invalidation_distrubution.spec rename to contrib/test_decoding/specs/invalidation_distribution.spec From ff787dd9e33fabe62f6a75bde9ccd090c196eaf5 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 28 Apr 2025 11:22:07 +0530 Subject: [PATCH 327/796] Fix xmin advancement during fast_forward decoding. During logical decoding, we advance catalog_xmin of logical too early in fast_forward mode, resulting in required catalog data being removed by vacuum. This mode is normally used to advance the slot without processing the changes, but we still can't let the slot's xmin to advance to an incorrect value. Commit f49a80c481 fixed a similar issue where the logical slot's catalog_xmin was getting advanced prematurely during non-fast-forward mode. During xl_running_xacts processing, instead of directly advancing the slot's xmin to the oldest running xid in the record, it allowed the xmin to be held back for snapshots that can be used for not-yet-replayed transactions, as those might consider older txns as running too. However, it missed the fact that the same problem can happen during fast_forward mode decoding, as we won't build a base snapshot in that mode, and the future call to get_changes from the same slot can miss seeing the required catalog changes leading to incorrect reslts. This commit allows building the base snapshot even in fast_forward mode to prevent the early advancement of xmin. Reported-by: Amit Kapila Author: Zhijie Hou Reviewed-by: Masahiko Sawada Reviewed-by: shveta malik Reviewed-by: Amit Kapila Backpatch-through: 13 Discussion: https://postgr.es/m/CAA4eK1LqWncUOqKijiafe+Ypt1gQAQRjctKLMY953J79xDBgAg@mail.gmail.com Discussion: https://postgr.es/m/OS0PR01MB57163087F86621D44D9A72BF94BB2@OS0PR01MB5716.jpnprd01.prod.outlook.com --- .../test_decoding/expected/oldest_xmin.out | 41 +++++++++++++++++++ contrib/test_decoding/specs/oldest_xmin.spec | 5 +++ src/backend/replication/logical/decode.c | 37 +++++++++++------ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/contrib/test_decoding/expected/oldest_xmin.out b/contrib/test_decoding/expected/oldest_xmin.out index dd6053f9c1f4b..57268b38d3322 100644 --- a/contrib/test_decoding/expected/oldest_xmin.out +++ b/contrib/test_decoding/expected/oldest_xmin.out @@ -38,3 +38,44 @@ COMMIT stop (1 row) + +starting permutation: s0_begin s0_getxid s1_begin s1_insert s0_alter s0_commit s0_checkpoint s0_advance_slot s0_advance_slot s1_commit s0_vacuum s0_get_changes +step s0_begin: BEGIN; +step s0_getxid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s1_begin: BEGIN; +step s1_insert: INSERT INTO harvest VALUES ((1, 2, 3)); +step s0_alter: ALTER TYPE basket DROP ATTRIBUTE mangos; +step s0_commit: COMMIT; +step s0_checkpoint: CHECKPOINT; +step s0_advance_slot: SELECT slot_name FROM pg_replication_slot_advance('isolation_slot', pg_current_wal_lsn()); +slot_name +-------------- +isolation_slot +(1 row) + +step s0_advance_slot: SELECT slot_name FROM pg_replication_slot_advance('isolation_slot', pg_current_wal_lsn()); +slot_name +-------------- +isolation_slot +(1 row) + +step s1_commit: COMMIT; +step s0_vacuum: VACUUM pg_attribute; +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------ +BEGIN +table public.harvest: INSERT: fruits[basket]:'(1,2,3)' +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/specs/oldest_xmin.spec b/contrib/test_decoding/specs/oldest_xmin.spec index 88bd30f5ff76c..7f2fe3d7ed776 100644 --- a/contrib/test_decoding/specs/oldest_xmin.spec +++ b/contrib/test_decoding/specs/oldest_xmin.spec @@ -25,6 +25,7 @@ step "s0_commit" { COMMIT; } step "s0_checkpoint" { CHECKPOINT; } step "s0_vacuum" { VACUUM pg_attribute; } step "s0_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); } +step "s0_advance_slot" { SELECT slot_name FROM pg_replication_slot_advance('isolation_slot', pg_current_wal_lsn()); } session "s1" setup { SET synchronous_commit=on; } @@ -40,3 +41,7 @@ step "s1_commit" { COMMIT; } # will be removed (xmax set) before T1 commits. That is, interlocking doesn't # forbid modifying catalog after someone read it (and didn't commit yet). permutation "s0_begin" "s0_getxid" "s1_begin" "s1_insert" "s0_alter" "s0_commit" "s0_checkpoint" "s0_get_changes" "s0_get_changes" "s1_commit" "s0_vacuum" "s0_get_changes" + +# Perform the same testing process as described above, but use advance_slot to +# forces xmin advancement during fast forward decoding. +permutation "s0_begin" "s0_getxid" "s1_begin" "s1_insert" "s0_alter" "s0_commit" "s0_checkpoint" "s0_advance_slot" "s0_advance_slot" "s1_commit" "s0_vacuum" "s0_get_changes" diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index 8ec5adfd9099a..4911e98ec2d2e 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -411,19 +411,24 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) /* * If we don't have snapshot or we are just fast-forwarding, there is no - * point in decoding changes. + * point in decoding data changes. However, it's crucial to build the base + * snapshot during fast-forward mode (as is done in + * SnapBuildProcessChange()) because we require the snapshot's xmin when + * determining the candidate catalog_xmin for the replication slot. See + * SnapBuildProcessRunningXacts(). */ - if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT || - ctx->fast_forward) + if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT) return; switch (info) { case XLOG_HEAP2_MULTI_INSERT: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeMultiInsert(ctx, buf); break; case XLOG_HEAP2_NEW_CID: + if (!ctx->fast_forward) { xl_heap_new_cid *xlrec; @@ -470,16 +475,20 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) /* * If we don't have snapshot or we are just fast-forwarding, there is no - * point in decoding data changes. + * point in decoding data changes. However, it's crucial to build the base + * snapshot during fast-forward mode (as is done in + * SnapBuildProcessChange()) because we require the snapshot's xmin when + * determining the candidate catalog_xmin for the replication slot. See + * SnapBuildProcessRunningXacts(). */ - if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT || - ctx->fast_forward) + if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT) return; switch (info) { case XLOG_HEAP_INSERT: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeInsert(ctx, buf); break; @@ -490,17 +499,20 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) */ case XLOG_HEAP_HOT_UPDATE: case XLOG_HEAP_UPDATE: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeUpdate(ctx, buf); break; case XLOG_HEAP_DELETE: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeDelete(ctx, buf); break; case XLOG_HEAP_TRUNCATE: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeTruncate(ctx, buf); break; @@ -528,7 +540,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) break; case XLOG_HEAP_CONFIRM: - if (SnapBuildProcessChange(builder, xid, buf->origptr)) + if (SnapBuildProcessChange(builder, xid, buf->origptr) && + !ctx->fast_forward) DecodeSpecConfirm(ctx, buf); break; From 8104115358a94d1f3dddd1ab6e064e0ae70cb78c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 30 Apr 2025 11:13:49 -0400 Subject: [PATCH 328/796] Update time zone data files to tzdata release 2025b. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DST law changes in Chile: there is a new time zone America/Coyhaique for Chile's Aysén Region, to account for it changing to UTC-03 year-round and thus diverging from America/Santiago. Historical corrections for Iran. Backpatch-through: 13 --- src/timezone/data/tzdata.zi | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/timezone/data/tzdata.zi b/src/timezone/data/tzdata.zi index db6ba4af2b013..a7fb52f1968f3 100644 --- a/src/timezone/data/tzdata.zi +++ b/src/timezone/data/tzdata.zi @@ -1,4 +1,4 @@ -# version 2025a +# version 2025b # This zic input file is in the public domain. R d 1916 o - Jun 14 23s 1 S R d 1916 1919 - O Su>=1 23s 0 - @@ -2432,6 +2432,20 @@ Z America/Ciudad_Juarez -7:5:56 - LMT 1922 Ja 1 7u Z America/Costa_Rica -5:36:13 - LMT 1890 -5:36:13 - SJMT 1921 Ja 15 -6 CR C%sT +Z America/Coyhaique -4:48:16 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 +-5 - %z 1916 Jul +-4:42:45 - SMT 1918 S 10 +-4 - %z 1919 Jul +-4:42:45 - SMT 1927 S +-5 x %z 1932 S +-4 - %z 1942 Jun +-5 - %z 1942 Au +-4 - %z 1946 Au 28 24 +-5 1 %z 1947 Mar 31 24 +-5 - %z 1947 May 21 23 +-4 x %z 2025 Mar 20 +-3 - %z Z America/Cuiaba -3:44:20 - LMT 1914 -4 B %z 2003 S 24 -4 - %z 2004 O @@ -3420,7 +3434,7 @@ Z Asia/Tbilisi 2:59:11 - LMT 1880 Z Asia/Tehran 3:25:44 - LMT 1916 3:25:44 - TMT 1935 Jun 13 3:30 i %z 1977 O 20 24 -4 i %z 1979 +4 i %z 1978 N 10 24 3:30 i %z Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 5:30 - %z 1987 O From 2a663cee7f438e50657c3002b08febd6b0a6a7dc Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 1 May 2025 11:06:21 +0100 Subject: [PATCH 329/796] doc: Warn that ts_headline() output is not HTML-safe. Add a documentation warning to ts_headline() pointing out that, when working with untrusted input documents, the output is not guaranteed to be safe for direct inclusion in web pages. This is because, while it does remove some XML tags from the input, it doesn't remove all HTML markup, and so the result may be unsafe (e.g., it might permit XSS attacks). To guard against that, all HTML markup should be removed from the input, making it plain text, or the output should be passed through an HTML sanitizer. In addition, document precisely what the default text search parser recognises as valid XML tags, since that's what determines which XML tags ts_headline() will remove. Reported-by: Richard Neill Author: Dean Rasheed Reviewed-by: Noah Misch Backpatch-through: 13 --- doc/src/sgml/textsearch.sgml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index bde5f391e5c06..bf91c23bd11ee 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -1342,7 +1342,7 @@ ts_headline( config <b> and </b>, which can be suitable - for HTML output. + for HTML output (but see the warning below). @@ -1354,6 +1354,21 @@ ts_headline( config + + Warning: Cross-site scripting (XSS) safety + + The output from ts_headline is not guaranteed to + be safe for direct inclusion in web pages. When + HighlightAll is false (the + default), some simple XML tags are removed from the document, but this + is not guaranteed to remove all HTML markup. Therefore, this does not + provide an effective defense against attacks such as cross-site + scripting (XSS) attacks, when working with untrusted input. To guard + against such attacks, all HTML markup should be removed from the input + document, or an HTML sanitizer should be used on the output. + + + These option names are recognized case-insensitively. You must double-quote string values if they contain spaces or commas. @@ -2225,6 +2240,18 @@ LIMIT 10; Specifically, the only non-alphanumeric characters supported for email user names are period, dash, and underscore. + + + tag does not support all valid tag names as defined by + W3C Recommendation, XML. + Specifically, the only tag names supported are those starting with an + ASCII letter, underscore, or colon, and containing only letters, digits, + hyphens, underscores, periods, and colons. tag also + includes XML comments starting with <!-- and ending + with -->, and XML declarations (but note that this + includes anything starting with <?x and ending with + >). + From ef1c2d60eaf92f17525a13913f78f5025c107a9f Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Thu, 1 May 2025 16:51:59 -0700 Subject: [PATCH 330/796] Doc: stop implying recommendation of insecure search_path value. SQL "SET search_path = 'pg_catalog, pg_temp'" is silently equivalent to "SET search_path = pg_temp, pg_catalog, "pg_catalog, pg_temp"" instead of the intended "SET search_path = pg_catalog, pg_temp". (The intent was a two-element search path. With the single quotes, it instead specifies one element with a comma and a space in the middle of the element.) In addition to the SET statement, this affects SET clauses of CREATE FUNCTION, ALTER ROLE, and ALTER DATABASE. It does not affect the set_config() SQL function. Though the documentation did not show an insecure command, remove single quotes that could entice a reader to write an insecure command. Back-patch to v13 (all supported versions). Reported-by: Sven Klemm Author: Sven Klemm Backpatch-through: 13 --- doc/src/sgml/extend.sgml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 218940ee5ce19..4b7094f2739d7 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -1339,8 +1339,8 @@ SELECT * FROM pg_extension_update_paths('extension_namesearch_path; do not trust the path provided by CREATE/ALTER EXTENSION to be secure. Best practice is to temporarily - set search_path to 'pg_catalog, - pg_temp' and insert references to the extension's + set search_path to pg_catalog, + pg_temp and insert references to the extension's installation schema explicitly where needed. (This practice might also be helpful for creating views.) Examples can be found in the contrib modules in From 9ab9e8990233498a24048bdac42957385bdbb3b6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 2 May 2025 12:27:01 -0400 Subject: [PATCH 331/796] First-draft release notes for 17.5. As usual, the release notes for other branches will be made by cutting these down, but put them up for community review first. --- doc/src/sgml/release-17.sgml | 1250 ++++++++++++++++++++++++++++++++++ 1 file changed, 1250 insertions(+) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index 2f1bed2e290fe..5f68e118760cc 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -1,6 +1,1256 @@ + + Release 17.5 + + + Release date: + 2025-05-08 + + + + This release contains a variety of fixes from 17.4. + For information about new features in major release 17, see + . + + + + Migration to Version 17.5 + + + A dump/restore is not required for those running 17.X. + + + + However, if you have any BRIN bloom indexes, it may be advisable to + reindex them after updating. See the first changelog entry below. + + + + Also, if you are upgrading from a version earlier than 17.1, + see . + + + + + Changes + + + + + + + Avoid data loss when merging compressed BRIN summaries + in brin_bloom_union() (Tomas Vondra) + + + + The code failed to account for decompression results not being + identical to the input objects, which would result in failure to add + some of the data to the merged summary, leading to missed rows in + index searches. + + + + This mistake was present back to v14 where BRIN bloom indexes were + introduced, but this code path was only rarely reached then. It's + substantially more likely to be hit in v17 because parallel index + builds now use the code. + + + + + + + Fix unexpected attribute has wrong type errors + in UPDATE, DELETE, + and MERGE queries that use whole-row table + references to views or functions in FROM + (Tom Lane) + + + + + + + Fix MERGE into a partitioned table + with DO NOTHING actions (Tender Wang) + + + + Some cases failed with unknown action in MERGE WHEN + clause errors. + + + + + + + Prevent failure in INSERT commands when the table + has a GENERATED column of a domain data type and + the domain's constraints disallow null values (Jian He) + + + + Constraint failure was reported even if the generation expression + produced a perfectly okay result. + + + + + + + Correctly process references to outer CTE names that appear within + a WITH clause attached to + an INSERT/UPDATE/DELETE/MERGE + command that's inside WITH (Tom Lane) + + + + The parser failed to detect disallowed recursion cases, nor did it + account for such references when sorting CTEs into a usable order. + + + + + + + Fix misprocessing of casts within the keys of JSON constructor + expressions (Amit Langote) + + + + + + + Don't try to parallelize array_agg() when the + argument is of an anonymous record type (Richard Guo, Tom Lane) + + + + The protocol for communicating with parallel workers doesn't support + identifying the concrete record type that a worker is returning. + + + + + + + Fix ARRAY(subquery) + and ARRAY[expression, ...] + constructs to produce sane results when the input is of + type int2vector or oidvector (Tom Lane) + + + + This patch restores the behavior that existed + before PostgreSQL 9.5: the result is of + type int2vector[] or oidvector[]. + + + + + + + Fix possible erroneous reports of invalid affixes while parsing + Ispell dictionaries (Jacob Brazeal) + + + + + + + Fix ALTER TABLE ADD COLUMN to correctly handle + the case of a domain type that has a default (Tom Lane, Tender Wang) + + + + If a domain type has a default, adding a column of that type (without + any explicit DEFAULT + clause) failed to install the domain's default + value in existing rows, instead leaving the new column null. + + + + + + + Repair misbehavior when there are duplicate column names in a + foreign key constraint's ON DELETE SET DEFAULT + or SET NULL action (Tom Lane) + + + + + + + Improve the error message for disallowed attempts to alter the + properties of a foreign key constraint (Álvaro Herrera) + + + + + + + Avoid error when resetting + the relhassubclass flag of a temporary + table that's marked ON COMMIT DELETE ROWS + (Noah Misch) + + + + + + + Add missing deparsing of the INDENT option + of XMLSERIALIZE() (Jim Jones) + + + + Previously, views or rules + using XMLSERIALIZE(... INDENT) were dumped + without the INDENT clause, causing incorrect + results after restore. + + + + + + + Avoid premature evaluation of the arguments of an aggregate function + that has both FILTER and ORDER + BY (or DISTINCT) options (David Rowley) + + + + If there is ORDER BY + or DISTINCT, we consider pre-sorting the + aggregate input values rather than doing the sort within the Agg + plan node. But this is problematic if the aggregate inputs include + expressions that could fail (for example, a division where some of + the input divisors could be zero) and there is + a FILTER clause that's meant to prevent such + failures. Pre-sorting would push the expression evaluations to + before the FILTER test, allowing the failures to + happen anyway. Avoid this by not pre-sorting if there's + a FILTER and the input expressions are anything + more complex than a simple Var or Const. + + + + + + + Fix erroneous deductions from column NOT NULL + constraints in the presence of outer joins (Richard Guo) + + + + In some cases the planner would discard an IS NOT + NULL query condition, even though the condition applies + after an outer join and thus is not redundant. + + + + + + + Avoid incorrect optimizations based on IS [NOT] + NULL tests that are applied to composite values + (Bruce Momjian) + + + + + + + Fix planner's failure to identify more than one hashable + ScalarArrayOpExpr subexpression within a top-level expression + (David Geier) + + + + This resulted in unnecessarily-inefficient execution of any + additional subexpressions that could have been processed with a hash + table (that is, IN, NOT IN, + or = ANY clauses with all-constant right-hand + sides). + + + + + + + Fix incorrect table size estimate with low fill factor (Tomas Vondra) + + + + When the planner estimates the number of rows in a + never-yet-analyzed table, it uses the table's fillfactor setting in + the estimation, but it neglected to clamp the result to at least one + row per page. A low fillfactor could thus result in an unreasonably + small estimate. + + + + + + + Disable skip fetch optimization in bitmap heap scan + (Matthias van de Meent) + + + + It turns out that this optimization can result in returning dead + tuples when a concurrent vacuum marks a page all-visible. + + + + + + + Fix performance issues in GIN index search startup when there are + many search keys (Tom Lane, Vinod Sridharan) + + + + An indexable clause with many keys (for example, jsonbcol + ?| array[...] with tens of thousands of array elements) + took O(N2) time to start up, and was + uncancelable for that interval too. + + + + + + + Detect missing support procedures in a BRIN index operator class, + and report an error instead of crashing (Álvaro Herrera) + + + + + + + Respond to interrupts (such as query cancel) while waiting for + asynchronous subplans of an Append plan node (Heikki Linnakangas) + + + + Previously, nothing would happen until one of the subplans becomes + ready. + + + + + + + Report the I/O statistics of active WAL senders more frequently + (Bertrand Drouvot) + + + + Previously, the pg_stat_io view failed to + accumulate I/O performed by a WAL sender until that process exited. + Now such I/O will be reported after at most one second's delay. + + + + + + + Fix race condition in handling + of synchronous_standby_names immediately after + startup (Melnikov Maksim, Michael Paquier) + + + + For a short period after system startup, backends might fail to wait + for synchronous commit even + though synchronous_standby_names is enabled. + + + + + + + Cope with possible intra-query changes + of io_combine_limit (Thomas Munro) + + + + + + + Avoid infinite loop if scram_iterations is set to + INT_MAX (Kevin K Biju) + + + + + + + Avoid possible crashes due to double transformation + of json_array()'s subquery (Tom Lane) + + + + + + + Fix pg_strtof() to not crash with null endptr + (Alexander Lakhin, Tom Lane) + + + + + + + Fix crash after out-of-memory in certain GUC assignments (Daniel + Gustafsson) + + + + + + + Avoid crash when a Snowball stemmer encounters an out-of-memory + condition (Maksim Korotkov) + + + + + + + Fix over-enthusiastic freeing of SpecialJoinInfo structs during + planning (Richard Guo) + + + + This led to crashes during planning if partitionwise joining is + enabled. + + + + + + + Skip WAL recycling and preallocation during archive recovery, to + avoid corruption of WAL files that were restored from the archive + (Noah Misch, Arun Thirupathi) + + + + This change back-patches v15-era fixes that were considered largely + cosmetic at the time, but turn out to prevent data corruption in the + wake of subsequent fixes. + + + + + + + Disallow copying of invalidated replication slots (Shlok Kyal) + + + + This prevents trouble when the invalid slot points to WAL that's + already been removed. + + + + + + + Disallow restoring logical replication slots on standby servers that + are not in hot-standby mode (Masahiko Sawada) + + + + This prevents a scenario where the slot could remain valid after + promotion even if wal_level is too low. + + + + + + + Prevent over-advancement of catalog xmin in fast + forward mode of logical decoding (Zhijie Hou) + + + + This mistake could allow deleted catalog entries to be vacuumed away + even though they were still potentially needed by the WAL-reading + process. + + + + + + + Avoid data loss when DDL operations that don't take a strong lock + affect tables that are being logically replicated (Shlok Kyal, + Hayato Kuroda) + + + + The catalog changes caused by the DDL command were not reflected + into WAL-decoding processes, allowing them to decode subsequent + changes using stale catalog data, probably resulting in data + corruption. + + + + + + + Prevent incorrect reset of replication origin when an apply worker + encounters an error but the error is caught and does not result in + worker exit (Hayato Kuroda) + + + + This mistake could allow duplicate data to be applied. + + + + + + + Fix crash in logical replication if the subscriber's partitioned + table has a BRIN index (Tom Lane) + + + + + + + Avoid duplicate snapshot creation in logical replication index + lookups (Heikki Linnakangas) + + + + + + + Improve detection of mixed-origin subscriptions + (Hou Zhijie, Shlok Kyal) + + + + Subscription creation gives a warning if a subscribed-to table is + also being followed through other publications, since that could + cause duplicate data to be received. This change improves that + logic to also detect cases where a partition parent or child table + is the one being followed through another publication. + + + + + + + Fix wrong checkpoint details in error message about incorrect + recovery timeline choice (David Steele) + + + + If the requested recovery timeline is not reachable, the reported + checkpoint and timeline should be the values read from the + backup_label, if there is one. This message previously reported + values from the control file, which is correct when recovering from + the control file without a backup_label, but not when there is a + backup_label. + + + + + + + Fix order of operations in smgropen() + (Andres Freund) + + + + Ensure that the SMgrRelation object is fully initialized before + calling the smgr_open callback, so that it can be cleaned up + properly if the callback fails. + + + + + + + Fix assertion failure in snapshot building (Masahiko Sawada) + + + + + + + Remove incorrect assertion + in pgstat_report_stat() (Michael Paquier) + + + + + + + Fix overly-strict assertion + in gistFindCorrectParent() (Heikki Linnakangas) + + + + + + + Avoid assertion failure in parallel vacuum + when maintenance_work_mem has a very small value + (Masahiko Sawada) + + + + + + + Fix rare assertion failure in standby servers when the primary is + restarted (Heikki Linnakangas) + + + + + + + In PL/pgSQL, avoid unexpected plan node type error + when a scrollable cursor is defined on a + simple SELECT expression + query (Andrei Lepikhov) + + + + + + + Don't try to drop individual index partitions + in pg_dump's + mode (Jian He) + + + + The server rejects such DROP commands. That has + no real consequences, since the partitions will go away anyway in + the subsequent DROPs of either their parent + tables or their partitioned index. However, the error reported for + the attempted drop causes problems when restoring + in mode. + + + + + + + In pg_dumpall, avoid emitting invalid + role GRANT commands + if pg_auth_members contains invalid role + OIDs (Tom Lane) + + + + Instead, print a warning and skip the entry. This copes better with + catalog corruption that has been seen to occur in back branches as a + result of race conditions between GRANT + and DROP ROLE. + + + + + + + In pg_amcheck + and pg_upgrade, use the correct function + to free allocations made by libpq + (Michael Paquier, Ranier Vilela) + + + + These oversights could result in crashes in certain Windows build + configurations, such as a debug build + of libpq used by a non-debug build of the + calling application. + + + + + + + Fix reindexdb's scheduling of parallel + reindex operations (Alexander Korotkov) + + + + The original coding failed to achieve the expected amount of + parallelism. + + + + + + + Allow contrib/dblink queries to be interrupted + by query cancel (Noah Misch) + + + + This change back-patches a v17-era fix. It prevents possible hangs + in CREATE DATABASE and DROP + DATABASE due to failure to detect deadlocks. + + + + + + + Avoid crashing with corrupt input data + in contrib/pageinspect's + heap_page_items() (Dmitry Kovalenko) + + + + + + + Prevent assertion failure + in contrib/pg_freespacemap's + pg_freespacemap() (Tender Wang) + + + + Applying pg_freespacemap() to a relation + lacking storage (such as a view) caused an assertion failure, + although there was no ill effect in non-assert builds. + Add an error check to reject that case. + + + + + + + In contrib/postgres_fdw, avoid pulling up + restriction conditions from subqueries (Alexander Pyhalov) + + + + This fix prevents rare cases of unexpected expression in + subquery output errors. + + + + + + + Fix build failure when an old version + of libpq_fe.h is present in system include + directories (Tom Lane) + + + + + + + Fix build failure on macOS 15.4 (Tom Lane, Peter Eisentraut) + + + + This macOS update broke our configuration probe + for strchrnul(). + + + + + + + Fix valgrind labeling of per-buffer data of read streams + (Thomas Munro) + + + + This affects no core code in released versions + of PostgreSQL, but an extension using the + per-buffer data feature might have encountered spurious failures + when being tested under valgrind. + + + + + + + Avoid valgrind complaints about string hashing code (John Naylor) + + + + + + + Update time zone data files to tzdata + release 2025b for DST law changes in Chile, plus historical + corrections for Iran (Tom Lane) + + + + There is a new time zone America/Coyhaique for Chile's Aysén Region, + to account for it changing to UTC-03 year-round and thus diverging + from America/Santiago. + + + + + + + + Release 17.4 From a1f82ef89085d0819e9bc59c85dc44a4074a00ef Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 2 May 2025 12:35:36 -0400 Subject: [PATCH 332/796] Doc: forgot to run add_commit_links.pl. --- doc/src/sgml/release-17.sgml | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index 5f68e118760cc..cf6d678a8d433 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -50,6 +50,7 @@ Branch: REL_14_STABLE [6be02bbc8] 2025-03-26 17:03:06 +0100 Avoid data loss when merging compressed BRIN summaries in brin_bloom_union() (Tomas Vondra) + § @@ -89,6 +90,7 @@ Branch: REL_13_STABLE [b200180de] 2025-03-13 12:13:07 -0400 and MERGE queries that use whole-row table references to views or functions in FROM (Tom Lane) + § @@ -103,6 +105,7 @@ Branch: REL_15_STABLE [14a33d3f0] 2025-03-29 09:52:18 +0000 Fix MERGE into a partitioned table with DO NOTHING actions (Tender Wang) + § @@ -124,6 +127,7 @@ Branch: REL_14_STABLE [4604928ed] 2025-04-15 12:08:34 -0400 Prevent failure in INSERT commands when the table has a GENERATED column of a domain data type and the domain's constraints disallow null values (Jian He) + § @@ -147,6 +151,7 @@ Branch: REL_13_STABLE [e276b5829] 2025-04-05 15:01:33 -0400 a WITH clause attached to an INSERT/UPDATE/DELETE/MERGE command that's inside WITH (Tom Lane) + § @@ -164,6 +169,7 @@ Branch: REL_17_STABLE [8b2392ae3] 2025-03-13 09:56:49 +0900 Fix misprocessing of casts within the keys of JSON constructor expressions (Amit Langote) + § @@ -177,6 +183,7 @@ Branch: REL_16_STABLE [a7aa9f21f] 2025-03-09 13:11:20 -0400 Don't try to parallelize array_agg() when the argument is of an anonymous record type (Richard Guo, Tom Lane) + § @@ -200,6 +207,7 @@ Branch: REL_13_STABLE [474aee3df] 2025-03-13 16:07:55 -0400 and ARRAY[expression, ...] constructs to produce sane results when the input is of type int2vector or oidvector (Tom Lane) + § @@ -222,6 +230,7 @@ Branch: REL_13_STABLE [f1c1bafcd] 2025-03-08 11:25:01 -0500 Fix possible erroneous reports of invalid affixes while parsing Ispell dictionaries (Jacob Brazeal) + § @@ -244,6 +253,8 @@ Branch: REL_13_STABLE [dd34cbfce] 2025-04-02 11:13:01 -0400 Fix ALTER TABLE ADD COLUMN to correctly handle the case of a domain type that has a default (Tom Lane, Tender Wang) + § + § @@ -266,6 +277,7 @@ Branch: REL_15_STABLE [f5069f026] 2025-04-04 20:11:48 -0400 Repair misbehavior when there are duplicate column names in a foreign key constraint's ON DELETE SET DEFAULT or SET NULL action (Tom Lane) + § @@ -282,6 +294,7 @@ Branch: REL_13_STABLE [0f354e080] 2025-03-04 20:07:30 +0100 Improve the error message for disallowed attempts to alter the properties of a foreign key constraint (Álvaro Herrera) + § @@ -300,6 +313,7 @@ Branch: REL_13_STABLE [d34b671a6] 2025-04-20 08:28:53 -0700 the relhassubclass flag of a temporary table that's marked ON COMMIT DELETE ROWS (Noah Misch) + § @@ -316,6 +330,8 @@ Branch: REL_16_STABLE [514d47dfb] 2025-02-21 20:37:38 +0900 Add missing deparsing of the INDENT option of XMLSERIALIZE() (Jim Jones) + § + § @@ -337,6 +353,7 @@ Branch: REL_16_STABLE [887a23237] 2025-04-20 22:12:59 +1200 Avoid premature evaluation of the arguments of an aggregate function that has both FILTER and ORDER BY (or DISTINCT) options (David Rowley) + § @@ -364,6 +381,7 @@ Branch: REL_17_STABLE [bc5a08af3] 2025-03-04 16:17:19 +0900 Fix erroneous deductions from column NOT NULL constraints in the presence of outer joins (Richard Guo) + § @@ -383,6 +401,7 @@ Branch: REL_17_STABLE [b8b1e87b7] 2025-04-07 21:33:41 -0400 Avoid incorrect optimizations based on IS [NOT] NULL tests that are applied to composite values (Bruce Momjian) + § @@ -399,6 +418,7 @@ Branch: REL_14_STABLE [b68f664bb] 2025-04-02 11:58:37 +1300 Fix planner's failure to identify more than one hashable ScalarArrayOpExpr subexpression within a top-level expression (David Geier) + § @@ -418,6 +438,7 @@ Branch: REL_17_STABLE [587b6aa3f] 2025-02-19 23:54:18 +0100 --> Fix incorrect table size estimate with low fill factor (Tomas Vondra) + § @@ -442,6 +463,7 @@ Branch: REL_13_STABLE [b9ec8125d] 2025-04-02 14:50:49 -0400 Disable skip fetch optimization in bitmap heap scan (Matthias van de Meent) + § @@ -469,6 +491,8 @@ Branch: REL_13_STABLE [c7597a1d3] 2025-04-12 12:27:46 -0400 Fix performance issues in GIN index search startup when there are many search keys (Tom Lane, Vinod Sridharan) + § + § @@ -492,6 +516,7 @@ Branch: REL_13_STABLE [f5b4a0b49] 2025-03-11 12:50:35 +0100 Detect missing support procedures in a BRIN index operator class, and report an error instead of crashing (Álvaro Herrera) + § @@ -507,6 +532,7 @@ Branch: REL_14_STABLE [d2fb076be] 2025-03-12 20:53:25 +0200 Respond to interrupts (such as query cancel) while waiting for asynchronous subplans of an Append plan node (Heikki Linnakangas) + § @@ -525,6 +551,7 @@ Branch: REL_16_STABLE [e2a82cd23] 2025-04-08 07:58:50 +0900 Report the I/O statistics of active WAL senders more frequently (Bertrand Drouvot) + § @@ -548,6 +575,7 @@ Branch: REL_13_STABLE [e2f42f812] 2025-04-11 10:02:21 +0900 Fix race condition in handling of synchronous_standby_names immediately after startup (Melnikov Maksim, Michael Paquier) + § @@ -566,6 +594,7 @@ Branch: REL_17_STABLE [e27346807] 2025-03-13 15:48:02 +1300 Cope with possible intra-query changes of io_combine_limit (Thomas Munro) + § @@ -579,6 +608,7 @@ Branch: REL_16_STABLE [de1484736] 2025-03-26 17:51:44 +0900 Avoid infinite loop if scram_iterations is set to INT_MAX (Kevin K Biju) + § @@ -592,6 +622,7 @@ Branch: REL_16_STABLE [ca54f9b70] 2025-04-05 12:13:35 -0400 Avoid possible crashes due to double transformation of json_array()'s subquery (Tom Lane) + § @@ -608,6 +639,7 @@ Branch: REL_13_STABLE [ebe919e95] 2025-03-01 14:22:56 -0500 Fix pg_strtof() to not crash with null endptr (Alexander Lakhin, Tom Lane) + § @@ -621,6 +653,7 @@ Branch: REL_16_STABLE [8d48e84c5] 2025-03-27 22:57:34 +0100 Fix crash after out-of-memory in certain GUC assignments (Daniel Gustafsson) + § @@ -637,6 +670,7 @@ Branch: REL_13_STABLE [197427fb3] 2025-02-18 21:24:12 -0500 Avoid crash when a Snowball stemmer encounters an out-of-memory condition (Maksim Korotkov) + § @@ -649,6 +683,7 @@ Branch: REL_17_STABLE [727bc6ac3] 2025-02-19 10:04:44 +0900 Fix over-enthusiastic freeing of SpecialJoinInfo structs during planning (Richard Guo) + § @@ -698,6 +733,7 @@ Branch: REL_16_STABLE [87e8599e0] 2025-04-03 10:30:05 -0700 --> Disallow copying of invalidated replication slots (Shlok Kyal) + § @@ -716,6 +752,7 @@ Branch: REL_16_STABLE [cc628f661] 2025-02-24 14:03:10 -0800 Disallow restoring logical replication slots on standby servers that are not in hot-standby mode (Masahiko Sawada) + § @@ -737,6 +774,7 @@ Branch: REL_13_STABLE [d65485b02] 2025-04-28 11:55:00 +0530 Prevent over-advancement of catalog xmin in fast forward mode of logical decoding (Zhijie Hou) + § @@ -766,6 +804,8 @@ Branch: REL_13_STABLE [4164d6976] 2025-04-25 11:45:40 +0530 Avoid data loss when DDL operations that don't take a strong lock affect tables that are being logically replicated (Shlok Kyal, Hayato Kuroda) + § + § @@ -787,6 +827,7 @@ Branch: REL_16_STABLE [0de091a4b] 2025-04-23 10:35:54 +0530 Prevent incorrect reset of replication origin when an apply worker encounters an error but the error is caught and does not result in worker exit (Hayato Kuroda) + § @@ -803,6 +844,7 @@ Branch: REL_17_STABLE [788baa9a2] 2025-02-19 16:35:15 -0500 Fix crash in logical replication if the subscriber's partitioned table has a BRIN index (Tom Lane) + § @@ -825,6 +867,8 @@ Branch: REL_13_STABLE [6c1e79589] 2025-03-10 19:02:08 +0200 Avoid duplicate snapshot creation in logical replication index lookups (Heikki Linnakangas) + § + § @@ -838,6 +882,7 @@ Branch: REL_16_STABLE [1c2a2354c] 2025-02-21 14:08:27 +0530 Improve detection of mixed-origin subscriptions (Hou Zhijie, Shlok Kyal) + § @@ -860,6 +905,7 @@ Branch: REL_15_STABLE [62bed7bb0] 2025-02-20 10:43:40 +0900 Fix wrong checkpoint details in error message about incorrect recovery timeline choice (David Steele) + § @@ -881,6 +927,7 @@ Branch: REL_17_STABLE [ee578921b] 2025-03-18 13:44:10 -0400 Fix order of operations in smgropen() (Andres Freund) + § @@ -912,6 +959,7 @@ Branch: REL_15_STABLE [c1201ffcf] 2025-04-23 13:54:57 +0900 Remove incorrect assertion in pgstat_report_stat() (Michael Paquier) + § @@ -928,6 +976,7 @@ Branch: REL_13_STABLE [b92482dc3] 2025-04-04 13:50:52 +0300 Fix overly-strict assertion in gistFindCorrectParent() (Heikki Linnakangas) + § @@ -941,6 +990,7 @@ Branch: REL_17_STABLE [a38dce3c4] 2025-03-18 16:36:59 -0700 Avoid assertion failure in parallel vacuum when maintenance_work_mem has a very small value (Masahiko Sawada) + § @@ -956,6 +1006,7 @@ Branch: REL_14_STABLE [66235baab] 2025-03-23 20:41:59 +0200 Fix rare assertion failure in standby servers when the primary is restarted (Heikki Linnakangas) + § @@ -974,6 +1025,7 @@ Branch: REL_13_STABLE [0f60e1fba] 2025-03-21 11:30:42 -0400 when a scrollable cursor is defined on a simple SELECT expression query (Andrei Lepikhov) + § @@ -991,6 +1043,7 @@ Branch: REL_13_STABLE [6a3e57865] 2025-04-16 13:31:44 -0400 Don't try to drop individual index partitions in pg_dump's mode (Jian He) + § @@ -1018,6 +1071,7 @@ Branch: REL_13_STABLE [5302ff95c] 2025-02-21 13:37:12 -0500 role GRANT commands if pg_auth_members contains invalid role OIDs (Tom Lane) + § @@ -1050,6 +1104,9 @@ Branch: REL_14_STABLE [35a591a04] 2025-02-27 14:06:00 +0900 and pg_upgrade, use the correct function to free allocations made by libpq (Michael Paquier, Ranier Vilela) + § + § + § @@ -1069,6 +1126,7 @@ Branch: REL_17_STABLE [09ef2f8df] 2025-03-16 13:29:20 +0200 Fix reindexdb's scheduling of parallel reindex operations (Alexander Korotkov) + § @@ -1115,6 +1173,7 @@ Branch: REL_13_STABLE [3f9132ed2] 2025-04-19 16:37:43 -0400 Avoid crashing with corrupt input data in contrib/pageinspect's heap_page_items() (Dmitry Kovalenko) + § @@ -1132,6 +1191,7 @@ Branch: REL_13_STABLE [db8238da4] 2025-03-27 13:20:23 -0400 Prevent assertion failure in contrib/pg_freespacemap's pg_freespacemap() (Tender Wang) + § @@ -1151,6 +1211,7 @@ Branch: REL_17_STABLE [729fe699e] 2025-03-25 05:50:39 +0200 In contrib/postgres_fdw, avoid pulling up restriction conditions from subqueries (Alexander Pyhalov) + § @@ -1169,6 +1230,7 @@ Branch: REL_17_STABLE [f186f90e5] 2025-03-25 20:03:56 -0400 Fix build failure when an old version of libpq_fe.h is present in system include directories (Tom Lane) + § @@ -1184,6 +1246,7 @@ Branch: REL_13_STABLE [e4440a73c] 2025-04-01 16:49:51 -0400 --> Fix build failure on macOS 15.4 (Tom Lane, Peter Eisentraut) + § @@ -1202,6 +1265,7 @@ Branch: REL_17_STABLE [57dca6faa] 2025-02-21 15:16:37 +1300 Fix valgrind labeling of per-buffer data of read streams (Thomas Munro) + § @@ -1220,6 +1284,7 @@ Branch: REL_17_STABLE [fde7c0164] 2025-02-24 18:03:48 +0700 --> Avoid valgrind complaints about string hashing code (John Naylor) + § @@ -1237,6 +1302,7 @@ Branch: REL_13_STABLE [9da548df3] 2025-04-30 11:14:19 -0400 Update time zone data files to tzdata release 2025b for DST law changes in Chile, plus historical corrections for Iran (Tom Lane) + § From da08369688a14ad360af4b1e7a8bcf9cae0a0d40 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 2 May 2025 15:12:49 -0400 Subject: [PATCH 333/796] Doc: correct spelling of meson switch. It's --auto-features not --auto_features. Reported-by: Egor Chindyaskin Discussion: https://postgr.es/m/172465652540.862882.17808523044292761256@wrigleys.postgresql.org Discussion: https://postgr.es/m/1979661.1746212726@sss.pgh.pa.us Backpatch-through: 16 --- doc/src/sgml/installation.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 649b74aac1888..ba5c07685716a 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -2725,7 +2725,7 @@ ninja install - + Setting this option allows you to override the value of all From f72f099ad6dd45bfb79454bce0b84d8171c261b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 2 May 2025 21:25:50 +0200 Subject: [PATCH 334/796] Handle self-referencing FKs correctly in partitioned tables For self-referencing foreign keys in partitioned tables, we weren't handling creation of pg_constraint rows during CREATE TABLE PARTITION AS as well as ALTER TABLE ATTACH PARTITION. This is an old bug -- mostly, we broke this in 614a406b4ff1 while trying to fix it (so 12.13, 13.9, 14.6 and 15.0 and up all behave incorrectly). This commit reverts part of that with additional fixes for full correctness, and installs more tests to verify the parts we broke, not just the catalog contents but also the user-visible behavior. Backpatch to all live branches. In branches 13 and 14, commit 46a8c27a7226 changed the behavior during DETACH to drop a FK constraint rather than trying to repair it, because the complete fix of repairing catalog constraints was problematic due to lack of previous fixes. For this reason, the test behavior in those branches is a bit different. However, as best as I can tell, the fix works correctly there. In release notes we have to recommend that all self-referencing foreign keys on partitioned tables be recreated if partitions have been created or attached after the FK was created, keeping in mind that violating rows might already be present on the referencing side. Reported-by: Guillaume Lelarge Reported-by: Matthew Gabeler-Lee Reported-by: Luca Vallisa Discussion: https://postgr.es/m/CAECtzeWHCA+6tTcm2Oh2+g7fURUJpLZb-=pRXgeWJ-Pi+VU=_w@mail.gmail.com Discussion: https://postgr.es/m/18156-a44bc7096f0683e6@postgresql.org Discussion: https://postgr.es/m/CAAT=myvsiF-Attja5DcWoUWh21R12R-sfXECY2-3ynt8kaOqjw@mail.gmail.com --- src/backend/commands/tablecmds.c | 21 +---- src/test/regress/expected/foreign_key.out | 104 ++++++++++++++-------- src/test/regress/expected/triggers.out | 8 +- src/test/regress/sql/foreign_key.sql | 35 ++++++-- 4 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 4846f6a8eaf98..b1090497eb401 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -10608,14 +10608,14 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel, Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); /* - * Clone constraints for which the parent is on the referenced side. + * First, clone constraints where the parent is on the referencing side. */ - CloneFkReferenced(parentRel, partitionRel); + CloneFkReferencing(wqueue, parentRel, partitionRel); /* - * Now clone constraints where the parent is on the referencing side. + * Clone constraints for which the parent is on the referenced side. */ - CloneFkReferencing(wqueue, parentRel, partitionRel); + CloneFkReferenced(parentRel, partitionRel); } /* @@ -10626,8 +10626,6 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel, * clone those constraints to the given partition. This is to be called * when the partition is being created or attached. * - * This ignores self-referencing FKs; those are handled by CloneFkReferencing. - * * This recurses to partitions, if the relation being attached is partitioned. * Recursion is done by calling addFkRecurseReferenced. */ @@ -10718,17 +10716,6 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) continue; } - /* - * Don't clone self-referencing foreign keys, which can be in the - * partitioned table or in the partition-to-be. - */ - if (constrForm->conrelid == RelationGetRelid(parentRel) || - constrForm->conrelid == RelationGetRelid(partitionRel)) - { - ReleaseSysCache(tuple); - continue; - } - /* We need the same lock level that CreateTrigger will acquire */ fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock); diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index eac9af9a858ba..8cbad245217ad 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -2042,58 +2042,90 @@ CREATE TABLE part33_self_fk ( id_abc bigint ); ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); -SELECT cr.relname, co.conname, co.contype, co.convalidated, +-- verify that this constraint works +INSERT INTO parted_self_fk VALUES (1, NULL), (2, NULL), (3, NULL); +INSERT INTO parted_self_fk VALUES (10, 1), (11, 2), (12, 3) RETURNING tableoid::regclass; + tableoid +--------------- + part2_self_fk + part2_self_fk + part2_self_fk +(3 rows) + +INSERT INTO parted_self_fk VALUES (4, 5); -- error: referenced doesn't exist +ERROR: insert or update on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey" +DETAIL: Key (id_abc)=(5) is not present in table "parted_self_fk". +DELETE FROM parted_self_fk WHERE id = 1 RETURNING *; -- error: reference remains +ERROR: update or delete on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey1" on table "parted_self_fk" +DETAIL: Key (id)=(1) is still referenced from table "parted_self_fk". +SELECT cr.relname, co.conname, co.convalidated, p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFT JOIN pg_class cf ON cf.oid = co.confrelid LEFT JOIN pg_constraint p ON p.oid = co.conparentid -WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) -ORDER BY co.contype, cr.relname, co.conname, p.conname; - relname | conname | contype | convalidated | conparent | convalidated | foreignrel -----------------+----------------------------+---------+--------------+----------------------------+--------------+---------------- - part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part32_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk - part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t | - part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t | - part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t | - part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t | - part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t | - parted_self_fk | parted_self_fk_pkey | p | t | | | -(12 rows) +WHERE co.contype = 'f' AND + cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY cr.relname, co.conname, p.conname; + relname | conname | convalidated | conparent | convalidated | foreignrel +----------------+-----------------------------+--------------+-----------------------------+--------------+---------------- + part1_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part2_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part32_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part33_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part3_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey | t | | | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey1 | t | parted_self_fk_id_abc_fkey | t | part1_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey2 | t | parted_self_fk_id_abc_fkey | t | part2_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey3 | t | parted_self_fk_id_abc_fkey | t | part3_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey4 | t | parted_self_fk_id_abc_fkey3 | t | part32_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey5 | t | parted_self_fk_id_abc_fkey3 | t | part33_self_fk +(11 rows) -- detach and re-attach multiple times just to ensure everything is kosher ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; +INSERT INTO part2_self_fk VALUES (16, 9); -- error: referenced doesn't exist +ERROR: insert or update on table "part2_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey" +DETAIL: Key (id_abc)=(9) is not present in table "parted_self_fk". +DELETE FROM parted_self_fk WHERE id = 2 RETURNING *; -- error: reference remains +ERROR: update or delete on table "part1_self_fk" violates foreign key constraint "part2_self_fk_id_abc_fkey" on table "part2_self_fk" +DETAIL: Key (id)=(2) is still referenced from table "part2_self_fk". ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); +INSERT INTO parted_self_fk VALUES (16, 9); -- error: referenced doesn't exist +ERROR: insert or update on table "part2_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey" +DETAIL: Key (id_abc)=(9) is not present in table "parted_self_fk". +DELETE FROM parted_self_fk WHERE id = 3 RETURNING *; -- error: reference remains +ERROR: update or delete on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey1" on table "parted_self_fk" +DETAIL: Key (id)=(3) is still referenced from table "parted_self_fk". ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); -SELECT cr.relname, co.conname, co.contype, co.convalidated, +ALTER TABLE parted_self_fk DETACH PARTITION part3_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (30) TO (40); +ALTER TABLE part3_self_fk DETACH PARTITION part33_self_fk; +ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); +SELECT cr.relname, co.conname, co.convalidated, p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFT JOIN pg_class cf ON cf.oid = co.confrelid LEFT JOIN pg_constraint p ON p.oid = co.conparentid -WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) -ORDER BY co.contype, cr.relname, co.conname, p.conname; - relname | conname | contype | convalidated | conparent | convalidated | foreignrel -----------------+----------------------------+---------+--------------+----------------------------+--------------+---------------- - part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part32_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk - parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk - part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t | - part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t | - part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t | - part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t | - part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t | - parted_self_fk | parted_self_fk_pkey | p | t | | | -(12 rows) +WHERE co.contype = 'f' AND + cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY cr.relname, co.conname, p.conname; + relname | conname | convalidated | conparent | convalidated | foreignrel +----------------+-----------------------------+--------------+-----------------------------+--------------+---------------- + part1_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part2_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part32_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part33_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part3_self_fk | parted_self_fk_id_abc_fkey | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey | t | | | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey1 | t | parted_self_fk_id_abc_fkey | t | part1_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey2 | t | parted_self_fk_id_abc_fkey | t | part2_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey3 | t | parted_self_fk_id_abc_fkey | t | part3_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey4 | t | parted_self_fk_id_abc_fkey3 | t | part32_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey5 | t | parted_self_fk_id_abc_fkey3 | t | part33_self_fk +(11 rows) -- Leave this table around, for pg_upgrade/pg_dump tests -- Test creating a constraint at the parent that already exists in partitions. diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index ada0ae743a36a..b7f0c3027e016 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -2796,11 +2796,13 @@ select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, ---------+-------------------------+------------------------+----------- child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O + child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O + child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O -(6 rows) +(8 rows) alter table parent disable trigger all; select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, @@ -2811,11 +2813,13 @@ select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, ---------+-------------------------+------------------------+----------- child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D + child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D + child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D -(6 rows) +(8 rows) drop table parent, child1; -- Verify that firing state propagates correctly on creation, too diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 2d7584b49fe47..ea08fd5a6f6a7 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1490,29 +1490,52 @@ CREATE TABLE part33_self_fk ( ); ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); -SELECT cr.relname, co.conname, co.contype, co.convalidated, +-- verify that this constraint works +INSERT INTO parted_self_fk VALUES (1, NULL), (2, NULL), (3, NULL); +INSERT INTO parted_self_fk VALUES (10, 1), (11, 2), (12, 3) RETURNING tableoid::regclass; + +INSERT INTO parted_self_fk VALUES (4, 5); -- error: referenced doesn't exist +DELETE FROM parted_self_fk WHERE id = 1 RETURNING *; -- error: reference remains + +SELECT cr.relname, co.conname, co.convalidated, p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFT JOIN pg_class cf ON cf.oid = co.confrelid LEFT JOIN pg_constraint p ON p.oid = co.conparentid -WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) -ORDER BY co.contype, cr.relname, co.conname, p.conname; +WHERE co.contype = 'f' AND + cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY cr.relname, co.conname, p.conname; -- detach and re-attach multiple times just to ensure everything is kosher ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; + +INSERT INTO part2_self_fk VALUES (16, 9); -- error: referenced doesn't exist +DELETE FROM parted_self_fk WHERE id = 2 RETURNING *; -- error: reference remains + ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); + +INSERT INTO parted_self_fk VALUES (16, 9); -- error: referenced doesn't exist +DELETE FROM parted_self_fk WHERE id = 3 RETURNING *; -- error: reference remains + ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); -SELECT cr.relname, co.conname, co.contype, co.convalidated, +ALTER TABLE parted_self_fk DETACH PARTITION part3_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (30) TO (40); + +ALTER TABLE part3_self_fk DETACH PARTITION part33_self_fk; +ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); + +SELECT cr.relname, co.conname, co.convalidated, p.conname AS conparent, p.convalidated, cf.relname AS foreignrel FROM pg_constraint co JOIN pg_class cr ON cr.oid = co.conrelid LEFT JOIN pg_class cf ON cf.oid = co.confrelid LEFT JOIN pg_constraint p ON p.oid = co.conparentid -WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) -ORDER BY co.contype, cr.relname, co.conname, p.conname; +WHERE co.contype = 'f' AND + cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY cr.relname, co.conname, p.conname; -- Leave this table around, for pg_upgrade/pg_dump tests From 8d2f8581a953a03d22ece1a3851bc6c085b22229 Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Sat, 3 May 2025 19:10:01 +0900 Subject: [PATCH 335/796] Fix typos in comments. Also adjust the phrasing in the comments. Author: Etsuro Fujita Author: Heikki Linnakangas Reviewed-by: Tender Wang Reviewed-by: Gurjeet Singh Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CAPmGK17%3DPHSDZ%2B0G6jcj12buyyE1bQQc3sbp1Wxri7tODT-SDw%40mail.gmail.com Backpatch-through: 15 --- src/backend/utils/activity/pgstat_database.c | 4 ++-- src/backend/utils/activity/pgstat_function.c | 4 ++-- src/backend/utils/activity/pgstat_relation.c | 4 ++-- src/backend/utils/activity/pgstat_subscription.c | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c index 29bc090974803..10ee3e57a89a3 100644 --- a/src/backend/utils/activity/pgstat_database.c +++ b/src/backend/utils/activity/pgstat_database.c @@ -368,8 +368,8 @@ pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) /* * Flush out pending stats for the entry * - * If nowait is true, this function returns false if lock could not - * immediately acquired, otherwise true is returned. + * If nowait is true and the lock could not be immediately acquired, returns + * false without flushing the entry. Otherwise returns true. */ bool pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) diff --git a/src/backend/utils/activity/pgstat_function.c b/src/backend/utils/activity/pgstat_function.c index d26da551a4e6c..709b8e6726e75 100644 --- a/src/backend/utils/activity/pgstat_function.c +++ b/src/backend/utils/activity/pgstat_function.c @@ -186,8 +186,8 @@ pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize) /* * Flush out pending stats for the entry * - * If nowait is true, this function returns false if lock could not - * immediately acquired, otherwise true is returned. + * If nowait is true and the lock could not be immediately acquired, returns + * false without flushing the entry. Otherwise returns true. */ bool pgstat_function_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index 8a3f7d434cfa9..383b2dbe3e895 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -792,8 +792,8 @@ pgstat_twophase_postabort(TransactionId xid, uint16 info, /* * Flush out pending stats for the entry * - * If nowait is true, this function returns false if lock could not - * immediately acquired, otherwise true is returned. + * If nowait is true and the lock could not be immediately acquired, returns + * false without flushing the entry. Otherwise returns true. * * Some of the stats are copied to the corresponding pending database stats * entry when successfully flushing. diff --git a/src/backend/utils/activity/pgstat_subscription.c b/src/backend/utils/activity/pgstat_subscription.c index d9af8de6587e8..fdfe92ad95874 100644 --- a/src/backend/utils/activity/pgstat_subscription.c +++ b/src/backend/utils/activity/pgstat_subscription.c @@ -81,8 +81,8 @@ pgstat_fetch_stat_subscription(Oid subid) /* * Flush out pending stats for the entry * - * If nowait is true, this function returns false if lock could not - * immediately acquired, otherwise true is returned. + * If nowait is true and the lock could not be immediately acquired, returns + * false without flushing the entry. Otherwise returns true. */ bool pgstat_subscription_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) From 0b059ab52ae1a2ca30c6feda4a8fda401744fb5c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 4 May 2025 13:52:59 -0400 Subject: [PATCH 336/796] Release notes for 17.5, 16.9, 15.13, 14.18, 13.21. --- doc/src/sgml/release-17.sgml | 114 ++++++++++++++--------------------- 1 file changed, 44 insertions(+), 70 deletions(-) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index cf6d678a8d433..fdb5205a2bff0 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -23,8 +23,15 @@ - However, if you have any BRIN bloom indexes, it may be advisable to - reindex them after updating. See the first changelog entry below. + However, if you have any self-referential foreign key constraints on + partitioned tables, it may be necessary to recreate those constraints + to ensure that they are being enforced correctly. See the first + changelog entry below. + + + + Also, if you have any BRIN bloom indexes, it may be advisable to + reindex them after updating. See the second changelog entry below. @@ -40,6 +47,39 @@ + + Handle self-referential foreign keys on partitioned tables correctly + (Álvaro Herrera) + § + + + + Creating or attaching partitions failed to make the required catalog + entries for a foreign-key constraint, if the table referenced by the + constraint was the same partitioned table. This resulted in failure + to enforce the constraint fully. + + + + To fix this, you should drop and recreate any self-referential + foreign keys on partitioned tables, if partitions have been created + or attached since the constraint was created. Bear in mind that + violating rows might already be present, in which case recreating + the constraint will fail, and you'll need to fix up those rows + before trying again. + + + + + Fix ALTER TABLE ADD COLUMN to correctly handle - the case of a domain type that has a default (Tom Lane, Tender Wang) + the case of a domain type that has a default + (Jian He, Tom Lane, Tender Wang) § § @@ -694,38 +735,6 @@ Branch: REL_17_STABLE [727bc6ac3] 2025-02-19 10:04:44 +0900 - - Skip WAL recycling and preallocation during archive recovery, to - avoid corruption of WAL files that were restored from the archive - (Noah Misch, Arun Thirupathi) - - - - This change back-patches v15-era fixes that were considered largely - cosmetic at the time, but turn out to prevent data corruption in the - wake of subsequent fixes. - - - - - - - Fix assertion failure in snapshot building (Masahiko Sawada) - - - - - - - Allow contrib/dblink queries to be interrupted - by query cancel (Noah Misch) - - - - This change back-patches a v17-era fix. It prevents possible hangs - in CREATE DATABASE and DROP - DATABASE due to failure to detect deadlocks. - - - - - + + Avoid one-byte buffer overread when examining invalidly-encoded + strings that are claimed to be in GB18030 encoding + (Noah Misch, Andres Freund) + § + § + + + + While unlikely, a SIGSEGV crash could occur if an incomplete + multibyte character appeared at the end of memory. This was + possible both in the server and + in libpq-using applications. + (CVE-2025-4207) + + + + + - -# pg_tde Release Candidate ({{date.RC2}}) +# pg_tde Release Candidate 2 ({{date.RC2}}) `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. -[Get started](../install.md){.md-button} +[Get Started](../install.md){.md-button} ## Release Highlights This release provides the following features and improvements: -* **Improved performance with redesigned WAL encryption**. +* **Restricted key provider configuration to superusers** + + The database owners can no longer configure key providers directly. Instead, they must refer to the superuser who manages the provider setup. This security improvement clearly separates the responsibilities between users and administrators. - The approach to WAL encryption has been redesigned. Now, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. The information about what is encrypted is stored in the internal key metadata. This change improves WAL encryption flow with native replication and increases performance for large scale databases. +* **WAL encryption supports Vault**. -* **Default encryption key for single-tenancy**. + `pg_tde` now supports using the Vault keyring for secure storage and management of WAL encryption keys. - The new functionality allows you to set a default principal key for the entire database cluster. This key is used to encrypt all databases and tables that do not have a custom principal key set. This feature simplifies encryption configuration and management in single-tenant environments where each user has their own database instance. +* **Automatic WAL internal key generation at server startup**. -* **Ability to change key provider configuration** + On each server start, a new internal key is generated for encrypting subsequent WAL records (assuming WAL encryption is enabled). The existing WAL records and their keys remain unchanged, this ensures continuity and secure key management without affecting historical data. - You no longer need to configure a new key provider and set a new principal key if the provider's configuration changed. Now can change the key provider configuration both for the current database and the entire PostgreSQL cluster using [functions](../functions.md#key-provider-management). This enhancement lifts existing limitations and is a native and common way to operate in PostgreSQL. +* **Proper removal of relation-level encryption keys on table drop** -* **Key management permissions** + Previously, encrypted relation keys persisted even after dropping the associated tables, potentially leaving orphaned entries in the map file. This is now corrected, when an encrypted table is dropped, its corresponding key is also removed from the key map. - The new functions allow you to manage permissions for global and database key management separately. This feature provides more granular control over key management operations and allows you to delegate key management tasks to different roles. +* **Fixed external tablespace data loss with encrypted partitions** -* **Additional information about principal keys and providers** + An issue was fixed where data could be lost when the encrypted partitioned tables were moved to external tablespaces. - The new functions allow you to display additional information about principal keys and providers. This feature helps you to understand the current key configuration and troubleshoot issues related to key management. +* **New visibility and verification functions for default principal keys** -* **`tde_heap_basic` access method deprecation** + Added additional functions to help you verify and inspect the state of default principal keys more easily. - The `tde_heap_basic` access method has limitations in encryption capabilities and affects performance. Also, it poses a potential security risk when used in production environments due to indexes remaining unencrypted. Considering all the above, we decided to deprecate this access method and remove it in future releases. Use the `tde_heap` access method instead that is available with Percona Server for PostgreSQL 17 - a drop-in replacement for PostgreSQL Community. +* **Fixed SQL failures caused by inconsistent key provider switching** + + An issue was resolved where SQL queries could fail after switching key providers while the server was running. + This occurred because principal keys became inaccessible when spread across multiple keyring backends, triggering the single-provider-at-a-time design constraint. + `pg_tde` now enforces consistency during provider changes to prevent a corrupted key state and query errors. ## Upgrade considerations -`pg_tde` Release Candidate is not backward compatible with `pg_tde` Beta2 due to significant changes in code. This means you cannot directly upgrade from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. +`pg_tde` Release Candidate 2 is not backward compatible with `pg_tde` Beta2 due to significant changes in code. This means you cannot directly upgrade from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. ## Known issues * The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. - To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: - - * temporarily for the current session using the `ulimit -l ` command. - * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. +To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: - Adjust the limits with caution since it affects other processes running in your system. +* temporarily for the current session using the `ulimit -l ` command. +* set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. -* You can now delete global key providers even when their associated principal key is still in use. This known issue will be fixed in the next release. For now, avoid deleting global key providers. +Adjust the limits with caution since it affects other processes running in your system. ## Changelog ### New Features -* [PG-1234](https://perconadev.atlassian.net/browse/PG-1234) - Added functions for separate global and database key management permissions. - -* [PG-1255](https://perconadev.atlassian.net/browse/PG-1255) - Added functionality to delete key providers. - -* [PG-1256](https://perconadev.atlassian.net/browse/PG-1256) - Added single-tenant support via the default principal key functionality. - -* [PG-1258](https://perconadev.atlassian.net/browse/PG-1258) - Added functions to display additional information about principal keys / providers. - -* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) - Redesigned WAL encryption. - -* [PG-1303](https://perconadev.atlassian.net/browse/PG-1303) - Deprecated tde_heap_basic access method. +* [PG-813](https://perconadev.atlassian.net/browse/PG-813) – Enabled support for logical replication in `pg_tde`. +* [PG-817](https://perconadev.atlassian.net/browse/PG-817) – Added fuzz testing to `pstress` to strengthen validation and resilience. +* [PG-824](https://perconadev.atlassian.net/browse/PG-824) – Ensured fsync is called on `pg_tde.map`, `pg_tde.dat`, and FS key provider files. +* [PG-830](https://perconadev.atlassian.net/browse/PG-830) – Implemented full WAL encryption using Vault keyring. +* [PG-831](https://perconadev.atlassian.net/browse/PG-831) – Tested WAL recovery and both streaming and logical replication compatibility. +* [PG-855](https://perconadev.atlassian.net/browse/PG-855) – Added a contributor guide to help new developers engage with pg_tde. +* [PG-938](https://perconadev.atlassian.net/browse/PG-938) – Evaluated use of `pg_basebackup` for automated backup validation with pg_tde. +* [PG-962](https://perconadev.atlassian.net/browse/PG-962) – Automated test cases to validate data integrity after PostgreSQL restart. +* [PG-1001](https://perconadev.atlassian.net/browse/PG-1001) – Verified encryption behavior of temporary tables. +* [PG-1099](https://perconadev.atlassian.net/browse/PG-1099) – Developed automation for bare-metal performance benchmarking. +* [PG-1232](https://perconadev.atlassian.net/browse/PG-1232) – Documented the architectural design behind pg_tde. +* [PG-1289](https://perconadev.atlassian.net/browse/PG-1289) – Added test cases for verifying compatibility with different PostgreSQL versions. +* [PG-1295](https://perconadev.atlassian.net/browse/PG-1295) – Introduced TAP test cases to validate WAL encryption across access methods. +* [PG-1444](https://perconadev.atlassian.net/browse/PG-1444) – Implemented support for removing relation-level encryption keys when dropping tables. +* [PG-1455](https://perconadev.atlassian.net/browse/PG-1455) – Introduced random base numbers in encryption IVs for enhanced security. +* [PG-1458](https://perconadev.atlassian.net/browse/PG-1458) – Added visibility and verification functions for default principal keys. +* [PG-1460](https://perconadev.atlassian.net/browse/PG-1460) – Enabled automatic rotation of WAL internal keys on server start. +* [PG-1461](https://perconadev.atlassian.net/browse/PG-1461) – Implemented random IV initialization for WAL keys. +* [PG-1506](https://perconadev.atlassian.net/browse/PG-1506) – Added parameter support for client certificates in KMIP provider configuration. ## Improvements -* [PG-858](https://perconadev.atlassian.net/browse/PG-858) - Refactored internal/principal key LWLocks to make local databases inherit a global key provider. - -* [PG-1243](https://perconadev.atlassian.net/browse/PG-1243) - Investigated performance issues at a specific threshold and large databases and updated documentation about handling hint bits. - -* [PG-1310](https://perconadev.atlassian.net/browse/PG-1310) - Added access method enforcement via the GUC variable. - -* [PG-1361](https://perconadev.atlassian.net/browse/PG-1361) - Fixed pg_tde relocatability. - -* [PG-1380](https://perconadev.atlassian.net/browse/PG-1380) - Added support for `pg_tde_is_encrypted()` function on indexes and sequences. +* [PG-826](https://perconadev.atlassian.net/browse/PG-826) – Documented how to encrypt and decrypt existing tables using pg_tde. +* [PG-827](https://perconadev.atlassian.net/browse/PG-827) – Fixed CI pipeline tests on the smgr branch. +* [PG-834](https://perconadev.atlassian.net/browse/PG-834) – Resolved issues with `CREATE ... USING pg_tde` on the smgr branch. +* [PG-838](https://perconadev.atlassian.net/browse/PG-838) – Added documentation for setting up streaming replication with pg_tde. +* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) – Improved WAL encryption design and performance. +* [PG-1304](https://perconadev.atlassian.net/browse/PG-1304) – Removed deprecated `tde_heap_basic` access method before GA. +* [PG-1307](https://perconadev.atlassian.net/browse/PG-1307) – Assessed overall development effort required for WAL encryption. +* [PG-1392](https://perconadev.atlassian.net/browse/PG-1392) – Investigated encryption coverage gaps in freespace and visibility maps. +* [PG-1419](https://perconadev.atlassian.net/browse/PG-1419) – Changed provider validation to occur during provider addition instead of key setup. +* [PG-1427](https://perconadev.atlassian.net/browse/PG-1427) – Tested and fixed KMIP implementation for Thales support. +* [PG-1437](https://perconadev.atlassian.net/browse/PG-1437) – Cleaned up and resolved TODO items in the codebase. +* [PG-1440](https://perconadev.atlassian.net/browse/PG-1440) – Restricted key provider configuration to superusers for improved security. +* [PG-1441](https://perconadev.atlassian.net/browse/PG-1441) – Prevented replication of table-level encryption keys. +* [PG-1446](https://perconadev.atlassian.net/browse/PG-1446) – Improved locking mechanisms in pg_tde. +* [PG-1447](https://perconadev.atlassian.net/browse/PG-1447) – Verified compatibility of encryption with template databases. +* [PG-1457](https://perconadev.atlassian.net/browse/PG-1457) – Renamed key management functions for clarity and consistency. +* [PG-1467](https://perconadev.atlassian.net/browse/PG-1467) – Added Clang-based CI integration on GitHub. +* [PG-1507](https://perconadev.atlassian.net/browse/PG-1507) – Handled ALTER TYPE operations in the TDE event trigger. +* [PG-1508](https://perconadev.atlassian.net/browse/PG-1508) – Fixed encryption state inconsistencies when altering inherited tables. +* [PG-1550](https://perconadev.atlassian.net/browse/PG-1550) – Restricted database owners from creating key providers to improve security. +* [PG-1586](https://perconadev.atlassian.net/browse/PG-1586) – Verified and fixed KMIP compatibility with Fortanix HSM. ### Bugs Fixed -* [PG-821](https://perconadev.atlassian.net/browse/PG-821) - Fixed the issue with `pg_basebackup` failing when configuring replication. - -* [PG-847](https://perconadev.atlassian.net/browse/PG-847) - Fixed the issue with `pg_basebackup` and `pg_checksum` throwing an error on files created by `pg_tde` when the checksum is enabled on the database cluster. - -* [PG-1004](https://perconadev.atlassian.net/browse/PG-1004) - Fixed the issue with `pg_checksums` utility failing during checksum verification on `pg_tde` tables. Now `pg_checksum` skips encrypted relations by looking if the relation has a custom storage manager (SMGR) key. - -* [PG-1373](https://perconadev.atlassian.net/browse/PG-1373) - Fixed the issue with potential unterminated strings by using the `memcpy()` or `strlcpy()` instead of the `strncpy()` function. - -* [PG-1378](https://perconadev.atlassian.net/browse/PG-1378) - Fixed the issue with toast tables created by the `ALTER TABLE` command not being encrypted. - -* [PG-1379](https://perconadev.atlassian.net/browse/PG-1379) - Fixed sequence and alter table handling in the event trigger. - -* [PG-1222](https://perconadev.atlassian.net/browse/PG-1222) - Fixed the bug with confused relations with the same `RelFileNumber` in different databases. - -* [PG-1400](https://perconadev.atlassian.net/browse/PG-1400) - Corrected the pg_tde_change_key_provider naming in help. - -* [PG-1401](https://perconadev.atlassian.net/browse/PG-1401) - Fixed the issue with inheriting an encryption status during the ALTER TABLE SET access method command execution by basing a new encryption status only on the new encryption setting. - -* [PG-1414](https://perconadev.atlassian.net/browse/PG-1414) - Fixed the error message wording when configuring WAL encryption by referencing to a correct function. - -* [PG-1450](https://perconadev.atlassian.net/browse/PG-1450) - Fixed the `pg_tde_delete_key_provider()` function behavior when called multiple times by ignoring already deleted records. - -* [PG-1451](https://perconadev.atlassian.net/browse/PG-1451) -Fixed the issue with the repeating error message about inability to retrieve a principal key even when a user creates non-encrypted tables by checking the current transaction ID in both the event trigger start function and during a file creation. If the transaction changed during the setup of the current event trigger data, the event trigger is reset. - -* [PG-1473](https://perconadev.atlassian.net/browse/PG-1473) - Allowed only users with key viewer privileges to execute `pg_tde_verify_principal_key()` and `pg_tde_verify_global_principal_key()` functions. - -* [PG-1474](https://perconadev.atlassian.net/browse/PG-1474) - Fixed the issue with the principal key reference corruption when reassigning it to a key provider with the same name by setting the key name in vault/kmip getters. - -* [PG-1476](https://perconadev.atlassian.net/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. - -* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. - -* [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. -/browse/PG-1476) - Fixed the issue with the server failing to start when WAL encryption is enabled by creating a new principal key for WAL in case only one default key exists in the database. - -* [PG-1479](https://perconadev.atlassian.net/browse/PG-1479), [PG-1480](https://perconadev.atlassian.net/browse/PG-1480) - Fixed the issue with the lost access to data after the global key provider change and the server restart by fixing the incorrect parameter order in default key rotation. - -* [PG-1489](https://perconadev.atlassian.net/browse/PG-1489) - Fixed the issue with replicating the keys and key provider configuration by creating the `pg_tde` directory on the replica server. +* [PG-1397](https://perconadev.atlassian.net/browse/PG-1397) – Fixed segmentation fault during replication with WAL encryption enabled. +* [PG-1413](https://perconadev.atlassian.net/browse/PG-1413) – Resolved invalid WAL magic number errors after toggling encryption. +* [PG-1416](https://perconadev.atlassian.net/browse/PG-1416) – Fixed SQL query failures caused by inconsistent key provider switching. +* [PG-1468](https://perconadev.atlassian.net/browse/PG-1468) – Fixed WAL read failures on replicas after key rotation. +* [PG-1491](https://perconadev.atlassian.net/browse/PG-1491) – Corrected `pg_tde_is_encrypted()` behavior for partitioned tables. +* [PG-1493](https://perconadev.atlassian.net/browse/PG-1493) – Fixed data loss when encrypted partitioned tables were moved to external tablespaces. +* [PG-1503](https://perconadev.atlassian.net/browse/PG-1503) – Blocked deletion of global key providers still associated with principal keys. +* [PG-1504](https://perconadev.atlassian.net/browse/PG-1504) – Ensured correct encryption inheritance in partitioned `tde_heap` tables. +* [PG-1510](https://perconadev.atlassian.net/browse/PG-1510) – Used different keys and IVs for PostgreSQL forks to prevent conflicts. +* [PG-1530](https://perconadev.atlassian.net/browse/PG-1530) – Fixed inability to read WAL after toggling WAL encryption. +* [PG-1532](https://perconadev.atlassian.net/browse/PG-1532) – Resolved errors rewriting owned sequences when pg_tde isn't in the default schema. +* [PG-1535](https://perconadev.atlassian.net/browse/PG-1535) – Prevented server crash on calling `pg_tde_principal_key_info()`. +* [PG-1537](https://perconadev.atlassian.net/browse/PG-1537) – Fixed crash on NULL input in user-facing functions. +* [PG-1539](https://perconadev.atlassian.net/browse/PG-1539) – Handled principal key header verification errors gracefully. +* [PG-1540](https://perconadev.atlassian.net/browse/PG-1540) – Ensured sequences are assigned correct encryption status. +* [PG-1541](https://perconadev.atlassian.net/browse/PG-1541) – Resolved WAL decryption failure after key rotation. +* [PG-1543](https://perconadev.atlassian.net/browse/PG-1543) – Fixed validation error when multiple server keys exist. +* [PG-1545](https://perconadev.atlassian.net/browse/PG-1545) – Resolved error from `pg_tde_grant_grant_management_to_role()` execution. +* [PG-1546](https://perconadev.atlassian.net/browse/PG-1546) – Fixed incorrect behavior in role grant function. +* [PG-1551](https://perconadev.atlassian.net/browse/PG-1551) – Improved handling of short reads and errors in WAL storage code. +* [PG-1571](https://perconadev.atlassian.net/browse/PG-1571) – Fixed WAL decryption failure due to corrupted or mismatched principal keys. +* [PG-1573](https://perconadev.atlassian.net/browse/PG-1573) – Prevented crash during WAL replay when lock was not held. +* [PG-1574](https://perconadev.atlassian.net/browse/PG-1574) – Ensured encrypted WAL is readable by streaming replica. +* [PG-1576](https://perconadev.atlassian.net/browse/PG-1576) – Resolved crash from malformed JSON in user-facing functions. diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index e383f05c3aab9..47e4bcc9ec751 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -2,6 +2,7 @@ `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +* [pg_tde Release Candidate 2 {{date.RC}}](rc2.md) * [pg_tde Release Candidate {{date.RC}}](rc.md) * [pg_tde Beta2 (2024-12-16)](beta2.md) * [pg_tde Beta (2024-06-30)](beta.md) diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 9a3027a979880..f0a31fda328e8 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -116,16 +116,16 @@ plugins: - macros: include_yaml: - 'variables.yml' # Use in markdown as '{{ VAR }}' - #- with-pdf: # https://github.com/orzih/mkdocs-with-pdf - # output_path: '_pdf/PerconaTDE.pdf' - # cover_title: 'Percona Transparent Data Encryption' - # cover_subtitle: Release Candidate (2025-03-27) - # author: 'Percona Technical Documentation Team' - # cover_logo: docs/_images/Percona_Logo_Color.png - # debug_html: false -# two_columns_level: 3 -# custom_template_path: _resource/templates -# enabled_if_env: ENABLE_PDF_EXPORT + - with-pdf: # https://github.com/orzih/mkdocs-with-pdf + output_path: '_pdf/PerconaTDE.pdf' + cover_title: 'Percona Transparent Data Encryption' + cover_subtitle: Release Candidate (2025-03-27) + author: 'Percona Technical Documentation Team' + cover_logo: docs/_images/Percona_Logo_Color.png + debug_html: false + two_columns_level: 3 + custom_template_path: _resource/templates + enabled_if_env: ENABLE_PDF_EXPORT extra: version: @@ -193,6 +193,7 @@ nav: - faq.md - "Release Notes": - "pg_tde release notes": release-notes/release-notes.md + - release-notes/rc2.md - release-notes/rc.md - release-notes/beta2.md - release-notes/beta.md diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index a7e36ebfc24a0..365412e50b1a3 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -1,9 +1,9 @@ #Variables used throughout the docs -release: 'RC' -pgversion17: '17.4' +release: 'RC2' +pgversion17: '17.5.1' tdebranch: TDE_REL_17_STABLE date: - RC2: + RC2: '2025-05-21' RC: '2025-03-27' From 506606f59d40398e72b344a351015310b6e33a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 16 May 2025 08:57:07 +0200 Subject: [PATCH 364/796] Rename TDESMgrRelationData to TDESMgrRelation And also remove the typedef that previously held the name TDESMgrRelation. This typedef hides the pointer-ness of things and only serves to make the code harder to read. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 4af256c668a87..40c535537227d 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -7,7 +7,7 @@ #include "access/pg_tde_tdemap.h" #include "pg_tde_event_capture.h" -typedef enum TDEMgrRelationDataEncryptionStatus +typedef enum TDEMgrRelationEncryptionStatus { /* This is a plaintext relation */ RELATION_NOT_ENCRYPTED = 0, @@ -17,17 +17,17 @@ typedef enum TDEMgrRelationDataEncryptionStatus /* This is an encrypted relation, but we haven't loaded the key yet. */ RELATION_KEY_NOT_AVAILABLE = 2, -} TDEMgrRelationDataEncryptionStatus; +} TDEMgrRelationEncryptionStatus; /* - * TDESMgrRelationData is an extended copy of MDSMgrRelationData in md.c + * TDESMgrRelation is an extended copy of MDSMgrRelationData in md.c * * The first fields of this struct must always exactly match * MDSMgrRelationData since we will pass this structure to the md.c functions. * * Any fields specific to the tde smgr must be placed after these fields. */ -typedef struct TDESMgrRelationData +typedef struct TDESMgrRelation { /* parent data */ SMgrRelationData reln; @@ -39,11 +39,9 @@ typedef struct TDESMgrRelationData int md_num_open_segs[MAX_FORKNUM + 1]; struct _MdfdVec *md_seg_fds[MAX_FORKNUM + 1]; - TDEMgrRelationDataEncryptionStatus encryption_status; + TDEMgrRelationEncryptionStatus encryption_status; InternalKey relKey; -} TDESMgrRelationData; - -typedef TDESMgrRelationData *TDESMgrRelation; +} TDESMgrRelation; static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); @@ -100,7 +98,7 @@ static void tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void **buffers, BlockNumber nblocks, bool skipFsync) { - TDESMgrRelation tdereln = (TDESMgrRelation) reln; + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; if (tdereln->encryption_status == RELATION_NOT_ENCRYPTED) { @@ -172,7 +170,7 @@ static void tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync) { - TDESMgrRelation tdereln = (TDESMgrRelation) reln; + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; if (tdereln->encryption_status == RELATION_NOT_ENCRYPTED) { @@ -207,7 +205,7 @@ static void tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, void **buffers, BlockNumber nblocks) { - TDESMgrRelation tdereln = (TDESMgrRelation) reln; + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; InternalKey *int_key; mdreadv(reln, forknum, blocknum, buffers, nblocks); @@ -257,7 +255,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, static void tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool isRedo) { - TDESMgrRelation tdereln = (TDESMgrRelation) reln; + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; /* Copied from mdcreate() in md.c */ if (isRedo && tdereln->md_num_open_segs[forknum] > 0) @@ -312,7 +310,7 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool static void tde_mdopen(SMgrRelation reln) { - TDESMgrRelation tdereln = (TDESMgrRelation) reln; + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; mdopen(reln); @@ -352,7 +350,7 @@ RegisterStorageMgr(void) { if (storage_manager_id != MdSMgrId) elog(FATAL, "Another storage manager was loaded before pg_tde. Multiple storage managers is unsupported."); - storage_manager_id = smgr_register(&tde_smgr, sizeof(TDESMgrRelationData)); + storage_manager_id = smgr_register(&tde_smgr, sizeof(TDESMgrRelation)); } /* From 48c37f8544da5beaba650ef67dffa40f088c5fd1 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 22 May 2025 12:48:35 +0300 Subject: [PATCH 365/796] DOCS-RN-rc2-update-fix (#326) multiple style fixes --- .../documentation/_resource/overrides/main.html | 4 ++-- .../documentation/docs/release-notes/rc.md | 2 +- .../documentation/docs/release-notes/rc2.md | 16 ---------------- .../docs/release-notes/release-notes.md | 4 ++-- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/contrib/pg_tde/documentation/_resource/overrides/main.html b/contrib/pg_tde/documentation/_resource/overrides/main.html index 8e523a7d493bc..f9282a9e30d3a 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/main.html +++ b/contrib/pg_tde/documentation/_resource/overrides/main.html @@ -4,8 +4,8 @@ {% extends "base.html" %} {% block announce %} - This is a Release Candidate of Percona Transparent Data Encryption extension and it is - not recommended for production environments yet. We encourage you to test it and give your feedback. + This is the Release Candidate 2 of Percona Transparent Data Encryption (TDE) extension. It is + not recommended for production environments at this stage. We encourage you to test it and give your feedback. This will help us improve the product and make it production-ready faster. {% endblock %} diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc.md b/contrib/pg_tde/documentation/docs/release-notes/rc.md index 792663a6da00e..d3ff780ff9187 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc.md @@ -1,4 +1,4 @@ -# pg_tde Release Candidate ({{date.RC}}) +# pg_tde Release Candidate 1 ({{date.RC}}) `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc2.md b/contrib/pg_tde/documentation/docs/release-notes/rc2.md index d1f736a30278a..c2d174f7e1f55 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc2.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc2.md @@ -57,7 +57,6 @@ Adjust the limits with caution since it affects other processes running in your ### New Features -* [PG-813](https://perconadev.atlassian.net/browse/PG-813) – Enabled support for logical replication in `pg_tde`. * [PG-817](https://perconadev.atlassian.net/browse/PG-817) – Added fuzz testing to `pstress` to strengthen validation and resilience. * [PG-824](https://perconadev.atlassian.net/browse/PG-824) – Ensured fsync is called on `pg_tde.map`, `pg_tde.dat`, and FS key provider files. * [PG-830](https://perconadev.atlassian.net/browse/PG-830) – Implemented full WAL encryption using Vault keyring. @@ -67,9 +66,7 @@ Adjust the limits with caution since it affects other processes running in your * [PG-962](https://perconadev.atlassian.net/browse/PG-962) – Automated test cases to validate data integrity after PostgreSQL restart. * [PG-1001](https://perconadev.atlassian.net/browse/PG-1001) – Verified encryption behavior of temporary tables. * [PG-1099](https://perconadev.atlassian.net/browse/PG-1099) – Developed automation for bare-metal performance benchmarking. -* [PG-1232](https://perconadev.atlassian.net/browse/PG-1232) – Documented the architectural design behind pg_tde. * [PG-1289](https://perconadev.atlassian.net/browse/PG-1289) – Added test cases for verifying compatibility with different PostgreSQL versions. -* [PG-1295](https://perconadev.atlassian.net/browse/PG-1295) – Introduced TAP test cases to validate WAL encryption across access methods. * [PG-1444](https://perconadev.atlassian.net/browse/PG-1444) – Implemented support for removing relation-level encryption keys when dropping tables. * [PG-1455](https://perconadev.atlassian.net/browse/PG-1455) – Introduced random base numbers in encryption IVs for enhanced security. * [PG-1458](https://perconadev.atlassian.net/browse/PG-1458) – Added visibility and verification functions for default principal keys. @@ -82,20 +79,7 @@ Adjust the limits with caution since it affects other processes running in your * [PG-826](https://perconadev.atlassian.net/browse/PG-826) – Documented how to encrypt and decrypt existing tables using pg_tde. * [PG-827](https://perconadev.atlassian.net/browse/PG-827) – Fixed CI pipeline tests on the smgr branch. * [PG-834](https://perconadev.atlassian.net/browse/PG-834) – Resolved issues with `CREATE ... USING pg_tde` on the smgr branch. -* [PG-838](https://perconadev.atlassian.net/browse/PG-838) – Added documentation for setting up streaming replication with pg_tde. -* [PG-1294](https://perconadev.atlassian.net/browse/PG-1294) – Improved WAL encryption design and performance. -* [PG-1304](https://perconadev.atlassian.net/browse/PG-1304) – Removed deprecated `tde_heap_basic` access method before GA. -* [PG-1307](https://perconadev.atlassian.net/browse/PG-1307) – Assessed overall development effort required for WAL encryption. -* [PG-1392](https://perconadev.atlassian.net/browse/PG-1392) – Investigated encryption coverage gaps in freespace and visibility maps. -* [PG-1419](https://perconadev.atlassian.net/browse/PG-1419) – Changed provider validation to occur during provider addition instead of key setup. * [PG-1427](https://perconadev.atlassian.net/browse/PG-1427) – Tested and fixed KMIP implementation for Thales support. -* [PG-1437](https://perconadev.atlassian.net/browse/PG-1437) – Cleaned up and resolved TODO items in the codebase. -* [PG-1440](https://perconadev.atlassian.net/browse/PG-1440) – Restricted key provider configuration to superusers for improved security. -* [PG-1441](https://perconadev.atlassian.net/browse/PG-1441) – Prevented replication of table-level encryption keys. -* [PG-1446](https://perconadev.atlassian.net/browse/PG-1446) – Improved locking mechanisms in pg_tde. -* [PG-1447](https://perconadev.atlassian.net/browse/PG-1447) – Verified compatibility of encryption with template databases. -* [PG-1457](https://perconadev.atlassian.net/browse/PG-1457) – Renamed key management functions for clarity and consistency. -* [PG-1467](https://perconadev.atlassian.net/browse/PG-1467) – Added Clang-based CI integration on GitHub. * [PG-1507](https://perconadev.atlassian.net/browse/PG-1507) – Handled ALTER TYPE operations in the TDE event trigger. * [PG-1508](https://perconadev.atlassian.net/browse/PG-1508) – Fixed encryption state inconsistencies when altering inherited tables. * [PG-1550](https://perconadev.atlassian.net/browse/PG-1550) – Restricted database owners from creating key providers to improve security. diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index 47e4bcc9ec751..8a5cc02020cfe 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -2,8 +2,8 @@ `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. -* [pg_tde Release Candidate 2 {{date.RC}}](rc2.md) -* [pg_tde Release Candidate {{date.RC}}](rc.md) +* [pg_tde Release Candidate 2 ({{date.RC}})](rc2.md) +* [pg_tde Release Candidate ({{date.RC}})](rc.md) * [pg_tde Beta2 (2024-12-16)](beta2.md) * [pg_tde Beta (2024-06-30)](beta.md) * [pg_tde Alpha1 (2024-03-28)](alpha1.md) From c6d4bc776fb875d12019be636efab0362660ba11 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 22 May 2025 15:22:53 +0300 Subject: [PATCH 366/796] DOCS-small-fixes (#322) literally 1 uppercase changes to files where appropriate to ensured style throughout docs by doing that, and a small sql fix for a command NOTE: I will change the capitalization to the titles a bit later as per the style guide, right now I want to have a clean style across all titles so it doesn't look too mishmashy. --- .../documentation/docs/architecture/index.md | 4 +-- contrib/pg_tde/documentation/docs/features.md | 2 +- .../kmip-server.md | 6 ++--- .../vault.md | 2 +- .../documentation/docs/how-to/decrypt.md | 12 ++++----- .../docs/how-to/external-parameters.md | 2 +- .../docs/how-to/multi-tenant-setup.md | 7 ++--- contrib/pg_tde/documentation/docs/index.md | 4 +-- .../pg_tde/documentation/docs/index/index.md | 2 +- .../docs/index/table-access-method.md | 4 +-- .../documentation/docs/index/tde-encrypts.md | 2 +- .../docs/index/tde-limitations.md | 2 +- contrib/pg_tde/documentation/docs/setup.md | 9 +++---- contrib/pg_tde/documentation/docs/test.md | 26 +++++++++---------- .../documentation/docs/wal-encryption.md | 11 ++++---- 15 files changed, 46 insertions(+), 49 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 6cf02d416e668..e5e1612094610 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -45,8 +45,8 @@ In the future these could be extracted into separate shared libraries with an op `pg_tde` uses two kinds of keys for encryption: -* Internal keys to encrypt the data. They are stored in PostgreSQL's data directory under `$PGDATA/pg_tde``. -* Higher-level keys to encrypt internal keys. These keys are called "principal keys". They are stored externally, in a Key Management System (KMS) using the key provider API. +* Internal keys to encrypt the data. They are stored in PostgreSQL's data directory under `$PGDATA/pg_tde`. +* Higher-level keys to encrypt internal keys. These keys are called *principal keys*. They are stored externally, in a Key Management System (KMS) using the key provider API. `pg_tde` uses one principal key per database. Every internal key for the given database is encrypted using this principal key. diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index 8edd99fbc991c..e7dc760e1c821 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -21,4 +21,4 @@ The following features are available for the extension: * Multiple Key management options * Logical replication of encrypted tables -[Overview](index/index.md){.md-button} [Get started](install.md){.md-button} +[Overview](index/index.md){.md-button} [Get Started](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md index 5c5ee8e892790..aea589ffb08fb 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md @@ -29,9 +29,7 @@ SELECT pg_tde_add_global_key_provider_kmip( * `port` is the port to communicate with the KMIP server. Typically used port is 5696 * `server-certificate` is the path to the certificate file for the KMIP server * `client_cert` is the path to the client certificate. -* `client_key` (optional) is the path to the client key. If not specified, the certificate key has to contain both the certifcate and the key. - -:material-information: Warning: `pg_tde_add_global_key_provider_kmip` currently accepts only a combined client key and a client certificate for its final parameter, reffered to as `client key`. +* `client_key` is the path to the client key. The following example is for testing purposes only. @@ -47,7 +45,7 @@ SELECT pg_tde_add_global_key_provider_kmip( For more information on related functions, see the link below: -[Percona pg_tde function reference](../functions.md){.md-button} +[Percona pg_tde Function Reference](../functions.md){.md-button} ## Next steps diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index e809ec6a56fdf..bc4df08d6da95 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -39,7 +39,7 @@ SELECT pg_tde_add_global_key_provider_vault_v2( For more information on related functions, see the link below: -[Percona pg_tde function reference](../functions.md){.md-button} +[Percona pg_tde Function Reference](../functions.md){.md-button} ## Next steps diff --git a/contrib/pg_tde/documentation/docs/how-to/decrypt.md b/contrib/pg_tde/documentation/docs/how-to/decrypt.md index 21e9d10fd28bb..59cc1c4a0d84f 100644 --- a/contrib/pg_tde/documentation/docs/how-to/decrypt.md +++ b/contrib/pg_tde/documentation/docs/how-to/decrypt.md @@ -5,19 +5,19 @@ If you encrypted a table with the `tde_heap` access method and need to remove the encryption from it, run the following command against the desired table (`mytable` in the example below): ```sql - ALTER TABLE mytable SET ACCESS METHOD heap; +ALTER TABLE mytable SET ACCESS METHOD heap; ``` Note that the `SET ACCESS METHOD` command drops hint bits and this may affect performance. Running a plain `SELECT count(*)` or `VACUUM` command on the entire table will check every tuple for visibility and set its hint bits. Therefore, after executing the `ALTER TABLE` command, run a simple `count(*)` on your tables: ```sql - SELECT count(*) FROM mytable; +SELECT count(*) FROM mytable; ``` Check that the table is not encrypted: ```sql - SELECT pg_tde_is_encrypted('mytable'); +SELECT pg_tde_is_encrypted('mytable'); ``` The output returns `f` meaning that the table is no longer encrypted. @@ -27,8 +27,8 @@ The output returns `f` meaning that the table is no longer encrypted. Alternatively, you can create a new not encrypted table with the same structure and data as the initial table. For example, the original encrypted table is `EncryptedCustomers`. Use the following command to create a new table `Customers`: ```sql - CREATE TABLE Customers AS - SELECT * FROM EncryptedCustomers; +CREATE TABLE Customers AS +SELECT * FROM EncryptedCustomers; ``` The new table `Customers` inherits the structure and the data from `EncryptedCustomers`. @@ -36,5 +36,5 @@ The new table `Customers` inherits the structure and the data from `EncryptedCus (Optional) If you no longer need the `EncryptedCustomers` table, you can delete it. ```sql - DROP TABLE EncryptedCustomers; +DROP TABLE EncryptedCustomers; ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/external-parameters.md b/contrib/pg_tde/documentation/docs/how-to/external-parameters.md index 86334dfb6afcc..6177a91223532 100644 --- a/contrib/pg_tde/documentation/docs/how-to/external-parameters.md +++ b/contrib/pg_tde/documentation/docs/how-to/external-parameters.md @@ -18,7 +18,7 @@ use the following command: SELECT pg_tde_add_database_key_provider_file( 'file-provider', json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ) - );" + ); ``` Or to use the `file` method, use the following command: diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 88ccd323810dd..8f7c14f17a421 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -2,9 +2,9 @@ The steps below describe how to set up multi-tenancy with `pg_tde`. Multi-tenancy allows you to encrypt different databases with different keys. This provides granular control over data and enables you to introduce different security policies and access controls for each database so that only authorized users of specific databases have access to the data. -If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Setup](../setup.md) section. +If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Configure pg_tde](../setup.md) section. -For how to enable WAL encryption, refer to the [WAL encryption](../wal-encryption.md) section. +For how to enable WAL encryption, refer to the [Configure WAL Encryption](../wal-encryption.md) section. --8<-- "kms-considerations.md" @@ -132,6 +132,7 @@ You must do these steps for every database where you have created the extension. SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` - The key is auto-generated. + !!! note + The key is auto-generated. :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index b2a0d1a2cdfa3..a2702fd6df4c7 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -6,6 +6,6 @@ This is the {{release}} version of the extension and it is not meant for production use yet. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). -[What is Transparent Data Encryption (TDE)?](index/index.md){.md-button} -[Get started](install.md){.md-button} +[Overview](index/index.md){.md-button} +[Get Started](install.md){.md-button} [What's new in pg_tde {{release}}](release-notes/release-notes.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/index.md b/contrib/pg_tde/documentation/docs/index/index.md index 94830bd9e32dc..8c988a00128d4 100644 --- a/contrib/pg_tde/documentation/docs/index/index.md +++ b/contrib/pg_tde/documentation/docs/index/index.md @@ -4,4 +4,4 @@ Transparent Data Encryption (TDE) protects your data at rest by ensuring that ev Encryption happens transparently in the background, with minimal impact on database operations. -[TDE Benefits](how-tde-helps.md){.md-button} [Check the full feature list](../features.md){.md-button} [Get started](../install.md){.md-button} +[TDE Benefits](how-tde-helps.md){.md-button} [Check the full feature list](../features.md){.md-button} [Get Started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/table-access-method.md b/contrib/pg_tde/documentation/docs/index/table-access-method.md index bff2655f0014a..1e366f1f16887 100644 --- a/contrib/pg_tde/documentation/docs/index/table-access-method.md +++ b/contrib/pg_tde/documentation/docs/index/table-access-method.md @@ -79,13 +79,13 @@ Here is how you can set the new default table access method: You can run the SET command anytime during the session. ```sql - SET default_table_access_method = tde_heap; + SET default_table_access_method = tde_heap; ``` 2. Reload the configuration to apply the changes: ```sql - SELECT pg_reload_conf(); + SELECT pg_reload_conf(); ``` [Limitations of TDE](tde-limitations.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md index d7cd64386537a..77023c35469a2 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md +++ b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md @@ -8,4 +8,4 @@ * **Indexes** associated encrypted tables. * **Logical replication data** for encrypted tables (ensures encrypted content is preserved across replicas). -[Table access methods and TDE](table-access-method.md){.md-button} +[Table Access Methods and TDE](table-access-method.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index f1c07db6f0c99..393167e651123 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -6,4 +6,4 @@ * `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. * `pg_tde` Release candidate is incompatible with `pg_tde`Beta2 due to significant changes in code. There is no direct upgrade flow from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. -[Versions and supported PostgreSQL deployments](supported-versions.md){.md-button} +[Versions and Supported PostgreSQL Deployments](supported-versions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/setup.md b/contrib/pg_tde/documentation/docs/setup.md index 7a54279062b9d..a83546a83f095 100644 --- a/contrib/pg_tde/documentation/docs/setup.md +++ b/contrib/pg_tde/documentation/docs/setup.md @@ -3,10 +3,9 @@ Before you can use `pg_tde` for data encryption, you must enable the extension and configure PostgreSQL to load it at startup. This setup ensures that the necessary hooks and shared memory are available for encryption operations. !!! note + To learn how to configure multi-tenancy, refer to the [Configure multi-tenancy](how-to/multi-tenant-setup.md) guidelines. - To learn how to configure multi-tenancy, refer to the [Configure multi-tenancy](how-to/multi-tenant-setup.md) guidelines. - -The `pg_tde` extension requires additional shared memory. You need to configure PostgreSQL to prelaod it at startup. +The `pg_tde` extension requires additional shared memory. You need to configure PostgreSQL to preload it at startup. ## 1. Configure shared_preload_libraries @@ -45,7 +44,7 @@ Restart the `postgresql` cluster to apply the configuration. After restarting PostgreSQL, connect to `psql` as a **superuser** or **database owner** and run: ```sql - CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_tde; ``` See [CREATE EXTENSION :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-createextension.html) for more details. @@ -59,7 +58,7 @@ See [CREATE EXTENSION :octicons-link-external-16:](https://www.postgresql.org/do To automatically have `pg_tde` enabled for all new databases, modify the `template1` database: ``` - psql -d template1 -c 'CREATE EXTENSION pg_tde;' +psql -d template1 -c 'CREATE EXTENSION pg_tde;' ``` !!! note diff --git a/contrib/pg_tde/documentation/docs/test.md b/contrib/pg_tde/documentation/docs/test.md index d34c47d7b1bae..c0cf972cc0b4a 100644 --- a/contrib/pg_tde/documentation/docs/test.md +++ b/contrib/pg_tde/documentation/docs/test.md @@ -7,26 +7,26 @@ After enabling the `pg_tde` extension for a database, you can begin encrypting d 1. Create a table in the database for which you have [enabled `pg_tde`](setup.md) using the `tde_heap` access method as follows: ```sql - CREATE TABLE ( ) USING tde_heap; + CREATE TABLE ( ) USING tde_heap; ``` :material-information: Warning: Example for testing purposes only: ```sql - CREATE TABLE albums ( - album_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - artist_id INTEGER, - title TEXT NOT NULL, - released DATE NOT NULL - ) USING tde_heap; + CREATE TABLE albums ( + album_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + artist_id INTEGER, + title TEXT NOT NULL, + released DATE NOT NULL + ) USING tde_heap; ``` - Learn more about table access methods and how you can enable data encryption by default in the [Table access methods](index/table-access-method.md) section. + Learn more about table access methods and how you can enable data encryption by default in the [Table Access Methods and TDE](index/table-access-method.md) section. 2. To check if the data is encrypted, run the following function: ```sql - SELECT pg_tde_is_encrypted('table_name'); + SELECT pg_tde_is_encrypted('table_name'); ``` The function returns `t` if the table is encrypted and `f` - if not. @@ -42,23 +42,21 @@ You can encrypt an existing table. It requires rewriting the table, so for large Run the following command: ```sql - ALTER TABLE table_name SET ACCESS METHOD tde_heap; +ALTER TABLE table_name SET ACCESS METHOD tde_heap; ``` !!! important - Using `SET ACCESS METHOD` drops hint bits which can impact query performance. To restore performance, run: ```sql - SELECT count(*) FROM table_name; + SELECT count(*) FROM table_name; ``` This forces PostgreSQL to check every tuple for visibility and reset the hint bits. !!! hint - Want to remove encryption later? See how to [decrypt your data](how-to/decrypt.md). ## Next steps -[Configure WAL encryption (tech preview) :material-arrow-right:](wal-encryption.md){.md-button} +[Configure WAL Encryption (tech preview) :material-arrow-right:](wal-encryption.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 5f5448c04cd34..a40831fcf110b 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -62,13 +62,13 @@ Before turning WAL encryption on, you must follow the steps below to create your 3. Create principal key ```sql - SELECT pg_tde_set_server_key_using_global_key_provider('key', 'provider-name'); + SELECT pg_tde_set_server_key_using_global_key_provider('key', 'provider-name'); ``` 4. Enable WAL level encryption using the `ALTER SYSTEM` command. You need the privileges of the superuser to run this command: ```sql - ALTER SYSTEM SET pg_tde.wal_encrypt = on; + ALTER SYSTEM SET pg_tde.wal_encrypt = on; ``` 5. Restart the server to apply the changes. @@ -76,15 +76,16 @@ Before turning WAL encryption on, you must follow the steps below to create your * On Debian and Ubuntu: ```sh - sudo systemctl restart postgresql + sudo systemctl restart postgresql ``` * On RHEL and derivatives ```sh - sudo systemctl restart postgresql-17 + sudo systemctl restart postgresql-17 ``` Now WAL files start to be encrypted for both encrypted and unencrypted tables. -For more technical references related to architecture, variables or functions, see [Technical Reference](advanced-topics/index.md). +For more technical references related to architecture, variables or functions, see: +[Technical Reference](advanced-topics/index.md){.md-button} From da8455e22faf0f0415595f6d49c9017cbdc3c2fe Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 21 May 2025 19:05:47 +0200 Subject: [PATCH 367/796] Make check for temporary table owned by other backend idiomatic Instead of doing it our own way do the way the rest of the code does it. --- contrib/pg_tde/src/common/pg_tde_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 91a9c8128e26e..109657f2b9d4a 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -37,7 +37,7 @@ pg_tde_is_encrypted(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - if (RelFileLocatorBackendIsTemp(rlocator) && !rel->rd_islocaltemp) + if (RELATION_IS_OTHER_TEMP(rel)) ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("we cannot check if temporary relations from other backends are encrypted")); From ef908f749287694339e8964e335d43bcba7fc8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 11:18:03 +0200 Subject: [PATCH 368/796] Remove "type" from json generated by CLI tool The configuration json generated by the pg_tde_change_key_provider CLI tool was not valid since it contained an unexpected field. Also include provider id in the record before attempting the modification as otherwise the modify function throws an error. --- contrib/pg_tde/src/pg_tde_change_key_provider.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index c7840ed8ac74d..be72b63c0218d 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -175,7 +175,7 @@ main(int argc, char *argv[]) exit(1); } - if (!build_json(json, 2, "type", "file", "path", argv[4 + argstart])) + if (!build_json(json, 1, "path", argv[4 + argstart])) { exit(1); } @@ -193,7 +193,11 @@ main(int argc, char *argv[]) exit(1); } - if (!build_json(json, 5, "type", "vault-v2", "url", argv[4 + argstart], "token", argv[5 + argstart], "mountPath", argv[6 + argstart], "caPath", (argc - argstart > 7 ? argv[7 + argstart] : ""))) + if (!build_json(json, 4, + "url", argv[4 + argstart], + "token", argv[5 + argstart], + "mountPath", argv[6 + argstart], + "caPath", (argc - argstart > 7 ? argv[7 + argstart] : ""))) { exit(1); } @@ -211,7 +215,12 @@ main(int argc, char *argv[]) exit(1); } - if (!build_json(json, 6, "type", "kmip", "host", argv[4 + argstart], "port", argv[5 + argstart], "caPath", (argc - argstart > 8 ? argv[8 + argstart] : ""), "certPath", argv[6 + argstart], "keyPath", argv[7 + argstart])) + if (!build_json(json, 5, + "host", argv[4 + argstart], + "port", argv[5 + argstart], + "caPath", (argc - argstart > 8 ? argv[8 + argstart] : ""), + "certPath", argv[6 + argstart], + "keyPath", argv[7 + argstart])) { exit(1); } @@ -254,6 +263,7 @@ main(int argc, char *argv[]) strncpy(provider.options, json, sizeof(provider.options)); strncpy(provider.provider_name, provider_name, sizeof(provider.provider_name)); + provider.provider_id = keyring->keyring_id; provider.provider_type = get_keyring_provider_from_typename(new_provider_type); modify_key_provider_info(&provider, db_oid, false); From 72ac920a31485c64a16425b1cdbe43232505ba9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 11:18:45 +0200 Subject: [PATCH 369/796] Add information about global key providers to CLI tool The pg_tde_change_key_provider CLI tool usage information did not contain information about how to update a global key provider. --- contrib/pg_tde/src/pg_tde_change_key_provider.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index be72b63c0218d..7e7445a098cc4 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -30,6 +30,8 @@ help(void) puts("pg_tde_change_key_provider [-D ] vault []"); puts("pg_tde_change_key_provider [-D ] kmip []"); puts(""); + printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); + puts(""); puts("WARNING:"); puts(""); puts("This tool only changes the values, without properly XLogging the changes, or adjusting the configuration in the running postgres processes. Only use it in case the database is inaccessible and can't be started.\n"); From a5d6b7b57dc2fc54757fe1d5719c22b98a6f5e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 11:20:55 +0200 Subject: [PATCH 370/796] Do not validate provider settings in CLI tool This tool is only meant to be used in dire circumstances and whoever is using it should be sure what they want to do. Also remove information about "running postgres processes" from usage information as the tool will refuse to do anything if the cluster is running. --- contrib/pg_tde/src/catalog/tde_keyring.c | 5 ++--- .../pg_tde/src/include/catalog/tde_keyring.h | 5 +++++ .../pg_tde/src/pg_tde_change_key_provider.c | 20 ++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index eb70176eed15e..3296f537b909a 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -62,7 +62,6 @@ static GenericKeyring *load_keyring_provider_options(ProviderType provider_type, static KmipKeyring *load_kmip_keyring_provider_options(char *keyring_options); static VaultV2Keyring *load_vaultV2_keyring_provider_options(char *keyring_options); static int open_keyring_infofile(Oid dbOid, int flags); -static void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog); #ifdef FRONTEND @@ -432,7 +431,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid) #endif /* !FRONTEND */ -static void +void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) { off_t bytes_written; @@ -516,7 +515,7 @@ check_provider_record(KeyringProviderRecord *provider_record) } /* Returns true if the record is found, false otherwise. */ -static bool +bool get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, KeyringProviderRecordInFile *record) { diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 42b708cb8f9cf..75b490b3218f5 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -41,6 +41,11 @@ extern void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog); extern void delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog); +extern bool get_keyring_info_file_record_by_name(char *provider_name, + Oid database_id, + KeyringProviderRecordInFile *record); +extern void write_key_provider_info(KeyringProviderRecordInFile *record, + bool write_xlog); extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); extern void ParseKeyringJSONOptions(ProviderType provider_type, diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 7e7445a098cc4..39d0774612391 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -34,7 +34,7 @@ help(void) puts(""); puts("WARNING:"); puts(""); - puts("This tool only changes the values, without properly XLogging the changes, or adjusting the configuration in the running postgres processes. Only use it in case the database is inaccessible and can't be started.\n"); + puts("This tool only changes the values, without properly XLogging the changes, or validating that keys can be fetched using them. Only use it in case the database is inaccessible and can't be started.\n"); } #define BUFFER_SIZE 1024 @@ -115,8 +115,7 @@ main(int argc, char *argv[]) char tdedir[MAXPGPATH] = {0,}; char *cptr = tdedir; bool provider_found = false; - GenericKeyring *keyring = NULL; - KeyringProviderRecord provider; + KeyringProviderRecordInFile record; Oid db_oid; @@ -254,20 +253,17 @@ main(int argc, char *argv[]) cptr = strcat(cptr, PG_TDE_DATA_DIR); pg_tde_set_data_dir(tdedir); - /* reports error if not found */ - keyring = GetKeyProviderByName(provider_name, db_oid); - - if (keyring == NULL) + if (get_keyring_info_file_record_by_name(provider_name, db_oid, &record) == false) { printf("Error: provider not found\n."); exit(1); } - strncpy(provider.options, json, sizeof(provider.options)); - strncpy(provider.provider_name, provider_name, sizeof(provider.provider_name)); - provider.provider_id = keyring->keyring_id; - provider.provider_type = get_keyring_provider_from_typename(new_provider_type); - modify_key_provider_info(&provider, db_oid, false); + record.provider.provider_type = get_keyring_provider_from_typename(new_provider_type); + memset(record.provider.options, 0, sizeof(record.provider.options)); + strncpy(record.provider.options, json, sizeof(record.provider.options)); + + write_key_provider_info(&record, false); printf("Key provider updated successfully!\n"); From 11f16d7d2e79ada6aab9d1dc595377293ba7386d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 16:05:14 +0200 Subject: [PATCH 371/796] Fix erroneous usage instructions This was missed in 443d33c96a76f361d91467f17c50af337dd1f043 --- contrib/pg_tde/src/pg_tde_change_key_provider.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 39d0774612391..84ba605c123df 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -28,7 +28,7 @@ help(void) puts(""); puts("pg_tde_change_key_provider [-D ] file "); puts("pg_tde_change_key_provider [-D ] vault []"); - puts("pg_tde_change_key_provider [-D ] kmip []"); + puts("pg_tde_change_key_provider [-D ] kmip []"); puts(""); printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); puts(""); From 4f38eb11e2fb9233ef7c27a9cb7bb094e70ae5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 16:16:04 +0200 Subject: [PATCH 372/796] Fix incorrect usage instructions for vault provider This seems to always have been incorrect. --- contrib/pg_tde/src/pg_tde_change_key_provider.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 84ba605c123df..7b4ab414ea7fb 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -22,12 +22,12 @@ help(void) puts(""); puts("pg_tde_change_key_provider [-D ] "); puts(""); - puts(" Where can be file, vault or kmip"); + puts(" Where can be file, vault-v2 or kmip"); puts(""); puts("Depending on the provider type, the complete parameter list is:"); puts(""); puts("pg_tde_change_key_provider [-D ] file "); - puts("pg_tde_change_key_provider [-D ] vault []"); + puts("pg_tde_change_key_provider [-D ] vault-v2 []"); puts("pg_tde_change_key_provider [-D ] kmip []"); puts(""); printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); From 3e1db8a23ad6efbe3acb5d3f7c526c6850eb9385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 17:08:07 +0200 Subject: [PATCH 373/796] Print errors to stderr in CLI tool This seems like the right thing to do. --- contrib/pg_tde/src/pg_tde_change_key_provider.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 7b4ab414ea7fb..be3e37c1519d2 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -83,7 +83,7 @@ build_json(char *buffer, int count,...) } if (ptr - buffer > BUFFER_SIZE) { - printf("Error: Configuration too long.\n"); + fprintf(stderr, "Error: Configuration too long.\n"); return false; } } @@ -93,7 +93,7 @@ build_json(char *buffer, int count,...) if (ptr - buffer > BUFFER_SIZE) { - printf("Error: Configuration too long.\n"); + fprintf(stderr, "Error: Configuration too long.\n"); return false; } @@ -150,7 +150,7 @@ main(int argc, char *argv[]) { help(); puts("\n"); - printf("Error: Data directory missing.\n"); + fprintf(stderr, "Error: Data directory missing.\n"); exit(1); } @@ -172,7 +172,7 @@ main(int argc, char *argv[]) { help(); puts("\n"); - printf("Error: wrong number of arguments.\n"); + fprintf(stderr, "Error: wrong number of arguments.\n"); exit(1); } @@ -190,7 +190,7 @@ main(int argc, char *argv[]) { help(); puts("\n"); - printf("Error: wrong number of arguments.\n"); + fprintf(stderr, "Error: wrong number of arguments.\n"); exit(1); } @@ -212,7 +212,7 @@ main(int argc, char *argv[]) { help(); puts("\n"); - printf("Error: wrong number of arguments.\n"); + fprintf(stderr, "Error: wrong number of arguments.\n"); exit(1); } @@ -231,7 +231,7 @@ main(int argc, char *argv[]) { help(); puts("\n"); - printf("Error: Unknown provider type: %s\n.", new_provider_type); + fprintf(stderr, "Error: Unknown provider type: %s\n.", new_provider_type); exit(1); } @@ -255,7 +255,7 @@ main(int argc, char *argv[]) if (get_keyring_info_file_record_by_name(provider_name, db_oid, &record) == false) { - printf("Error: provider not found\n."); + fprintf(stderr, "Error: provider not found\n."); exit(1); } From f41950468be6cccb0cec2e0e22a54856a029f896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 21 May 2025 17:09:13 +0200 Subject: [PATCH 374/796] Add test for pg_tde_change_key_provider Since there is precedent for using "normal" tap assertions for CLI tools, they are also used here for easier debugging of test failures. --- contrib/pg_tde/meson.build | 1 + .../t/016_pg_tde_change_key_provider.pl | 271 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 contrib/pg_tde/t/016_pg_tde_change_key_provider.pl diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index c78db880e7a2a..f5709c08706dd 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -118,6 +118,7 @@ tap_tests = [ 't/013_crash_recovery.pl', 't/014_pg_waldump_basic.pl', 't/015_pg_waldump_fullpage.pl', + 't/016_pg_tde_change_key_provider.pl', ] tests += { diff --git a/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl b/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl new file mode 100644 index 0000000000000..c197f38f15f03 --- /dev/null +++ b/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl @@ -0,0 +1,271 @@ +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +use JSON; + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', q{shared_preload_libraries = 'pg_tde'}); +$node->start; + +$node->safe_psql('postgres', q{CREATE EXTENSION IF NOT EXISTS pg_tde}); +$node->safe_psql('postgres', + q{SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/pg_tde_change_key_provider-global')} +); +$node->safe_psql('postgres', + q{SELECT pg_tde_add_database_key_provider_file('database-provider', '/tmp/pg_tde_change_key_provider-database')} +); +my $db_oid = $node->safe_psql('postgres', + q{SELECT oid FROM pg_catalog.pg_database WHERE datname = 'postgres'}); +my $options; + +$node->stop; + +command_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + $db_oid, + 'database-provider', + 'file', + '/tmp/pg_tde_change_key_provider-database-2', + ], + qr/Key provider updated successfully!/, + 'updates key provider to file type'); + +$node->start; + +is( $node->safe_psql( + 'postgres', + q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + ), + 'file', + 'provider type is set to file'); + +$options = decode_json( + $node->safe_psql( + 'postgres', + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + )); +is( $options->{path}, + '/tmp/pg_tde_change_key_provider-database-2', + 'path is set correctly for file provider'); + +$node->stop; + +command_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + $db_oid, + 'database-provider', + 'vault-v2', + 'http://vault-server.example:8200/', + 'secret-token', + 'mount-path', + '/tmp/ca_path', + ], + qr/Key provider updated successfully!/, + 'updates key provider to vault-v2 type'); + +$node->start; + +is( $node->safe_psql( + 'postgres', + q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + ), + 'vault-v2', + 'provider type is set to vault-v2'); + +$options = decode_json( + $node->safe_psql( + 'postgres', + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + )); +is($options->{token}, 'secret-token', + 'token is set correctly for vault-v2 provider'); +is( $options->{url}, + 'http://vault-server.example:8200/', + 'url is set correctly for vault-v2 provider'); +is($options->{mountPath}, 'mount-path', + 'mount path is set correctly for vault-v2 provider'); +is($options->{caPath}, '/tmp/ca_path', + 'CA path is set correctly for vault-v2 provider'); + +$node->stop; + +command_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + $db_oid, + 'database-provider', + 'kmip', + 'kmip-server.example', + '12345', + '/tmp/cert_path', + '/tmp/key_path', + '/tmp/ca_path', + ], + qr/Key provider updated successfully!/, + 'updates key provider to kmip type'); + +$node->start; + +is( $node->safe_psql( + 'postgres', + q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + ), + 'kmip', + 'provider type is set to kmip'); + +$options = decode_json( + $node->safe_psql( + 'postgres', + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + )); +is($options->{host}, 'kmip-server.example', + 'host is set correctly for kmip provider'); +is($options->{port}, '12345', 'port is set correctly for kmip provider'); +is($options->{certPath}, '/tmp/cert_path', + 'client cert path is set correctly for kmip provider'); +is($options->{keyPath}, '/tmp/key_path', + 'client cert key path is set correctly for kmip provider'); +is($options->{caPath}, '/tmp/ca_path', + 'CA path is set correctly for kmip provider'); + +$node->stop; + +command_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'vault-v2', + 'http://vault-server.example:8200/', + 'secret-token', + 'mount-path', + '/tmp/ca_path', + ], + qr/Key provider updated successfully!/, + 'updates key provider to vault-v2 type for global provider'); + +$node->start; + +is( $node->safe_psql( + 'postgres', + q{SELECT provider_type FROM pg_tde_list_all_global_key_providers() WHERE provider_name = 'global-provider'} + ), + 'vault-v2', + 'provider type is set to vault-v2 for global provider'); + +$options = decode_json( + $node->safe_psql( + 'postgres', + q{SELECT options FROM pg_tde_list_all_global_key_providers() WHERE provider_name = 'global-provider'} + )); +is( $options->{url}, + 'http://vault-server.example:8200/', + 'options are updated for global provider'); + +$node->stop; + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => '/non/existing/path', + '1664', + 'global-provider', + 'file', + '/tmp/file', + ], + qr{pg_tde_change_key_provider: error: could not open file "/non/existing/path/global/pg_control" for reading: No such file or directory}, + 'gives error on incorrect data dir'); + +$node->start; +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'file', + '/tmp/file', + ], + qr/pg_tde_change_key_provider: error: cluster must be shut down/, + 'gives error on if cluster is running'); +$node->stop; + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '12345678', + 'global-provider', + 'file', + '/tmp/file', + ], + qr/error: could not open tde file "[^"]+": No such file or directory/, + 'gives error on unknown database oid'); + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'incorrect-global-provider', + 'file', + '/tmp/file', + ], + qr/Error: provider not found/, + 'gives error on unknown key provider'); + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'incorrect-provider-type', + ], + qr/Error: Unknown provider type: incorrect-provider-type/, + 'gives error on unknown provider type'); + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'file', + ], + qr/Error: wrong number of arguments./, + 'gives error on missing arguments for file provider'); + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'kmip', + ], + qr/Error: wrong number of arguments./, + 'gives error on missing arguments for kmip provider'); + +command_fails_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + '1664', + 'global-provider', + 'vault-v2', + ], + qr/Error: wrong number of arguments./, + 'gives error on missing arguments for vault-v2 provider'); + +done_testing(); From 1fdddb9fdb555941fc00d674182b4bb2ffdf742b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 15:59:46 +0200 Subject: [PATCH 375/796] Remove PG_VERSION_NUM checks We know what version we're compiling already. --- contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 8 +------- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 -- contrib/pg_tde/src/keyring/keyring_vault.c | 8 ++------ 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index c9fd1c76cb4a7..fe3067e35fd88 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -155,12 +155,7 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch parse.provider_type = provider_type; parse.provider_opts = out_opts; parse.state = JK_EXPECT_TOP_LEVEL_OBJECT; - -#if PG_VERSION_NUM >= 170000 jlex = makeJsonLexContextCstringLen(NULL, in_buf, buf_len, PG_UTF8, true); -#else - jlex = makeJsonLexContextCstringLen(in_buf, buf_len, PG_UTF8, true); -#endif /* * Set up semantic actions. The function below will be called when the @@ -185,9 +180,8 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch errmsg("parsing of keyring options failed: %s", json_errdetail(jerr, jlex))); } -#if PG_VERSION_NUM >= 170000 + freeJsonLexContext(jlex); -#endif } /* diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 232c2b2da4b62..41a697830f9d3 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -76,9 +76,7 @@ static dshash_parameters principal_key_dsh_params = { .entry_size = sizeof(TDEPrincipalKey), .compare_function = dshash_memcmp, .hash_function = dshash_memhash, -#if PG_VERSION_NUM >= 170000 .copy_function = dshash_memcpy, -#endif }; static TdePrincipalKeylocalState principalKeyLocalState; diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index edf5828780598..1c01357899208 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -251,11 +251,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode goto cleanup; } -#if PG_VERSION_NUM < 170000 - jlex = makeJsonLexContextCstringLen(str.ptr, str.len, PG_UTF8, true); -#else jlex = makeJsonLexContextCstringLen(NULL, str.ptr, str.len, PG_UTF8, true); -#endif json_error = parse_json_response(&parse, jlex); if (json_error != JSON_SUCCESS) @@ -292,10 +288,10 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode cleanup: if (str.ptr != NULL) pfree(str.ptr); -#if PG_VERSION_NUM >= 170000 + if (jlex != NULL) freeJsonLexContext(jlex); -#endif + return key; } From 9aaba7d812da847309e2504e8595e6dff9328632 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Tue, 29 Apr 2025 18:56:46 +0200 Subject: [PATCH 376/796] PG-1465 Add CI workfolw with sanitizers Add CI workflow with address and undefined behaviour santitzers. --- .github/workflows/coverage.yml | 2 +- .github/workflows/psp-reusable.yml | 2 +- .github/workflows/sanitizers.yml | 69 +++++++++++++++++++ ci_scripts/make-build.sh | 35 +++++++--- ci_scripts/make-test.sh | 44 +++++++----- ci_scripts/meson-build.sh | 28 +++++++- ci_scripts/meson-test.sh | 39 ++++++----- ci_scripts/suppressions/lsan.supp | 15 ++++ contrib/pg_tde/documentation/CONTRIBUTING.md | 2 +- .../pg_tde/documentation/docs/contribute.md | 2 +- src/test/recovery/t/012_subtransactions.pl | 4 ++ 11 files changed, 195 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/sanitizers.yml create mode 100644 ci_scripts/suppressions/lsan.supp diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 10b58784bdc7e..28e2369bf12e4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -28,7 +28,7 @@ jobs: run: ci_scripts/setup-keyring-servers.sh - name: Test postgres with TDE to generate coverage - run: ci_scripts/make-test.sh --tde-only + run: ci_scripts/make-test.sh tde - name: Collect coverage data run: find . -type f -name "*.c" ! -path '*libkmip*' | xargs -t gcov -abcfu diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/psp-reusable.yml index e72169af6353a..c363deb437221 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/psp-reusable.yml @@ -71,7 +71,7 @@ jobs: run: src/ci_scripts/setup-keyring-servers.sh - name: Test postgres - run: src/ci_scripts/${{ inputs.build_script }}-test.sh + run: src/ci_scripts/${{ inputs.build_script }}-test.sh all - name: Report on test fail uses: actions/upload-artifact@v4 diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml new file mode 100644 index 0000000000000..540b32e67d3ea --- /dev/null +++ b/.github/workflows/sanitizers.yml @@ -0,0 +1,69 @@ +name: Sanitizers +on: + pull_request: + push: + branches: + - TDE_REL_17_STABLE + +env: + CC: clang + LD: clang + UBSAN_OPTIONS: log_path=${{ github.workspace }}/sanitize.log print_suppressions=0 print_stacktrace=1 print_summary=1 halt_on_error=1 + ASAN_OPTIONS: log_path=${{ github.workspace }}/sanitize.log print_suppressions=0 abort_on_error=1 + LSAN_OPTIONS: log_path=${{ github.workspace }}/sanitize.log print_suppressions=0 suppressions=${{ github.workspace }}/ci_scripts/suppressions/lsan.supp + ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-14 + EXTRA_REGRESS_OPTS: "--temp-config=${{ github.workspace }}/test_postgresql.conf" + PGCTLTIMEOUT: 120 + +jobs: + run: + name: Run + runs-on: ubuntu-22.04 + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: ci_scripts/ubuntu-deps.sh + + - name: Build postgres + run: ci_scripts/make-build.sh sanitize + + - name: Setup kmip and vault + run: ci_scripts/setup-keyring-servers.sh + + - name: Configure test postgres + run: echo 'max_stack_depth=8MB' > test_postgresql.conf + + - name: Run tests pg_tde tests + run: ci_scripts/make-test.sh tde + + - name: Run PG regression tests + run: ci_scripts/make-test.sh server + + - name: Print sanitize logs + if: ${{ !cancelled() }} + run: cat sanitize.log.* + + - name: Report on test fail + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: sanitizers-testlog + path: | + build/testrun + contrib/**/log + contrib/**/regression.diffs + contrib/**/regression.out + contrib/**/results + contrib/**/tmp_check + contrib/**/t/results + src/test/**/log + src/test/**/regression.diffs + src/test/**/regression.out + src/test/**/results + src/test/**/tmp_check + sanitize.log.* + retention-days: 3 diff --git a/ci_scripts/make-build.sh b/ci_scripts/make-build.sh index c99e77a051bb1..63b139e805957 100755 --- a/ci_scripts/make-build.sh +++ b/ci_scripts/make-build.sh @@ -1,13 +1,13 @@ #!/bin/bash -ENABLE_COVERAGE= +ARGS= for arg in "$@" do case "$arg" in --enable-coverage) - ENABLE_COVERAGE="--enable-coverage" - shift;; + ARGS +=" --enable-coverage" + ;; esac done @@ -17,10 +17,29 @@ source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/.." -if [ "$1" = "debugoptimized" ]; then - export CFLAGS="-O2" - export CXXFLAGS="-O2" -fi +case "$1" in + debug) + echo "Building with debug option" + ARGS+=" --enable-cassert" + ;; -./configure --prefix="$INSTALL_DIR" --enable-debug --enable-cassert --enable-tap-tests $ENABLE_COVERAGE + debugoptimized) + echo "Building with debugoptimized option" + export CFLAGS="-O2" + ARGS+=" --enable-cassert" + ;; + + sanitize) + echo "Building with sanitize option" + export CFLAGS="-fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -fno-inline-functions" + ;; + + *) + echo "Unknown build type: $1" + echo "Please use one of the following: debug, debugoptimized, sanitize" + exit 1 + ;; +esac + +./configure --prefix="$INSTALL_DIR" --enable-debug --enable-tap-tests $ARGS make install-world -j diff --git a/ci_scripts/make-test.sh b/ci_scripts/make-test.sh index 6ce136dd28bca..2cf269fccaa64 100755 --- a/ci_scripts/make-test.sh +++ b/ci_scripts/make-test.sh @@ -1,25 +1,33 @@ #!/bin/bash set -e -TDE_ONLY=0 - -for arg in "$@" -do - case "$arg" in - --tde-only) - TDE_ONLY=1 - shift;; - esac -done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" source "$SCRIPT_DIR/env.sh" -if [ "$TDE_ONLY" -eq 1 ]; -then - cd "$SCRIPT_DIR/../contrib/pg_tde" - make -s check -else - cd "$SCRIPT_DIR/.." - make -s check-world -fi + +case "$1" in + server) + echo "Run server regression tests" + cd "$SCRIPT_DIR/.." + make -s check + ;; + + tde) + echo "Run tde tests" + cd "$SCRIPT_DIR/../contrib/pg_tde" + make -s check + ;; + + all) + echo "Run all tests" + cd "$SCRIPT_DIR/.." + make -s check-world + ;; + + *) + echo "Unknown test suite: $1" + echo "Please use one of the following: server, tde, all" + exit 1 + ;; +esac diff --git a/ci_scripts/meson-build.sh b/ci_scripts/meson-build.sh index e954b04619c09..1cd3145a04b93 100755 --- a/ci_scripts/meson-build.sh +++ b/ci_scripts/meson-build.sh @@ -17,5 +17,31 @@ source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/.." -meson setup build --prefix "$INSTALL_DIR" --buildtype="$1" -Dcassert=true -Dtap_tests=enabled $ENABLE_COVERAGE +BUILD_TYPE= + +case "$1" in + debug) + echo "Building with debug option" + BUILD_TYPE=$1 + ;; + + debugoptimized) + echo "Building with debugoptimized option" + BUILD_TYPE=$1 + ;; + + sanitize) + echo "Building with sanitize option" + export CFLAGS="-fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -fno-inline-functions" + BUILD_TYPE=debug + ;; + + *) + echo "Unknown build type: $1" + echo "Please use one of the following: debug, debugoptimized, sanitize" + exit 1 + ;; +esac + +meson setup build --prefix "$INSTALL_DIR" --buildtype="$BUILD_TYPE" -Dcassert=true -Dtap_tests=enabled $ENABLE_COVERAGE cd build && ninja && ninja install diff --git a/ci_scripts/meson-test.sh b/ci_scripts/meson-test.sh index f3f888d31c23f..c9de99dca05f4 100755 --- a/ci_scripts/meson-test.sh +++ b/ci_scripts/meson-test.sh @@ -1,25 +1,32 @@ #!/bin/bash set -e -TDE_ONLY=0 - -for arg in "$@" -do - case "$arg" in - --tde-only) - TDE_ONLY=1 - shift;; - esac -done SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" source "$SCRIPT_DIR/env.sh" cd "$SCRIPT_DIR/../build" -if [ "$TDE_ONLY" -eq 1 ]; -then - meson test --suite setup --suite pg_tde -else - meson test -fi + +case "$1" in + server) + echo "Run server regression tests" + meson test --suite setup --suite regress + ;; + + tde) + echo "Run tde tests" + meson test --suite setup --suite pg_tde + ;; + + all) + echo "Run all tests" + meson test + ;; + + *) + echo "Unknown test suite: $1" + echo "Please use one of the following: server, tde, all" + exit 1 + ;; +esac diff --git a/ci_scripts/suppressions/lsan.supp b/ci_scripts/suppressions/lsan.supp new file mode 100644 index 0000000000000..00ab2f110a22c --- /dev/null +++ b/ci_scripts/suppressions/lsan.supp @@ -0,0 +1,15 @@ +leak:save_ps_display_args +leak:initdb.c +leak:fe_memutils.c +leak:fe-exec.c +leak:fe-connect.c +leak:pqexpbuffer.c +leak:strdup +leak:preproc.y +leak:pgtypeslib/common.c +leak:ecpglib/memory.c +leak:kmip.c + +#pg_dump +leak:getSchemaData +leak:dumpDumpableObject diff --git a/contrib/pg_tde/documentation/CONTRIBUTING.md b/contrib/pg_tde/documentation/CONTRIBUTING.md index 9de67c5babf4b..5708d979dee9f 100644 --- a/contrib/pg_tde/documentation/CONTRIBUTING.md +++ b/contrib/pg_tde/documentation/CONTRIBUTING.md @@ -56,7 +56,7 @@ To run the tests, use the following command: ``` source ci_scripts/setup-keyring-servers.sh -ci_scripts/make-test.sh +ci_scripts/make-test.sh all ``` You can run tests on your local machine with whatever operating system you have. After you submit the pull request, we will check your patch on multiple operating systems. diff --git a/contrib/pg_tde/documentation/docs/contribute.md b/contrib/pg_tde/documentation/docs/contribute.md index e626488ea1599..b7eceb4f2c15c 100644 --- a/contrib/pg_tde/documentation/docs/contribute.md +++ b/contrib/pg_tde/documentation/docs/contribute.md @@ -56,7 +56,7 @@ To run the tests, use the following command: ``` source ci_scripts/setup-keyring-servers.sh -ci_scripts/make-test.sh +ci_scripts/make-test.sh all ``` You can run tests on your local machine with whatever operating system you have. After you submit the pull request, we will check your patch on multiple operating systems. diff --git a/src/test/recovery/t/012_subtransactions.pl b/src/test/recovery/t/012_subtransactions.pl index 2f74bfc9a5af5..8d83c0687c838 100644 --- a/src/test/recovery/t/012_subtransactions.pl +++ b/src/test/recovery/t/012_subtransactions.pl @@ -16,6 +16,10 @@ 'postgresql.conf', qq( max_prepared_transactions = 10 log_checkpoints = true + + ## Workaround to make tests pass with enabled UBSAN + ## https://www.postgresql.org/message-id/flat/1775221.1658699459%40sss.pgh.pa.us#18ac2074d2383f232a20bf8b7b32c526 + max_stack_depth = 8MB )); $node_primary->start; $node_primary->backup('primary_backup'); From 3aabe35b9d63602334e59371834d6bf0e3917dac Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 5 May 2025 23:55:33 +0200 Subject: [PATCH 377/796] Fix uninitialized pointers Use palloc0_object to initialize tail and head pointers to zero value. --- contrib/pg_tde/src/catalog/tde_keyring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 3296f537b909a..e7595ee3b9759 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -780,7 +780,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) providers_list = lappend(providers_list, keyring); #else if (providers_list == NULL) - providers_list = palloc_object(SimplePtrList); + providers_list = palloc0_object(SimplePtrList); simple_ptr_list_append(providers_list, keyring); #endif } From 2bacec90b25a5b32556a8a5c5bf79b794f7797d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 13:57:55 +0200 Subject: [PATCH 378/796] Make function that can only return one value void This function could only ever return JSON_SUCCESS. --- .../src/catalog/tde_keyring_parse_opts.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index fe3067e35fd88..80bad6d014441 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -135,7 +135,7 @@ static JsonParseErrorType json_kring_object_field_start(void *state, char *fname static JsonParseErrorType json_kring_object_start(void *state); static JsonParseErrorType json_kring_object_end(void *state); -static JsonParseErrorType json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char *value); +static void json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char *value); static char *get_remote_kring_value(const char *url, const char *field_name); static char *get_file_kring_value(const char *path, const char *field_name); @@ -273,7 +273,6 @@ json_kring_object_end(void *state) break; case JK_EXPECT_EXTERN_VAL: { - JsonParseErrorType ret; char *value = NULL; if (!parse->field_type) @@ -312,13 +311,7 @@ json_kring_object_end(void *state) return JSON_INCOMPLETE; } - ret = json_kring_assign_scalar(parse, parse->top_level_field, value); - - if (ret != JSON_SUCCESS) - { - return ret; - } - + json_kring_assign_scalar(parse, parse->top_level_field, value); parse->state = JK_EXPECT_TOP_FIELD; break; } @@ -465,10 +458,12 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) break; } - return json_kring_assign_scalar(parse, *field, value); + json_kring_assign_scalar(parse, *field, value); + + return JSON_SUCCESS; } -static JsonParseErrorType +static void json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char *value) { VaultV2Keyring *vault = (VaultV2Keyring *) parse->provider_opts; @@ -523,8 +518,6 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * default: elog(ERROR, "json keyring: unexpected scalar field %d", field); } - - return JSON_SUCCESS; } static char * From 3d5b3008fec51fb7c984997fe2724e0c040fee93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 14:59:40 +0200 Subject: [PATCH 379/796] Add assert to can-not-happen errors In the key provider options parser. To make the intent behind the code more obvious. Also use elog() for these errors to drive the point home. --- .../pg_tde/src/catalog/tde_keyring_parse_opts.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 80bad6d014441..d660ecaa5c3b4 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -235,8 +235,8 @@ json_kring_object_start(void *state) parse->state = JK_EXPECT_EXTERN_VAL; break; case JK_EXPECT_EXTERN_VAL: - ereport(ERROR, - errmsg("invalid semantic state")); + Assert(0); + elog(ERROR, "invalid semantic state"); break; } @@ -265,8 +265,8 @@ json_kring_object_end(void *state) switch (parse->state) { case JK_EXPECT_TOP_LEVEL_OBJECT: - ereport(ERROR, - errmsg("invalid semantic state")); + Assert(0); + elog(ERROR, "invalid semantic state"); break; case JK_EXPECT_TOP_FIELD: /* We're done parsing the top level object */ @@ -335,8 +335,8 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) switch (parse->state) { case JK_EXPECT_TOP_LEVEL_OBJECT: - ereport(ERROR, - errmsg("invalid semantic state")); + Assert(0); + elog(ERROR, "invalid semantic state"); break; case JK_EXPECT_TOP_FIELD: switch (parse->provider_type) @@ -453,8 +453,8 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) pfree(token); break; default: - ereport(ERROR, - errmsg("invalid token type")); + Assert(0); + elog(ERROR, "invalid token type"); break; } @@ -516,6 +516,7 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * break; default: + Assert(0); elog(ERROR, "json keyring: unexpected scalar field %d", field); } } From db87508427e4a3946902f72bac8f46f4036bfa80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 15:04:22 +0200 Subject: [PATCH 380/796] Replace can-not-happen NULL check with assert To make the intent of the code clear without pretending we can return something else than JSON_SUCCESS. --- contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index d660ecaa5c3b4..320943f0fda56 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -306,10 +306,7 @@ json_kring_object_end(void *state) pfree(parse->field_type); parse->field_type = NULL; - if (value == NULL) - { - return JSON_INCOMPLETE; - } + Assert(value != NULL); json_kring_assign_scalar(parse, parse->top_level_field, value); parse->state = JK_EXPECT_TOP_FIELD; From d41e55254ff1d7a6fdb868903859bf9be63de6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 15:05:26 +0200 Subject: [PATCH 381/796] Clean up some error messages for key provider options parser Add error codes and make them more coherent with other error messages. Also add quotes around strings to make it clearer what the error actually is. --- contrib/pg_tde/expected/key_provider.out | 4 +-- .../src/catalog/tde_keyring_parse_opts.c | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 29a00cd0568c9..c57a555cc79b6 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -3,7 +3,7 @@ SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: parse json keyring config: unexpected field foo +ERROR: unexpected field "foo" for external value "path" SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+---------------+---------------+--------- @@ -73,7 +73,7 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_verify_key(); ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: parse json keyring config: unexpected field foo +ERROR: unexpected field "foo" for external value "path" SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+------------------------------------------------- diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 320943f0fda56..f35adef972285 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -344,7 +344,9 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) else { parse->top_level_field = JK_FIELD_UNKNOWN; - elog(ERROR, "parse file keyring config: unexpected field %s", fname); + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected field \"%s\" for file provider", fname)); } break; @@ -360,7 +362,9 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) else { parse->top_level_field = JK_FIELD_UNKNOWN; - elog(ERROR, "parse json keyring config: unexpected field %s", fname); + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected field \"%s\" for vault-v2 provider", fname)); } break; @@ -378,7 +382,9 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) else { parse->top_level_field = JK_FIELD_UNKNOWN; - elog(ERROR, "parse json keyring config: unexpected field %s", fname); + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected field \"%s\" for vault-v2 provider", fname)); } break; @@ -397,7 +403,9 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) else { parse->extern_field = JK_FIELD_UNKNOWN; - elog(ERROR, "parse json keyring config: unexpected field %s", fname); + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unexpected field \"%s\" for external value \"%s\"", fname, JK_FIELD_NAMES[parse->top_level_field])); } break; } @@ -529,15 +537,15 @@ get_remote_kring_value(const char *url, const char *field_name) if (!curlSetupSession(url, NULL, &outStr)) { - elog(ERROR, "CURL error for remote object %s", field_name); + elog(ERROR, "CURL error for remote object \"%s\"", field_name); } if (curl_easy_perform(keyringCurl) != CURLE_OK) { - elog(ERROR, "HTTP request error for remote object %s", field_name); + elog(ERROR, "HTTP request error for remote object \"%s\"", field_name); } if (curl_easy_getinfo(keyringCurl, CURLINFO_RESPONSE_CODE, &httpCode) != CURLE_OK) { - elog(ERROR, "HTTP error for remote object %s, HTTP code %li", field_name, httpCode); + elog(ERROR, "HTTP error for remote object \"%s\", HTTP code \"%li\"", field_name, httpCode); } /* remove trailing whitespace */ @@ -555,14 +563,14 @@ get_file_kring_value(const char *path, const char *field_name) fd = BasicOpenFile(path, O_RDONLY); if (fd < 0) { - elog(ERROR, "failed to open file %s for %s", path, field_name); + elog(ERROR, "failed to open file \"%s\" for \"%s\"", path, field_name); } val = palloc0(MAX_CONFIG_FILE_DATA_LENGTH); if (pg_pread(fd, val, MAX_CONFIG_FILE_DATA_LENGTH, 0) == -1) { close(fd); - elog(ERROR, "failed to read file %s for %s", path, field_name); + elog(ERROR, "failed to read file \"%s\" for \"%s\"", path, field_name); } /* remove trailing whitespace */ val[strcspn(val, " \t\n\r")] = '\0'; From 44a10fd0f68653de0473113494d2c7aa135e7aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 22 May 2025 13:59:52 +0200 Subject: [PATCH 382/796] Use pg_parse_or_ereport when not compiled as FRONTEND To give the same error messages as any other json parsing issue. Normally the values we get in here would already be valid postgres json values however, so finding a parsing error should be rare. --- .../src/catalog/tde_keyring_parse_opts.c | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index f35adef972285..a2e35b3d2e924 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -26,6 +26,7 @@ #include "common/jsonapi.h" #include "mb/pg_wchar.h" #include "storage/fd.h" +#include "utils/jsonfuncs.h" #include "catalog/tde_keyring.h" #include "keyring/keyring_curl.h" @@ -149,7 +150,6 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch JsonLexContext *jlex; JsonKeyringState parse = {0}; JsonSemAction sem; - JsonParseErrorType jerr; /* Set up parsing context and initial semantic state */ parse.provider_type = provider_type; @@ -172,14 +172,21 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch sem.array_element_end = NULL; sem.scalar = json_kring_scalar; - /* Run the parser */ - jerr = pg_parse_json(jlex, &sem); - if (jerr != JSON_SUCCESS) +#ifndef FRONTEND + pg_parse_json_or_ereport(jlex, &sem); +#else { - ereport(ERROR, - errmsg("parsing of keyring options failed: %s", - json_errdetail(jerr, jlex))); + JsonParseErrorType jerr = pg_parse_json(jlex, &sem); + + if (jerr != JSON_SUCCESS) + { + ereport(ERROR, + errmsg("parsing of keyring options failed: %s", + json_errdetail(jerr, jlex))); + } + } +#endif freeJsonLexContext(jlex); } From 1f6b7ae800c82206484f5ffdff0998c00af0a715 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 22 May 2025 11:47:49 +0200 Subject: [PATCH 383/796] PG-1617 Add tests for temporary tables We were entirely missing any tests for temporary tables. --- .../pg_tde/expected/insert_update_delete.out | 14 ++++ .../pg_tde/expected/pg_tde_is_encrypted.out | 81 +++++++++++-------- contrib/pg_tde/sql/insert_update_delete.sql | 12 +++ contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 47 ++++++----- 4 files changed, 102 insertions(+), 52 deletions(-) diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index 144c5bc49e6ad..b97a7affe3aab 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -91,4 +91,18 @@ SELECT * FROM albums; (12 rows) DROP TABLE albums; +CREATE TEMPORARY TABLE animals ( + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + type TEXT UNIQUE, + num INT +) USING tde_heap; +INSERT INTO animals (type, num) VALUES ('cows', 3), ('pigs', 11); +SELECT * FROM animals ORDER BY id; + id | type | num +----+------+----- + 1 | cows | 3 + 2 | pigs | 11 +(2 rows) + +DROP TABLE animals; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index 5c0bea8f4df05..7a1a94c01e0a1 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -11,52 +11,65 @@ SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); (1 row) -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING tde_heap; -CREATE TABLE test_norm( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING heap; -CREATE TABLE test_part( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) PARTITION BY RANGE (id) USING tde_heap; -SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('test_enc', 'test_norm', 'test_part') ORDER BY relname; - relname | amname ------------+---------- - test_enc | tde_heap - test_norm | heap - test_part | tde_heap -(3 rows) +CREATE TABLE test_enc ( + id SERIAL, + PRIMARY KEY (id) +) USING tde_heap; +CREATE TABLE test_norm ( + id SERIAL, + PRIMARY KEY (id) +) USING heap; +CREATE TABLE test_part ( + id SERIAL, + PRIMARY KEY (id) +) PARTITION BY RANGE (id) USING tde_heap; +CREATE TEMP TABLE test_temp_enc ( + id SERIAL, + PRIMARY KEY (id) +) USING tde_heap; +CREATE TEMP TABLE test_temp_norm ( + id SERIAL, + PRIMARY KEY (id) +) USING heap; +SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('test_enc', 'test_norm', 'test_part', 'test_temp_enc', 'test_temp_norm') ORDER BY relname; + relname | amname +----------------+---------- + test_enc | tde_heap + test_norm | heap + test_part | tde_heap + test_temp_enc | tde_heap + test_temp_norm | heap +(5 rows) -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc'), ('test_norm'), ('test_part')) AS r (relname) ORDER BY relname; - relname | pg_tde_is_encrypted ------------+--------------------- - test_enc | t - test_norm | f - test_part | -(3 rows) +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc'), ('test_norm'), ('test_part'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; + relname | pg_tde_is_encrypted +----------------+--------------------- + test_enc | t + test_norm | f + test_part | + test_temp_enc | t + test_temp_norm | f +(5 rows) -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_id_seq'), ('test_norm_id_seq'), ('test_part_id_seq')) AS r (relname) ORDER BY relname; +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_id_seq'), ('test_norm_id_seq'), ('test_part_id_seq'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; relname | pg_tde_is_encrypted ------------------+--------------------- test_enc_id_seq | t test_norm_id_seq | f test_part_id_seq | t -(3 rows) + test_temp_enc | t + test_temp_norm | f +(5 rows) -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('test_norm_pkey'), ('test_part_pkey')) AS r (relname) ORDER BY relname; +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('test_norm_pkey'), ('test_part_pkey'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; relname | pg_tde_is_encrypted ----------------+--------------------- test_enc_pkey | t test_norm_pkey | f test_part_pkey | -(3 rows) + test_temp_enc | t + test_temp_norm | f +(5 rows) SELECT pg_tde_is_encrypted(NULL); pg_tde_is_encrypted @@ -71,6 +84,8 @@ SELECT key_provider_id, key_provider_name, key_name 1 | file-vault | test-db-key (1 row) +DROP TABLE test_temp_norm; +DROP TABLE test_temp_enc; DROP TABLE test_part; DROP TABLE test_norm; DROP TABLE test_enc; diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index a586bf302f451..9efd91a809dc0 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -38,4 +38,16 @@ UPDATE albums SET released='2020-04-01' WHERE id=2; SELECT * FROM albums; DROP TABLE albums; + +CREATE TEMPORARY TABLE animals ( + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + type TEXT UNIQUE, + num INT +) USING tde_heap; + +INSERT INTO animals (type, num) VALUES ('cows', 3), ('pigs', 11); +SELECT * FROM animals ORDER BY id; + +DROP TABLE animals; + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index b030661962e03..f1fdede1c93bd 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -3,37 +3,46 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING tde_heap; +CREATE TABLE test_enc ( + id SERIAL, + PRIMARY KEY (id) +) USING tde_heap; -CREATE TABLE test_norm( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING heap; +CREATE TABLE test_norm ( + id SERIAL, + PRIMARY KEY (id) +) USING heap; -CREATE TABLE test_part( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) PARTITION BY RANGE (id) USING tde_heap; +CREATE TABLE test_part ( + id SERIAL, + PRIMARY KEY (id) +) PARTITION BY RANGE (id) USING tde_heap; -SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('test_enc', 'test_norm', 'test_part') ORDER BY relname; +CREATE TEMP TABLE test_temp_enc ( + id SERIAL, + PRIMARY KEY (id) +) USING tde_heap; -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc'), ('test_norm'), ('test_part')) AS r (relname) ORDER BY relname; +CREATE TEMP TABLE test_temp_norm ( + id SERIAL, + PRIMARY KEY (id) +) USING heap; -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_id_seq'), ('test_norm_id_seq'), ('test_part_id_seq')) AS r (relname) ORDER BY relname; +SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('test_enc', 'test_norm', 'test_part', 'test_temp_enc', 'test_temp_norm') ORDER BY relname; -SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('test_norm_pkey'), ('test_part_pkey')) AS r (relname) ORDER BY relname; +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc'), ('test_norm'), ('test_part'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; + +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_id_seq'), ('test_norm_id_seq'), ('test_part_id_seq'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; + +SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('test_norm_pkey'), ('test_part_pkey'), ('test_temp_enc'), ('test_temp_norm')) AS r (relname) ORDER BY relname; SELECT pg_tde_is_encrypted(NULL); SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +DROP TABLE test_temp_norm; +DROP TABLE test_temp_enc; DROP TABLE test_part; DROP TABLE test_norm; DROP TABLE test_enc; From c5ca29864a7cc07b121923ca410936bcf404be25 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 21 May 2025 20:00:17 +0200 Subject: [PATCH 384/796] PG-1617 Stop storing WAL keys in the key cache The only reason we stored them in the key cache was in an attempt to protect them from being swapped but since we do not care about that in other contexts plus we plan to remove the cache we should just stop storing them in the cache. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 34 +++---------------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 10 +++--- .../pg_tde/src/include/access/pg_tde_tdemap.h | 9 ++--- 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 3d3781b6615b3..670041eebdee8 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -128,7 +128,6 @@ static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalK static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); -static void update_wal_keys_cache(void); InternalKey * pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator) @@ -1144,24 +1143,6 @@ pg_tde_get_wal_cache_keys(void) return tde_wal_key_cache; } -static void -update_wal_keys_cache(void) -{ - WALKeyCacheRec *wal_rec = tde_wal_key_cache; - RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - - for (int i = 0; i < tde_rel_key_cache.len && wal_rec; i++) - { - RelKeyCacheRec *rec = tde_rel_key_cache.data + i; - - if (RelFileLocatorEquals(rec->locator, rlocator)) - { - wal_rec->key = &rec->key; - wal_rec = wal_rec->next; - } - } -} - InternalKey * pg_tde_read_last_wal_key(void) { @@ -1240,14 +1221,12 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) */ if (keys_count == 0) { - InternalKey *cached_key; WALKeyCacheRec *wal_rec; InternalKey stub_key = { .start_lsn = InvalidXLogRecPtr, }; - cached_key = pg_tde_put_key_into_cache(&rlocator, &stub_key); - wal_rec = pg_tde_add_wal_key_to_cache(cached_key, InvalidXLogRecPtr); + wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); LWLockRelease(lock_pk); close(fd); @@ -1268,12 +1247,12 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) map_entry.enc_key.start_lsn >= start_lsn) { InternalKey *rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); - InternalKey *cached_key = pg_tde_put_key_into_cache(&rlocator, rel_key_data); WALKeyCacheRec *wal_rec; + wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data, map_entry.enc_key.start_lsn); + pfree(rel_key_data); - wal_rec = pg_tde_add_wal_key_to_cache(cached_key, map_entry.enc_key.start_lsn); if (!return_wal_rec) return_wal_rec = wal_rec; } @@ -1285,7 +1264,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) } static WALKeyCacheRec * -pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn) +pg_tde_add_wal_key_to_cache(InternalKey *key, XLogRecPtr start_lsn) { WALKeyCacheRec *wal_rec; #ifndef FRONTEND @@ -1300,7 +1279,7 @@ pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn) wal_rec->start_lsn = start_lsn; wal_rec->end_lsn = MaxXLogRecPtr; - wal_rec->key = cached_key; + wal_rec->key = *key; wal_rec->crypt_ctx = NULL; if (!tde_wal_key_last_rec) { @@ -1397,9 +1376,6 @@ pg_tde_put_key_into_cache(const RelFileLocator *rlocator, InternalKey *key) elog(WARNING, "could not mlock internal key cache pages: %m"); tde_rel_key_cache.cap = (size - 1) / sizeof(RelKeyCacheRec); - - /* update wal key pointers after moving the cache */ - update_wal_keys_cache(); } rec = tde_rel_key_cache.data + tde_rel_key_cache.len; diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index ab95db256f6ea..0e8674a73bbe7 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -314,11 +314,11 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, elog(DEBUG1, "WAL key %X/%X-%X/%X, encrypted: %s", LSN_FORMAT_ARGS(curr_key->start_lsn), LSN_FORMAT_ARGS(curr_key->end_lsn), - curr_key->key->type & TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); + curr_key->key.type & TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); #endif - if (curr_key->key->start_lsn != InvalidXLogRecPtr && - (curr_key->key->type & TDE_KEY_TYPE_WAL_ENCRYPTED)) + if (curr_key->key.start_lsn != InvalidXLogRecPtr && + (curr_key->key.type & TDE_KEY_TYPE_WAL_ENCRYPTED)) { /* * Check if the key's range overlaps with the buffer's and decypt @@ -334,7 +334,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, Assert(dec_off >= offset); - CalcXLogPageIVPrefix(tli, segno, curr_key->key->base_iv, iv_prefix); + CalcXLogPageIVPrefix(tli, segno, curr_key->key.base_iv, iv_prefix); /* We have reached the end of the segment */ if (dec_end == 0) @@ -349,7 +349,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, dec_off, dec_off - offset, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); #endif pg_tde_stream_crypt(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, - curr_key->key, &curr_key->crypt_ctx); + &curr_key->key, &curr_key->crypt_ctx); if (dec_off + dec_sz == offset) { diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 88a309a857184..3686124ab6792 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -66,20 +66,15 @@ typedef struct XLogRelKey } XLogRelKey; /* - * WALKeyCacheRec is built on top of the InternalKeys cache. We still don't - * want to key data be swapped out to the disk (implemented in the InternalKeys - * cache) but we need extra information and the ability to have and reference - * a sequence of keys. - * * TODO: For now it's a simple linked list which is no good. So consider having - * dedicated WAL keys cache inside some proper data structure. + * dedicated WAL keys cache inside some proper data structure. */ typedef struct WALKeyCacheRec { XLogRecPtr start_lsn; XLogRecPtr end_lsn; - InternalKey *key; + InternalKey key; void *crypt_ctx; struct WALKeyCacheRec *next; From a6d47dfb42a566c00034740b78e0832a08007c81 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 21 May 2025 22:07:57 +0200 Subject: [PATCH 385/796] PG-1617 Stop using cache for keys of temporary tables Since are going to remove the key cache we need to make sure to figure out another solution for the keys of temporary tables. The new solution is to store them in a per-backend hash table from which we delete the key when we drop the relation. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 80 +++++++++++++++++-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 3 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 4 +- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 670041eebdee8..cb741b7e5e64e 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -20,6 +20,7 @@ #include "access/xlog_internal.h" #include "access/xloginsert.h" #include "utils/builtins.h" +#include "utils/hsearch.h" #include "miscadmin.h" #include "access/pg_tde_tdemap.h" @@ -99,6 +100,24 @@ RelKeyCache tde_rel_key_cache = { .cap = 0, }; +typedef struct +{ + RelFileLocator rel; + InternalKey key; +} TempRelKeyEntry; + +#ifndef FRONTEND + +/* Arbitrarily picked small number of temporary relations */ +#define INIT_TEMP_RELS 16 + +/* + * Each backend has a hashtable that stores the keys for all temporary tables. + */ +static HTAB *TempRelKeys = NULL; + +#endif + /* * TODO: WAL should have its own RelKeyCache */ @@ -125,6 +144,7 @@ static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDES static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key); +static void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); @@ -141,11 +161,29 @@ pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator) static InternalKey * pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator) { - InternalKey int_key; + TempRelKeyEntry *entry; + bool found; + + if (TempRelKeys == NULL) + { + HASHCTL ctl; + + ctl.keysize = sizeof(RelFileLocator); + ctl.entrysize = sizeof(TempRelKeyEntry); + TempRelKeys = hash_create("pg_tde temporary relation keys", + INIT_TEMP_RELS, + &ctl, + HASH_ELEM | HASH_BLOBS); + } + + entry = (TempRelKeyEntry *) hash_search(TempRelKeys, + newrlocator, + HASH_ENTER, &found); + Assert(!found); - pg_tde_generate_internal_key(&int_key, TDE_KEY_TYPE_SMGR); + pg_tde_generate_internal_key(&entry->key, TDE_KEY_TYPE_SMGR); - return pg_tde_put_key_into_cache(newrlocator, &int_key); + return &entry->key; } static InternalKey * @@ -274,6 +312,18 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat LWLockRelease(tde_lwlock_enc_keys()); } +void +DeleteSMGRRelationKey(RelFileLocatorBackend rel) +{ + if (RelFileLocatorBackendIsTemp(rel)) + { + if (TempRelKeys) + hash_search(TempRelKeys, &rel.locator, HASH_REMOVE, NULL); + } + else + pg_tde_free_key_map_entry(&rel.locator); +} + /* * Deletes the key map file for a given database. */ @@ -491,7 +541,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ * This fucntion is called by the pg_tde SMGR when storage is unlinked on * transaction commit/abort. */ -void +static void pg_tde_free_key_map_entry(const RelFileLocator *rlocator) { char db_map_path[MAXPGPATH]; @@ -1047,6 +1097,24 @@ pg_tde_get_principal_key_info(Oid dbOid) return signed_key_info; } +static InternalKey * +pg_tde_get_temporary_rel_key(const RelFileLocator *rel) +{ +#ifndef FRONTEND + TempRelKeyEntry *entry; + + if (TempRelKeys == NULL) + return NULL; + + entry = hash_search(TempRelKeys, rel, HASH_FIND, NULL); + + if (entry) + return &entry->key; +#endif + + return NULL; +} + /* * Figures out whether a relation is encrypted or not, but without trying to * decrypt the key if it is. This also means that this function cannot push the @@ -1062,7 +1130,7 @@ IsSMGRRelationEncrypted(RelFileLocatorBackend rel) Assert(rel.locator.relNumber != InvalidRelFileNumber); if (RelFileLocatorBackendIsTemp(rel)) - return pg_tde_get_key_from_cache(&rel.locator, TDE_KEY_TYPE_SMGR) != NULL; + return pg_tde_get_temporary_rel_key(&rel.locator) != NULL; else if (pg_tde_get_key_from_cache(&rel.locator, TDE_KEY_TYPE_SMGR)) return true; @@ -1090,7 +1158,7 @@ GetSMGRRelationKey(RelFileLocatorBackend rel) Assert(rel.locator.relNumber != InvalidRelFileNumber); if (RelFileLocatorBackendIsTemp(rel)) - return pg_tde_get_key_from_cache(&rel.locator, TDE_KEY_TYPE_SMGR); + return pg_tde_get_temporary_rel_key(&rel.locator); else { InternalKey *key; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 3686124ab6792..7d2de5b487992 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -90,7 +90,6 @@ extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); extern void pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); -extern void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); #define PG_TDE_MAP_FILENAME "%d_keys" @@ -102,6 +101,8 @@ pg_tde_set_db_file_path(Oid dbOid, char *path) extern bool IsSMGRRelationEncrypted(RelFileLocatorBackend rel); extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel); +extern void DeleteSMGRRelationKey(RelFileLocatorBackend rel); + extern int pg_tde_count_relations(Oid dbOid); extern void pg_tde_delete_tde_files(Oid dbOid); diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 40c535537227d..ef6850e4bff13 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -161,8 +161,8 @@ tde_mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo) */ if (forknum == MAIN_FORKNUM || forknum == InvalidForkNumber) { - if (!RelFileLocatorBackendIsTemp(rlocator) && IsSMGRRelationEncrypted(rlocator)) - pg_tde_free_key_map_entry(&rlocator.locator); + if (IsSMGRRelationEncrypted(rlocator)) + DeleteSMGRRelationKey(rlocator); } } From eafc34e335e5c0dd44baf34b12b8875bb071c624 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 22 May 2025 12:16:10 +0200 Subject: [PATCH 386/796] PG-1617 Remove the relation key cache Since the relation keys are cached in the SMGR cache (or arguably the relation cache) the double layers of caching only complicated the code and caused an issue with possible key re-use or even data corruption on oid wraparound. This will slow down some code paths like pg_tde_is_encrypted() but the code simplifcation and fixing of the oid wraparound bug makes it worth it and some of that performance loss can be added back in future commits. This loses us the mlock() protection of the relation keys in the cache but since the keys in the SMGR were not protected anyway this is not a significant loss. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 213 +++------------------- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 40 ++-- 2 files changed, 48 insertions(+), 205 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index cb741b7e5e64e..f5c8fb23552f3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -32,7 +32,6 @@ #include #include -#include #include #include "pg_tde_defines.h" @@ -67,39 +66,6 @@ typedef struct TDEFileHeader TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; -typedef struct RelKeyCacheRec -{ - RelFileLocator locator; - InternalKey key; -} RelKeyCacheRec; - -/* - * Relation keys cache. - * - * This is a slice backed by memory `*data`. Initially, we allocate one memory - * page (usually 4Kb). We reallocate it by adding another page when we run out - * of space. This memory is locked in the RAM so it won't be paged to the swap - * (we don't want decrypted keys on disk). We do allocations in mem pages as - * these are the units `mlock()` operations are performed in. - * - * Currently, the cache can only grow (no eviction). The data is located in - * TopMemoryContext hence being wiped when the process exits, as well as memory - * is being unlocked by OS. - */ -typedef struct RelKeyCache -{ - RelKeyCacheRec *data; /* must be a multiple of a memory page - * (usually 4Kb) */ - int len; /* num of RelKeyCacheRecs currenty in cache */ - int cap; /* max amount of RelKeyCacheRec data can fit */ -} RelKeyCache; - -RelKeyCache tde_rel_key_cache = { - .data = NULL, - .len = 0, - .cap = 0, -}; - typedef struct { RelFileLocator rel; @@ -118,9 +84,6 @@ static HTAB *TempRelKeys = NULL; #endif -/* - * TODO: WAL should have its own RelKeyCache - */ static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; @@ -132,9 +95,7 @@ static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHea static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset); static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); -static InternalKey *pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type); static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); -static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, InternalKey *key); #ifndef FRONTEND static InternalKey *pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator); @@ -161,9 +122,12 @@ pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator) static InternalKey * pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator) { + InternalKey *rel_key_data = palloc_object(InternalKey); TempRelKeyEntry *entry; bool found; + pg_tde_generate_internal_key(rel_key_data, TDE_KEY_TYPE_SMGR); + if (TempRelKeys == NULL) { HASHCTL ctl; @@ -181,22 +145,22 @@ pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator) HASH_ENTER, &found); Assert(!found); - pg_tde_generate_internal_key(&entry->key, TDE_KEY_TYPE_SMGR); + entry->key = *rel_key_data; - return &entry->key; + return rel_key_data; } static InternalKey * pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) { - InternalKey rel_key_data; + InternalKey *rel_key_data = palloc_object(InternalKey); TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); XLogRelKey xlrec = { .rlocator = *newrlocator, }; - pg_tde_generate_internal_key(&rel_key_data, TDE_KEY_TYPE_SMGR); + pg_tde_generate_internal_key(rel_key_data, TDE_KEY_TYPE_SMGR); LWLockAcquire(lock_pk, LW_EXCLUSIVE); principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); @@ -207,7 +171,7 @@ pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) errhint("create one using pg_tde_set_key before using encrypted tables")); } - pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key); + pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); LWLockRelease(lock_pk); /* @@ -218,7 +182,7 @@ pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) XLogRegisterData((char *) &xlrec, sizeof(xlrec)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); - return pg_tde_put_key_into_cache(newrlocator, &rel_key_data); + return rel_key_data; } void @@ -281,13 +245,12 @@ tde_sprint_key(InternalKey *k) } /* - * Generates a new internal key for WAL and adds it to the _dat file. It doesn't - * add unecnrypted key into cache but rather sets it in `rel_key_data`. + * Generates a new internal key for WAL and adds it to the _dat file. * * We have a special function for WAL as it is being called during recovery - * (start) so there should be no XLog records, aquired locks, and reads from - * cache. The key is always created with start_lsn = InvalidXLogRecPtr. Which - * will be updated with the actual lsn by the first WAL write. + * start so there should be no XLog records and aquired locks. The key is + * always created with start_lsn = InvalidXLogRecPtr. Which will be updated + * with the actual lsn by the first WAL write. */ void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 entry_type) @@ -317,8 +280,8 @@ DeleteSMGRRelationKey(RelFileLocatorBackend rel) { if (RelFileLocatorBackendIsTemp(rel)) { - if (TempRelKeys) - hash_search(TempRelKeys, &rel.locator, HASH_REMOVE, NULL); + Assert(TempRelKeys); + hash_search(TempRelKeys, &rel.locator, HASH_REMOVE, NULL); } else pg_tde_free_key_map_entry(&rel.locator); @@ -1109,7 +1072,12 @@ pg_tde_get_temporary_rel_key(const RelFileLocator *rel) entry = hash_search(TempRelKeys, rel, HASH_FIND, NULL); if (entry) - return &entry->key; + { + InternalKey *key = palloc_object(InternalKey); + + *key = entry->key; + return key; + } #endif return NULL; @@ -1117,8 +1085,7 @@ pg_tde_get_temporary_rel_key(const RelFileLocator *rel) /* * Figures out whether a relation is encrypted or not, but without trying to - * decrypt the key if it is. This also means that this function cannot push the - * key to cache. + * decrypt the key if it is. */ bool IsSMGRRelationEncrypted(RelFileLocatorBackend rel) @@ -1130,9 +1097,11 @@ IsSMGRRelationEncrypted(RelFileLocatorBackend rel) Assert(rel.locator.relNumber != InvalidRelFileNumber); if (RelFileLocatorBackendIsTemp(rel)) - return pg_tde_get_temporary_rel_key(&rel.locator) != NULL; - else if (pg_tde_get_key_from_cache(&rel.locator, TDE_KEY_TYPE_SMGR)) - return true; +#ifndef FRONTEND + return TempRelKeys && hash_search(TempRelKeys, &rel.locator, HASH_FIND, NULL); +#else + return false; +#endif pg_tde_set_db_file_path(rel.locator.dbOid, db_map_path); @@ -1149,8 +1118,6 @@ IsSMGRRelationEncrypted(RelFileLocatorBackend rel) /* * Returns TDE key for a given relation. - * First it looks in a cache. If nothing found in the cache, it reads data from - * the tde fork file and populates cache. */ InternalKey * GetSMGRRelationKey(RelFileLocatorBackend rel) @@ -1160,40 +1127,7 @@ GetSMGRRelationKey(RelFileLocatorBackend rel) if (RelFileLocatorBackendIsTemp(rel)) return pg_tde_get_temporary_rel_key(&rel.locator); else - { - InternalKey *key; - - key = pg_tde_get_key_from_cache(&rel.locator, TDE_KEY_TYPE_SMGR); - if (key) - return key; - - key = pg_tde_get_key_from_file(&rel.locator, TDE_KEY_TYPE_SMGR); - if (key) - { - InternalKey *cached_key = pg_tde_put_key_into_cache(&rel.locator, key); - - pfree(key); - return cached_key; - } - - return NULL; - } -} - -static InternalKey * -pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type) -{ - for (int i = 0; i < tde_rel_key_cache.len; i++) - { - RelKeyCacheRec *rec = tde_rel_key_cache.data + i; - - if (RelFileLocatorEquals(rec->locator, *rlocator) && rec->key.type & key_type) - { - return &rec->key; - } - } - - return NULL; + return pg_tde_get_key_from_file(&rel.locator, TDE_KEY_TYPE_SMGR); } /* @@ -1363,94 +1297,3 @@ pg_tde_add_wal_key_to_cache(InternalKey *key, XLogRecPtr start_lsn) return wal_rec; } - -/* - * Add key to cache. See comments on `RelKeyCache`. - */ -static InternalKey * -pg_tde_put_key_into_cache(const RelFileLocator *rlocator, InternalKey *key) -{ - static long pageSize = 0; - RelKeyCacheRec *rec; - MemoryContext oldCtx; - - if (pageSize == 0) - { -#ifndef _SC_PAGESIZE - pageSize = getpagesize(); -#else - pageSize = sysconf(_SC_PAGESIZE); -#endif - } - - if (tde_rel_key_cache.data == NULL) - { -#ifndef FRONTEND - oldCtx = MemoryContextSwitchTo(TopMemoryContext); - tde_rel_key_cache.data = palloc_aligned(pageSize, pageSize, MCXT_ALLOC_ZERO); - MemoryContextSwitchTo(oldCtx); -#else - tde_rel_key_cache.data = aligned_alloc(pageSize, pageSize); - memset(tde_rel_key_cache.data, 0, pageSize); -#endif - - if (mlock(tde_rel_key_cache.data, pageSize) == -1) - elog(ERROR, "could not mlock internal key initial cache page: %m"); - - tde_rel_key_cache.len = 0; - tde_rel_key_cache.cap = (pageSize - 1) / sizeof(RelKeyCacheRec); - } - - /* - * Add another mem page if there is no more room left for another key. We - * allocate `current_memory_size` + 1 page and copy data there. - */ - if (tde_rel_key_cache.len == tde_rel_key_cache.cap) - { - size_t size; - size_t old_size; - RelKeyCacheRec *cachePage; - - old_size = TYPEALIGN(pageSize, tde_rel_key_cache.cap * sizeof(RelKeyCacheRec)); - - /* - * TODO: consider some formula for less allocations when caching a - * lot of objects. But on the other, hand it'll use more memory... - * E.g.: if (old_size < 0x8000) size = old_size * 2; else size = - * TYPEALIGN(pageSize, old_size + ((old_size + 3*256) >> 2)); - * - */ - size = old_size + pageSize; - -#ifndef FRONTEND - oldCtx = MemoryContextSwitchTo(TopMemoryContext); - cachePage = palloc_aligned(size, pageSize, MCXT_ALLOC_ZERO); - MemoryContextSwitchTo(oldCtx); -#else - cachePage = aligned_alloc(pageSize, size); - memset(cachePage, 0, size); -#endif - - memcpy(cachePage, tde_rel_key_cache.data, old_size); - - explicit_bzero(tde_rel_key_cache.data, old_size); - if (munlock(tde_rel_key_cache.data, old_size) == -1) - elog(WARNING, "could not munlock internal key cache pages: %m"); - pfree(tde_rel_key_cache.data); - - tde_rel_key_cache.data = cachePage; - - if (mlock(tde_rel_key_cache.data, size) == -1) - elog(WARNING, "could not mlock internal key cache pages: %m"); - - tde_rel_key_cache.cap = (size - 1) / sizeof(RelKeyCacheRec); - } - - rec = tde_rel_key_cache.data + tde_rel_key_cache.len; - - rec->locator = *rlocator; - rec->key = *key; - tde_rel_key_cache.len++; - - return &rec->key; -} diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index ef6850e4bff13..0f1edddd469e1 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -86,8 +86,7 @@ tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocat .backend = smgr_rlocator->backend, }; - /* Actually get the key here to ensure result is cached. */ - return GetSMGRRelationKey(old_smgr_locator) != 0; + return IsSMGRRelationEncrypted(old_smgr_locator); } } @@ -106,19 +105,19 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, } else { - InternalKey *int_key; unsigned char *local_blocks = palloc(BLCKSZ * (nblocks + 1)); unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); void **local_buffers = palloc_array(void *, nblocks); if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - tdereln->relKey = *tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + + tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; + pfree(int_key); } - int_key = &tdereln->relKey; - for (int i = 0; i < nblocks; ++i) { BlockNumber bn = blocknum + i; @@ -126,9 +125,9 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, local_buffers[i] = &local_blocks_aligned[i * BLCKSZ]; - CalcBlockIv(forknum, bn, int_key->base_iv, iv); + CalcBlockIv(forknum, bn, tdereln->relKey.base_iv, iv); - AesEncrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); + AesEncrypt(tdereln->relKey.key, iv, ((unsigned char **) buffers)[i], BLCKSZ, local_buffers[i]); } mdwritev(reln, forknum, blocknum, @@ -178,22 +177,22 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, } else { - InternalKey *int_key; unsigned char *local_blocks = palloc(BLCKSZ * (1 + 1)); unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); unsigned char iv[16]; if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - tdereln->relKey = *tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + + tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; + pfree(int_key); } - int_key = &tdereln->relKey; + CalcBlockIv(forknum, blocknum, tdereln->relKey.base_iv, iv); - CalcBlockIv(forknum, blocknum, int_key->base_iv, iv); - - AesEncrypt(int_key->key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); + AesEncrypt(tdereln->relKey.key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); mdextend(reln, forknum, blocknum, local_blocks_aligned, skipFsync); @@ -206,7 +205,6 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, void **buffers, BlockNumber nblocks) { TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; - InternalKey *int_key; mdreadv(reln, forknum, blocknum, buffers, nblocks); @@ -214,12 +212,13 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, return; else if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - tdereln->relKey = *tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + + tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; + pfree(int_key); } - int_key = &tdereln->relKey; - for (int i = 0; i < nblocks; ++i) { bool allZero = true; @@ -246,9 +245,9 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (allZero) continue; - CalcBlockIv(forknum, bn, int_key->base_iv, iv); + CalcBlockIv(forknum, bn, tdereln->relKey.base_iv, iv); - AesDecrypt(int_key->key, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); + AesDecrypt(tdereln->relKey.key, iv, ((unsigned char **) buffers)[i], BLCKSZ, ((unsigned char **) buffers)[i]); } } @@ -292,6 +291,7 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool { tdereln->encryption_status = RELATION_KEY_AVAILABLE; tdereln->relKey = *key; + pfree(key); } else { From 7aa9f960531ce4ee1aba3e183cc12bfe596bc1f5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 16:09:05 +0200 Subject: [PATCH 387/796] Use palloc_aligned() instead of doing manual alignment The palloc_aligned() function has magic which makes it possible to call pfree() on the aligned pointer. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 0f1edddd469e1..ecf9a21e82015 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -105,8 +105,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, } else { - unsigned char *local_blocks = palloc(BLCKSZ * (nblocks + 1)); - unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); + unsigned char *local_blocks = palloc_aligned(BLCKSZ * nblocks, PG_IO_ALIGN_SIZE, 0); void **local_buffers = palloc_array(void *, nblocks); if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) @@ -123,7 +122,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber bn = blocknum + i; unsigned char iv[16]; - local_buffers[i] = &local_blocks_aligned[i * BLCKSZ]; + local_buffers[i] = &local_blocks[i * BLCKSZ]; CalcBlockIv(forknum, bn, tdereln->relKey.base_iv, iv); @@ -177,8 +176,7 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, } else { - unsigned char *local_blocks = palloc(BLCKSZ * (1 + 1)); - unsigned char *local_blocks_aligned = (unsigned char *) TYPEALIGN(PG_IO_ALIGN_SIZE, local_blocks); + unsigned char *local_blocks = palloc_aligned(BLCKSZ, PG_IO_ALIGN_SIZE, 0); unsigned char iv[16]; if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) @@ -192,9 +190,9 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, CalcBlockIv(forknum, blocknum, tdereln->relKey.base_iv, iv); - AesEncrypt(tdereln->relKey.key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks_aligned); + AesEncrypt(tdereln->relKey.key, iv, ((unsigned char *) buffer), BLCKSZ, local_blocks); - mdextend(reln, forknum, blocknum, local_blocks_aligned, skipFsync); + mdextend(reln, forknum, blocknum, local_blocks, skipFsync); pfree(local_blocks); } From 5e00d3aa5ae5f2b4b0d30dd14c7c444a86e603bc Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Tue, 13 May 2025 12:24:59 +0200 Subject: [PATCH 388/796] Remove 'debug' option from CI matrix There is no need to run both 'debug' and 'debugoptimized' variants in CI. 'debugoptimized' gives us everything that needed for CI run. --- .github/workflows/psp-matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/psp-matrix.yml b/.github/workflows/psp-matrix.yml index 4c1f3319e5fbb..635ab9ed78454 100644 --- a/.github/workflows/psp-matrix.yml +++ b/.github/workflows/psp-matrix.yml @@ -13,7 +13,7 @@ jobs: matrix: os: ['ubuntu-22.04'] compiler: [gcc, clang] - build_type: [debug,debugoptimized] + build_type: [debugoptimized] build_script: [make, meson] uses: ./.github/workflows/psp-reusable.yml with: From 2da2bf5105be2f90975c000d8e8b62203c165741 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 24 May 2025 13:28:25 +0200 Subject: [PATCH 389/796] Fix typo in names of IV and tag length defines --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 ++-- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index f5c8fb23552f3..239ebb45b71f1 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -389,7 +389,7 @@ pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const { signed_key_info->data = principal_key->keyInfo; - if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_EMPTY_IV_SIZE)) + if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE)) ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -405,7 +405,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ map_entry->flags = rel_key_data->type; map_entry->enc_key = *rel_key_data; - if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_EMPTY_IV_SIZE)) + if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 7d2de5b487992..a12ea3699fdf2 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -38,8 +38,8 @@ typedef struct InternalKey (((key)->type & TDE_KEY_TYPE_WAL_UNENCRYPTED) != 0 || \ ((key)->type & TDE_KEY_TYPE_WAL_ENCRYPTED) != 0) -#define MAP_ENTRY_EMPTY_IV_SIZE 16 -#define MAP_ENTRY_EMPTY_AEAD_TAG_SIZE 16 +#define MAP_ENTRY_IV_SIZE 16 +#define MAP_ENTRY_AEAD_TAG_SIZE 16 typedef struct { @@ -56,8 +56,8 @@ typedef struct TDEMapEntry uint32 flags; InternalKey enc_key; /* IV and tag used when encrypting the key itself */ - unsigned char entry_iv[MAP_ENTRY_EMPTY_IV_SIZE]; - unsigned char aead_tag[MAP_ENTRY_EMPTY_AEAD_TAG_SIZE]; + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; } TDEMapEntry; typedef struct XLogRelKey From 8d7a7f8bd949797e47b2839ff9765edca783d23c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 24 May 2025 13:35:43 +0200 Subject: [PATCH 390/796] Use IV and tag length defines everywhere There were several places where 16 was hardcoded which was very inconsistent. Also fix GCM function so it take all lengths as arguments. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 28 ++++++++++++++++--- contrib/pg_tde/src/encryption/enc_aes.c | 12 ++++---- .../pg_tde/src/include/access/pg_tde_tdemap.h | 4 +-- .../pg_tde/src/include/encryption/enc_aes.h | 4 +-- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 239ebb45b71f1..d06043ba698fb 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -394,7 +394,12 @@ pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - AesGcmEncrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag); + AesGcmEncrypt(principal_key->keyData, + signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), + NULL, 0, + NULL, + signed_key_info->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } static void @@ -410,7 +415,12 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - AesGcmEncrypt(principal_key->keyData, map_entry->entry_iv, (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), rel_key_data->key, INTERNAL_KEY_LEN, map_entry->enc_key.key, map_entry->aead_tag); + AesGcmEncrypt(principal_key->keyData, + map_entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), + rel_key_data->key, INTERNAL_KEY_LEN, + map_entry->enc_key.key, + map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } static void @@ -883,7 +893,12 @@ pg_tde_count_relations(Oid dbOid) bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) { - return AesGcmDecrypt(principal_key->keyData, signed_key_info->sign_iv, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, NULL, signed_key_info->aead_tag); + return AesGcmDecrypt(principal_key->keyData, + signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), + NULL, 0, + NULL, + signed_key_info->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } static InternalKey * @@ -895,7 +910,12 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) *rel_key_data = map_entry->enc_key; - if (!AesGcmDecrypt(principal_key->keyData, map_entry->entry_iv, (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), map_entry->enc_key.key, INTERNAL_KEY_LEN, rel_key_data->key, map_entry->aead_tag)) + if (!AesGcmDecrypt(principal_key->keyData, + map_entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), + map_entry->enc_key.key, INTERNAL_KEY_LEN, + rel_key_data->key, + map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index d8b649407b5a0..003f6050112c2 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -133,7 +133,7 @@ AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned cha } void -AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag) +AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) { int out_len; int out_len_final; @@ -153,7 +153,7 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned ereport(ERROR, errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) == 0) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) == 0) ereport(ERROR, errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -173,7 +173,7 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned ereport(ERROR, errmsg("EVP_CipherFinal_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag) == 0) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_len, tag) == 0) ereport(ERROR, errmsg("EVP_CTRL_GCM_GET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -189,7 +189,7 @@ AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned } bool -AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag) +AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len) { int out_len; int out_len_final; @@ -208,7 +208,7 @@ AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned ereport(ERROR, errmsg("EVP_CIPHER_CTX_set_padding failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) == 0) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) == 0) ereport(ERROR, errmsg("EVP_CTRL_GCM_SET_IVLEN failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); @@ -216,7 +216,7 @@ AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned ereport(ERROR, errmsg("EVP_EncryptInit_ex failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag) == 0) + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag) == 0) ereport(ERROR, errmsg("EVP_CTRL_GCM_SET_TAG failed. OpenSSL error: %s", ERR_error_string(ERR_get_error(), NULL))); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index a12ea3699fdf2..6d4fc7522a728 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -44,8 +44,8 @@ typedef struct InternalKey typedef struct { TDEPrincipalKeyInfo data; - unsigned char sign_iv[16]; - unsigned char aead_tag[16]; + unsigned char sign_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; } TDESignedPrincipalKeyInfo; /* We do not need the dbOid since the entries are stored in a file per db */ diff --git a/contrib/pg_tde/src/include/encryption/enc_aes.h b/contrib/pg_tde/src/include/encryption/enc_aes.h index c545ae7aeebda..0b5269a5456ec 100644 --- a/contrib/pg_tde/src/include/encryption/enc_aes.h +++ b/contrib/pg_tde/src/include/encryption/enc_aes.h @@ -15,8 +15,8 @@ extern void AesInit(void); extern void AesEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); extern void AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out); -extern void AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag); -extern bool AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag); +extern void AesGcmEncrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); +extern bool AesGcmDecrypt(const unsigned char *key, const unsigned char *iv, int iv_len, const unsigned char *aad, int aad_len, const unsigned char *in, int in_len, unsigned char *out, unsigned char *tag, int tag_len); extern void AesCtrEncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out); #endif /* ENC_AES_H */ From 34c7d2601fb18deb2e8741d6ba8e9c666c241500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 26 May 2025 12:43:27 +0200 Subject: [PATCH 391/796] Use Assert(false) over Assert(0) This is what the postgres code mostly uses. --- contrib/pg_tde/src/catalog/tde_keyring.c | 2 +- contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index e7595ee3b9759..bc11c6240aa50 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -462,7 +462,7 @@ write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) XLogRegisterData((char *) record, sizeof(KeyringProviderRecordInFile)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); #else - Assert(0); + Assert(false); #endif } diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index a2e35b3d2e924..38a67ba0c78ea 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -216,7 +216,7 @@ json_kring_array_start(void *state) } /* Never reached */ - Assert(0); + Assert(false); return JSON_SEM_ACTION_FAILED; } @@ -242,7 +242,7 @@ json_kring_object_start(void *state) parse->state = JK_EXPECT_EXTERN_VAL; break; case JK_EXPECT_EXTERN_VAL: - Assert(0); + Assert(false); elog(ERROR, "invalid semantic state"); break; } @@ -272,7 +272,7 @@ json_kring_object_end(void *state) switch (parse->state) { case JK_EXPECT_TOP_LEVEL_OBJECT: - Assert(0); + Assert(false); elog(ERROR, "invalid semantic state"); break; case JK_EXPECT_TOP_FIELD: @@ -339,7 +339,7 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) switch (parse->state) { case JK_EXPECT_TOP_LEVEL_OBJECT: - Assert(0); + Assert(false); elog(ERROR, "invalid semantic state"); break; case JK_EXPECT_TOP_FIELD: @@ -465,7 +465,7 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) pfree(token); break; default: - Assert(0); + Assert(false); elog(ERROR, "invalid token type"); break; } @@ -528,7 +528,7 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * break; default: - Assert(0); + Assert(false); elog(ERROR, "json keyring: unexpected scalar field %d", field); } } From 740534237de2597ab718240d3de38e6a0519a7c5 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 26 May 2025 15:26:56 +0300 Subject: [PATCH 392/796] Updating pg_tde_set_default_key_using_global_key_provider parameter (#351) Updates to # Global Principal Key Configuration parameter of setting global key Rewrote and added correct falste/true parameters for ensure_new_key on set_default_key. --------- Co-authored-by: Anastasia Alexandrova --- .../pg_tde/documentation/docs/functions.md | 21 ++++++------ .../set-principal-key.md | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 4a70fa67db377..33d4f6922cfcf 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -264,21 +264,22 @@ Princial keys are stored on key providers by the name specified in this function ### pg_tde_set_key_using_database_key_provider -Creates or rotates the principal key for the current database using the specified database key provider and key name. +Creates or reuses a principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. + +This function is typically used when working with per-database encryption through a local key provider. ```sql SELECT pg_tde_set_key_using_database_key_provider( - 'name-of-the-key', + 'key-name', 'provider-name', - 'ensure_new_key' + 'false' -- or 'true' ); ``` - The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: +For the third parameter (`true`, `false`, or omitted): -* If set to `true` (default), a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false`, an existing principal key may be reused. +* `true`: Requires the key to be newly created. If a key with the same name already exists, the function fails. +* `false` (default if omitted): Reuses the existing key with that name, if present. If the key does not exist, a new key is created. ### pg_tde_set_key_using_global_key_provider @@ -286,7 +287,7 @@ Creates or rotates the global principal key using the specified global key provi ```sql SELECT pg_tde_set_key_using_global_key_provider( - 'name-of-the-key', + 'key-name', 'provider-name', 'ensure_new_key' ); @@ -304,7 +305,7 @@ Creates or rotates the server principal key using the specified global key provi ```sql SELECT pg_tde_set_server_key_using_global_key_provider( - 'name-of-the-key', + 'key-name', 'provider-name', 'ensure_new_key' ); @@ -324,7 +325,7 @@ The default key is automatically used as a principal key by any database that d ```sql SELECT pg_tde_set_default_key_using_global_key_provider( - 'name-of-the-key', + 'key-name', 'provider-name', 'ensure_new_key' ); diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 4d8e9637aa391..86bb9b1ada853 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -1,24 +1,35 @@ # Global Principal Key Configuration -You can configure a default principal key using a global key provider. This key will be used by all databases that do not have their own encryption keys configured. +You can configure a default principal key using a global key provider. This key will be used by all databases that do not have their own encryption keys configured. The function **both** sets the principal key and rotates internal keys as needed. ## Create a default principal key -Run the following command: +To configure a global principal key, run: ```sql SELECT pg_tde_set_default_key_using_global_key_provider( - 'name-of-the-key', - 'provider-name', - 'ensure_new_key' + 'key-name', + 'global_vault_provider', + 'false' -- or 'true', or omit entirely ); ``` ## Parameter description -* `name-of-the-key` is the name of the principal key. You will use this name to identify the key. -* `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. -* `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. +* `key-name` is the name under which the principal key is stored in the provider. +* `global_vault_provider` is the name of the global key provider you previously configured. +* Third parameter (optional): + * `true` requires the key to be newly created. If the key already exists, the function fails. + * `false` or omitted (default), allows reuse of an existing key if it exists. If not, a new key is created under the specified name. + +## How key generation works + +If the specified key does **not** exist, a new encryption key is created under the given name. In this case, the key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. + +!!! note + This process sets the **default principal key** for the server. Any database without its own key configuration will use this key. + +## Example This example is for testing purposes only. Replace the key name and provider name with your values: @@ -26,15 +37,10 @@ This example is for testing purposes only. Replace the key name and provider nam SELECT pg_tde_set_key_using_global_key_provider( 'test-db-master-key', 'file-vault', - 'ensure_new_key' + 'false' ); ``` -!!! note - The key is auto-generated. - -After this, all databases that do not have something else configured will use this newly generated principal key. - ## Next steps [Validate Encryption with pg_tde :material-arrow-right:](../test.md){.md-button} From f55756b0ca68ec76b423bde400aa3520bb3aed97 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 26 May 2025 14:42:59 +0200 Subject: [PATCH 393/796] Remove accidentally left over function default for KMIP --- contrib/pg_tde/pg_tde--1.0-rc.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 4a43da236e034..3b6e62567b79f 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -184,7 +184,7 @@ CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, kmip_port INT, kmip_ca_path TEXT, kmip_cert_path TEXT, - kmip_key_path TEXT DEFAULT '') + kmip_key_path TEXT) RETURNS INT LANGUAGE SQL BEGIN ATOMIC From 19b69aa0ba086a931b95e0184ba91550b365cec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 12 May 2025 13:57:18 +0200 Subject: [PATCH 394/796] Minor cleanups in isle tde_principal_key.c Remove some pointless variable usage, move variable to smaller scope and clean up some newlines. --- .../pg_tde/src/catalog/tde_principal_key.c | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 41a697830f9d3..c86e2c930c5a8 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -887,41 +887,36 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) HeapTuple tuple; SysScanDesc scan; Relation rel; - bool used = false; + TDEPrincipalKey *principal_key; + bool used; /* First verify that the global/default oid doesn't use it */ - Oid dbOid = GLOBAL_DATA_TDE_OID; - TDEPrincipalKey *principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); - + principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (principal_key != NULL && providerId == principal_key->keyInfo.keyringId) { LWLockRelease(tde_lwlock_enc_keys()); - return true; } - dbOid = DEFAULT_DATA_TDE_OID; - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); - + principal_key = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); if (principal_key != NULL && providerId == principal_key->keyInfo.keyringId) { LWLockRelease(tde_lwlock_enc_keys()); - return true; } /* We have to verify that it isn't currently used by any database */ rel = table_open(DatabaseRelationId, AccessShareLock); - scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); + used = false; while (HeapTupleIsValid(tuple = systable_getnext(scan))) { - dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + Oid dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; + principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); if (principal_key && principal_key->keyInfo.keyringId == providerId) { used = true; @@ -943,7 +938,6 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) bool used = principal_key != NULL && providerId == principal_key->keyInfo.keyringId; LWLockRelease(tde_lwlock_enc_keys()); - return used; } } @@ -977,11 +971,10 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci HeapTuple tuple; SysScanDesc scan; Relation rel; - Oid dbOid = GLOBAL_DATA_TDE_OID; TDEPrincipalKey *principal_key; /* First check the global oid */ - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(oldKey, principal_key)) { @@ -994,14 +987,13 @@ pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrinci * not ideal */ rel = table_open(DatabaseRelationId, RowExclusiveLock); - scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); while (HeapTupleIsValid(tuple = systable_getnext(scan))) { - dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; - principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + Oid dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; + principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(oldKey, principal_key)) { pg_tde_rotate_default_key_for_database(principal_key, newKey); From 15ab9fa8a39b2487f5edec66b113e4750d344cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 19 May 2025 18:06:02 +0200 Subject: [PATCH 395/796] Verify provider have in-use keys before change Prevent the change if they do not. In the future we might want to add a "force" parameter, but for now the command line utility can be used while the cluster is offline if that is necessary. --- contrib/pg_tde/expected/create_database.out | 2 +- .../pg_tde/expected/default_principal_key.out | 22 +-- contrib/pg_tde/expected/key_provider.out | 77 +++++++--- contrib/pg_tde/sql/key_provider.sql | 30 +++- contrib/pg_tde/src/catalog/tde_keyring.c | 12 ++ .../pg_tde/src/catalog/tde_principal_key.c | 84 +++++++++++ .../src/include/catalog/tde_principal_key.h | 1 + contrib/pg_tde/t/010_change_key_provider.pl | 76 ---------- .../t/expected/010_change_key_provider.out | 139 ------------------ 9 files changed, 195 insertions(+), 248 deletions(-) diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index 6e3eaa5cf2143..d3b0e60225962 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -26,7 +26,7 @@ INSERT INTO test_plain (x) VALUES (30), (40); SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -4 + -6 (1 row) SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 00ccd625194a5..c3236e55f685e 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -3 + -5 (1 row) -- Should fail: no default principal key for the server yet @@ -30,18 +30,20 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -3 | file-provider | default-key + -5 | file-provider | default-key (1 row) -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+--------------- + id | provider_name +----+------------------ -1 | file-keyring - -3 | file-provider -(2 rows) + -3 | global-provider + -4 | global-provider2 + -5 | file-provider +(4 rows) -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name @@ -60,7 +62,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -3 | file-provider | default-key + -5 | file-provider | default-key (1 row) SELECT current_database() AS regress_database @@ -86,7 +88,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -3 | file-provider | default-key + -5 | file-provider | default-key (1 row) \c :regress_database @@ -101,7 +103,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+----------------- - -3 | file-provider | new-default-key + -5 | file-provider | new-default-key (1 row) \c regress_pg_tde_other @@ -109,7 +111,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+----------------- - -3 | file-provider | new-default-key + -5 | file-provider | new-default-key (1 row) SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index c57a555cc79b6..1829c09972540 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -57,27 +57,12 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); - pg_tde_change_database_key_provider_file ------------------------------------------- - 1 -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"} - 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} -(2 rows) - -SELECT pg_tde_verify_key(); -ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: unexpected field "foo" for external value "path" SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+------------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"} + id | provider_name | provider_type | options +----+----------------+---------------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) @@ -282,6 +267,62 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {" ERROR: unexpected boolean in field "path" SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); ERROR: unexpected boolean in field "path" +-- Modifying key providers fails if new settings can't fetch existing server key +SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); + pg_tde_add_global_key_provider_file +------------------------------------- + -3 +(1 row) + +SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2'); +ERROR: could not fetch key "server-key" used as server key from modified key provider "global-provider": 0 +-- Modifying key providers fails if new settings can't fetch existing database key +SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1'); + pg_tde_add_global_key_provider_file +------------------------------------- + -4 +(1 row) + +SELECT current_database() AS regress_database +\gset +CREATE DATABASE db_using_global_provider; +\c db_using_global_provider; +CREATE EXTENSION pg_tde; +SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +\c :regress_database +SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2'); +ERROR: could not fetch key "database-key" used by database "db_using_global_provider" from modified key provider "global-provider2": 0 +DROP DATABASE db_using_global_provider; +CREATE DATABASE db_using_database_provider; +\c db_using_database_provider; +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file'); + pg_tde_add_database_key_provider_file +--------------------------------------- + 1 +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider'); + pg_tde_set_key_using_database_key_provider +-------------------------------------------- + +(1 row) + +SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2'); +ERROR: could not fetch key "database-key" used by database "db_using_database_provider" from modified key provider "db-provider": 0 +\c :regress_database +DROP DATABASE db_using_database_provider; -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index c777d8aaa266a..665aa28049be1 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -19,10 +19,6 @@ SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per'); -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_verify_key(); - SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -135,6 +131,31 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": tr SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); +-- Modifying key providers fails if new settings can't fetch existing server key +SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); +SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); +SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2'); + +-- Modifying key providers fails if new settings can't fetch existing database key +SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1'); +SELECT current_database() AS regress_database +\gset +CREATE DATABASE db_using_global_provider; +\c db_using_global_provider; +CREATE EXTENSION pg_tde; +SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2'); +\c :regress_database +SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2'); +DROP DATABASE db_using_global_provider; +CREATE DATABASE db_using_database_provider; +\c db_using_database_provider; +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file'); +SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider'); +SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2'); +\c :regress_database +DROP DATABASE db_using_database_provider; + -- Deleting key providers fails if key name is NULL SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); @@ -157,4 +178,5 @@ SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provid SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index bc11c6240aa50..afd4236eba7a3 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -511,6 +511,18 @@ check_provider_record(KeyringProviderRecord *provider_record) KeyringValidate(provider); +#ifndef FRONTEND /* We can't scan the pg_database catalog from + * frontend. */ + if (provider->keyring_id != 0) + { + /* + * If we are modifying an existing provider, verify that all of the + * keys already in use are the same. + */ + pg_tde_verify_provider_keys_in_use(provider); + } +#endif + pfree(provider); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index c86e2c930c5a8..d318aaaf6b5e4 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -942,6 +942,90 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) } } +/* + * Verifies that all keys that are currently in use matches the keys available + * at the provided key provider. This is meant to be used before modifying an + * existing provider to ensure the new settings will provide the same keys as + * those that are already in use. + */ +void +pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) +{ + TDEPrincipalKey *existing_principal_key; + HeapTuple tuple; + SysScanDesc scan; + Relation rel; + + Assert(modified_provider); + Assert(modified_provider->keyring_id); + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + /* Check the server key that is used for WAL encryption */ + existing_principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); + if (existing_principal_key != NULL && + existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + { + char *key_name = existing_principal_key->keyInfo.name; + KeyringReturnCodes return_code; + KeyInfo *proposed_key; + + proposed_key = KeyringGetKey(modified_provider, key_name, &return_code); + if (!proposed_key) + { + ereport(ERROR, + errmsg("could not fetch key \"%s\" used as server key from modified key provider \"%s\": %d", + key_name, modified_provider->provider_name, return_code)); + } + + if (proposed_key->data.len != existing_principal_key->keyLength || + memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + { + ereport(ERROR, + errmsg("key \"%s\" from modified key provider \"%s\" does not match existing server key", + key_name, modified_provider->provider_name)); + } + } + + /* Check all databases for usage of keys from this key provider. */ + rel = table_open(DatabaseRelationId, AccessShareLock); + scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_database database = (Form_pg_database) GETSTRUCT(tuple); + + existing_principal_key = GetPrincipalKeyNoDefault(database->oid, LW_EXCLUSIVE); + if (existing_principal_key != NULL && + existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + { + char *key_name = existing_principal_key->keyInfo.name; + KeyringReturnCodes return_code; + KeyInfo *proposed_key; + + proposed_key = KeyringGetKey(modified_provider, key_name, &return_code); + if (!proposed_key) + { + ereport(ERROR, + errmsg("could not fetch key \"%s\" used by database \"%s\" from modified key provider \"%s\": %d", + key_name, database->datname.data, modified_provider->provider_name, return_code)); + } + + if (proposed_key->data.len != existing_principal_key->keyLength || + memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + { + ereport(ERROR, + errmsg("key \"%s\" from modified key provider \"%s\" does not match existing key used by database \"%s\"", + key_name, modified_provider->provider_name, database->datname.data)); + } + } + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + LWLockRelease(tde_lwlock_enc_keys()); +} + static bool pg_tde_is_same_principal_key(TDEPrincipalKey *a, TDEPrincipalKey *b) { diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index ee5870ccf7b3c..26dd6f22befaa 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -55,5 +55,6 @@ extern TDEPrincipalKey *GetPrincipalKey(Oid dbOid, void *lockMode); extern void xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec); extern bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId); +extern void pg_tde_verify_provider_keys_in_use(GenericKeyring *proposed_provider); #endif /* PG_TDE_PRINCIPAL_KEY_H */ diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/010_change_key_provider.pl index 60b40243222bf..edf482c2ba760 100644 --- a/contrib/pg_tde/t/010_change_key_provider.pl +++ b/contrib/pg_tde/t/010_change_key_provider.pl @@ -62,82 +62,6 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -# Change provider and do not move file -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');" -); -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_list_all_database_key_providers();"); - -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -# Verify -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); - -PGTDE::append_to_result_file( - "-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per"); -move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per'); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -# Verify -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); - -PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); - -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); - -# Change provider and generate a new principal key -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');" -); -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" -); - -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;' -); -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);'); - -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); - -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');" -); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -# Verify -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');" -); - -# Verify -PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); -PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); - PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;'); $node->stop; diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index e744203167269..73c6b280d1757 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -90,144 +90,5 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) -SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); - pg_tde_change_database_key_provider_file ------------------------------------------- - 1 -(1 row) - -SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------ - (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_3.per""}") -(1 row) - -SELECT pg_tde_verify_key(); -psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - --- server restart -SELECT pg_tde_verify_key(); -psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; -psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 --- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per --- server restart -SELECT pg_tde_verify_key(); - pg_tde_verify_key -------------------- - -(1 row) - -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - -DROP EXTENSION pg_tde CASCADE; -psql::1: NOTICE: drop cascades to table test_enc -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); - pg_tde_add_database_key_provider_file ---------------------------------------- - 1 -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc (k) VALUES (5), (6); -SELECT pg_tde_verify_key(); - pg_tde_verify_key -------------------- - -(1 row) - -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - -SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per'); - pg_tde_change_database_key_provider_file ------------------------------------------- - 1 -(1 row) - --- server restart -SELECT pg_tde_verify_key(); -psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; -psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file -CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap; -psql::1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file -SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per'); - pg_tde_change_database_key_provider_file ------------------------------------------- - 1 -(1 row) - -SELECT pg_tde_verify_key(); - pg_tde_verify_key -------------------- - -(1 row) - -SELECT pg_tde_is_encrypted('test_enc'); - pg_tde_is_encrypted ---------------------- - t -(1 row) - -SELECT * FROM test_enc ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc From 235819037faacd4463736ef9700f47f371b0ef94 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 26 May 2025 13:02:03 +0200 Subject: [PATCH 396/796] Add comments about principal keys and WAL records --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 ++++ contrib/pg_tde/src/catalog/tde_principal_key.c | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index d06043ba698fb..b818e0d879e96 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -325,6 +325,10 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) * information. * * The caller must have an EXCLUSIVE LOCK on the files before calling this function. + * + * write_xlog: if true, the function will write an XLOG record about the + * principal key addition. We may want to skip this during server recovery/startup + * or in some other cases when WAL writes are not allowed. */ void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index d318aaaf6b5e4..c8ae366101091 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -830,6 +830,11 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode) #ifndef FRONTEND + /* + * If database doesn't have dedicated principal key we should try to + * fallback to default principal key. + */ + /* Lock is already updated to exclusive at this point */ principalKey = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); @@ -842,6 +847,12 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode) *newPrincipalKey = *principalKey; newPrincipalKey->keyInfo.databaseId = dbOid; + /* + * We have to write default principal key info to database keys file. + * However we cannot write XLOG records about this operation as current + * funcion may be invoked during server startup/recovery where WAL writes + * forbidden. + */ pg_tde_save_principal_key(newPrincipalKey, false); push_principal_key_to_cache(newPrincipalKey); From d26a71f741810ffe33c6f2774396cb7ea501218a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 17:30:17 +0200 Subject: [PATCH 397/796] Increase readability of GetPrincipalKey() By just having one ifdef block the function becomes easier on the eye. --- .../pg_tde/src/catalog/tde_principal_key.c | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index c8ae366101091..ab4516a8a6e2e 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -819,47 +819,40 @@ TDEPrincipalKey * GetPrincipalKey(Oid dbOid, LWLockMode lockMode) { TDEPrincipalKey *principalKey = GetPrincipalKeyNoDefault(dbOid, lockMode); -#ifndef FRONTEND - TDEPrincipalKey *newPrincipalKey = NULL; -#endif - - if (principalKey != NULL) - { - return principalKey; - } #ifndef FRONTEND - - /* - * If database doesn't have dedicated principal key we should try to - * fallback to default principal key. - */ - - /* Lock is already updated to exclusive at this point */ - principalKey = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); - if (principalKey == NULL) { - return NULL; - } + /* + * If database doesn't have dedicated principal key we should try to + * fallback to default principal key. + */ + TDEPrincipalKey *newPrincipalKey; - newPrincipalKey = palloc_object(TDEPrincipalKey); - *newPrincipalKey = *principalKey; - newPrincipalKey->keyInfo.databaseId = dbOid; + /* Lock is already updated to exclusive at this point */ + principalKey = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); - /* - * We have to write default principal key info to database keys file. - * However we cannot write XLOG records about this operation as current - * funcion may be invoked during server startup/recovery where WAL writes - * forbidden. - */ - pg_tde_save_principal_key(newPrincipalKey, false); + if (principalKey == NULL) + return NULL; - push_principal_key_to_cache(newPrincipalKey); + newPrincipalKey = palloc_object(TDEPrincipalKey); + *newPrincipalKey = *principalKey; + newPrincipalKey->keyInfo.databaseId = dbOid; - pfree(newPrincipalKey); + /* + * We have to write default principal key info to database keys file. + * However we cannot write XLOG records about this operation as + * current funcion may be invoked during server startup/recovery where + * WAL writes forbidden. + */ + pg_tde_save_principal_key(newPrincipalKey, false); + + push_principal_key_to_cache(newPrincipalKey); - principalKey = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + pfree(newPrincipalKey); + + principalKey = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + } #endif return principalKey; From 66ecdcc17d0201cf7d1ce0fa376f07391db20a46 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 26 May 2025 16:17:47 +0200 Subject: [PATCH 398/796] Use InvalidXLogRecPtr rather than zero literal This makes the code a bit close to PostgreSQL's style. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 0e8674a73bbe7..b9095b90b59ed 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -278,13 +278,13 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, if (!keys) { /* cache is empty, try to read keys from disk */ - keys = pg_tde_fetch_wal_keys(0); + keys = pg_tde_fetch_wal_keys(InvalidXLogRecPtr); } #ifndef FRONTEND write_key_lsn = pg_atomic_read_u64(&EncryptionState->enc_key_lsn); - if (write_key_lsn != 0) + if (!XLogRecPtrIsInvalid(write_key_lsn)) { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); From d32a30b8a82ab9f7dc834a1816c3e47047de0553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 27 May 2025 17:14:42 +0200 Subject: [PATCH 399/796] Remove nonsensical comment This comment made sense when it was written, but the code has since changed a lot without the comment being maintained. Now it means nothing relevant to the current code. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index ecf9a21e82015..3d33d252ecefc 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -262,7 +262,6 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool * This is the only function that gets called during actual CREATE * TABLE/INDEX (EVENT TRIGGER) */ - /* so we create the key here by loading it */ mdcreate(relold, reln, forknum, isRedo); From 8f522b23a5e36d4aafe72f00c9fefbf3f34450bd Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 20:54:51 +0200 Subject: [PATCH 400/796] Replace map entry/key type flags with an enum The code was much harder to understand than necessary due to the type being encoded as a bitmask where most combinations of bits were invalid. Using an enum makes the five different states which we encoded much more obvious. The WAL_INVALID state is a bit special since it is only ever set on the key and never on the map entry itself. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 39 ++++++++++--------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 8 ++-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 24 +++++------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index b818e0d879e96..4fd8a7f5cb9c2 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -53,7 +53,7 @@ } #endif -#define PG_TDE_FILEMAGIC 0x02454454 /* version ID value = TDE 02 */ +#define PG_TDE_FILEMAGIC 0x03454454 /* version ID value = TDE 03 */ #define MAP_ENTRY_SIZE sizeof(TDEMapEntry) #define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) @@ -87,8 +87,8 @@ static HTAB *TempRelKeys = NULL; static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; -static InternalKey *pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type); -static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path, TDEMapEntry *map_entry); +static InternalKey *pg_tde_get_key_from_file(const RelFileLocator *rlocator, TDEMapEntryType key_type); +static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); @@ -100,7 +100,7 @@ static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLog #ifndef FRONTEND static InternalKey *pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator); static InternalKey *pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator); -static void pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type); +static void pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type); static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); @@ -215,7 +215,7 @@ pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator) } static void -pg_tde_generate_internal_key(InternalKey *int_key, uint32 entry_type) +pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type) { int_key->type = entry_type; int_key->start_lsn = InvalidXLogRecPtr; @@ -253,7 +253,7 @@ tde_sprint_key(InternalKey *k) * with the actual lsn by the first WAL write. */ void -pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 entry_type) +pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type) { TDEPrincipalKey *principal_key; @@ -268,7 +268,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat } /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ - pg_tde_generate_internal_key(rel_key_data, TDE_KEY_TYPE_GLOBAL | entry_type); + pg_tde_generate_internal_key(rel_key_data, entry_type); pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); @@ -411,7 +411,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ { map_entry->spcOid = rlocator->spcOid; map_entry->relNumber = rlocator->relNumber; - map_entry->flags = rel_key_data->type; + map_entry->type = rel_key_data->type; map_entry->enc_key = *rel_key_data; if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) @@ -496,7 +496,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ break; } - if (read_map_entry.flags == MAP_ENTRY_EMPTY) + if (read_map_entry.type == MAP_ENTRY_EMPTY) { curr_pos = prev_pos; break; @@ -542,10 +542,10 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator) if (!pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) break; - if (map_entry.flags != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator->spcOid && map_entry.relNumber == rlocator->relNumber) + if (map_entry.type != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator->spcOid && map_entry.relNumber == rlocator->relNumber) { TDEMapEntry empty_map_entry = { - .flags = MAP_ENTRY_EMPTY, + .type = MAP_ENTRY_EMPTY, .enc_key = { .type = MAP_ENTRY_EMPTY, }, @@ -620,7 +620,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p if (!pg_tde_read_one_map_entry(old_fd, &read_map_entry, &old_curr_pos)) break; - if (read_map_entry.flags == MAP_ENTRY_EMPTY) + if (read_map_entry.type == MAP_ENTRY_EMPTY) continue; rloc.spcOid = read_map_entry.spcOid; @@ -716,7 +716,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) if (prev_map_entry.enc_key.start_lsn >= lsn) { - WALKeySetInvalid(&prev_map_entry.enc_key); + prev_map_entry.enc_key.type = TDE_KEY_TYPE_WAL_INVALID; if (pg_pwrite(fd, &prev_map_entry, MAP_ENTRY_SIZE, prev_key_pos) != MAP_ENTRY_SIZE) { @@ -775,7 +775,7 @@ pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo * reads the key data from the keydata file. */ static InternalKey * -pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) +pg_tde_get_key_from_file(const RelFileLocator *rlocator, TDEMapEntryType key_type) { TDEMapEntry map_entry; TDEPrincipalKey *principal_key; @@ -824,12 +824,12 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type) } /* - * Returns true if we find a valid match; e.g. flags is not set to + * Returns true if we find a valid match; e.g. type is not set to * MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one provided in * rlocator. */ static bool -pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_map_path, TDEMapEntry *map_entry) +pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry) { File map_fd; off_t curr_pos = 0; @@ -841,7 +841,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_ while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { - if ((map_entry->flags & key_type) && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) + if (map_entry->type == key_type && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) { found = true; break; @@ -883,7 +883,7 @@ pg_tde_count_relations(Oid dbOid) while (pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) { - if (map_entry.flags & TDE_KEY_TYPE_SMGR) + if (map_entry.type == TDE_KEY_TYPE_SMGR) count++; } @@ -1269,7 +1269,8 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) * Skip new (just created but not updated by write) and invalid keys */ if (map_entry.enc_key.start_lsn != InvalidXLogRecPtr && - WALKeyIsValid(&map_entry.enc_key) && + (map_entry.enc_key.type == TDE_KEY_TYPE_WAL_UNENCRYPTED || + map_entry.enc_key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) && map_entry.enc_key.start_lsn >= start_lsn) { InternalKey *rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index b9095b90b59ed..1f331e18e6484 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -202,7 +202,7 @@ TDEXLogSmgrInit(void) pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), TDE_KEY_TYPE_WAL_ENCRYPTED); } - else if (key && key->type & TDE_KEY_TYPE_WAL_ENCRYPTED) + else if (key && key->type == TDE_KEY_TYPE_WAL_ENCRYPTED) { pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), TDE_KEY_TYPE_WAL_UNENCRYPTED); @@ -233,7 +233,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, * * This func called with WALWriteLock held, so no need in any extra sync. */ - if (EncryptionKey.type & TDE_KEY_TYPE_GLOBAL && + if (EncryptionKey.type != MAP_ENTRY_EMPTY && pg_atomic_read_u64(&EncryptionState->enc_key_lsn) == 0) { XLogRecPtr lsn; @@ -314,11 +314,11 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, elog(DEBUG1, "WAL key %X/%X-%X/%X, encrypted: %s", LSN_FORMAT_ARGS(curr_key->start_lsn), LSN_FORMAT_ARGS(curr_key->end_lsn), - curr_key->key.type & TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); + curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); #endif if (curr_key->key.start_lsn != InvalidXLogRecPtr && - (curr_key->key.type & TDE_KEY_TYPE_WAL_ENCRYPTED)) + curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) { /* * Check if the key's range overlaps with the buffer's and decypt diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 6d4fc7522a728..b65b063be1700 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -13,12 +13,14 @@ #include "catalog/tde_principal_key.h" #include "common/pg_tde_utils.h" -/* Map entry flags */ -#define MAP_ENTRY_EMPTY 0x00 -#define TDE_KEY_TYPE_SMGR 0x02 -#define TDE_KEY_TYPE_GLOBAL 0x04 -#define TDE_KEY_TYPE_WAL_UNENCRYPTED 0x08 -#define TDE_KEY_TYPE_WAL_ENCRYPTED 0x10 +typedef enum +{ + MAP_ENTRY_EMPTY = 0, + TDE_KEY_TYPE_SMGR = 1, + TDE_KEY_TYPE_WAL_UNENCRYPTED = 2, + TDE_KEY_TYPE_WAL_ENCRYPTED = 3, + TDE_KEY_TYPE_WAL_INVALID = 4, +} TDEMapEntryType; #define INTERNAL_KEY_LEN 16 #define INTERNAL_KEY_IV_LEN 16 @@ -32,12 +34,6 @@ typedef struct InternalKey XLogRecPtr start_lsn; } InternalKey; -#define WALKeySetInvalid(key) \ - ((key)->type &= ~(TDE_KEY_TYPE_WAL_ENCRYPTED | TDE_KEY_TYPE_WAL_UNENCRYPTED)) -#define WALKeyIsValid(key) \ - (((key)->type & TDE_KEY_TYPE_WAL_UNENCRYPTED) != 0 || \ - ((key)->type & TDE_KEY_TYPE_WAL_ENCRYPTED) != 0) - #define MAP_ENTRY_IV_SIZE 16 #define MAP_ENTRY_AEAD_TAG_SIZE 16 @@ -53,7 +49,7 @@ typedef struct TDEMapEntry { Oid spcOid; RelFileNumber relNumber; - uint32 flags; + uint32 type; InternalKey enc_key; /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; @@ -89,7 +85,7 @@ extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); extern void pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator); -extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, uint32 flags); +extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType flags); #define PG_TDE_MAP_FILENAME "%d_keys" From 5fd8d16df39e7700123794c0ddbd29cc966f28d2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 18:36:58 +0200 Subject: [PATCH 401/796] Remove optimization for catalog tables Since we rely on the SMGR relation cache for fast key lookup when opening a SMGR relation and that the catalog should be open basically all the time this optimization adds little value and only complicates the code. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 30 +++++---------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 3d33d252ecefc..9a6537b728c31 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -45,26 +45,6 @@ typedef struct TDESMgrRelation static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); -static bool -tde_smgr_is_encrypted(const RelFileLocatorBackend *smgr_rlocator) -{ - /* Do not try to encrypt/decrypt catalog tables */ - if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) - return false; - - return IsSMGRRelationEncrypted(*smgr_rlocator); -} - -static InternalKey * -tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) -{ - /* Do not try to encrypt/decrypt catalog tables */ - if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) - return NULL; - - return GetSMGRRelationKey(*smgr_rlocator); -} - static bool tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { @@ -110,7 +90,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -181,7 +161,7 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -210,7 +190,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, return; else if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -279,7 +259,7 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool * Since event triggers do not fire on the standby or in recovery we * do not try to generate any new keys and instead trust the xlog. */ - InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); + InternalKey *key = GetSMGRRelationKey(reln->smgr_rlocator); if (!isRedo && !key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) key = pg_tde_create_smgr_key(&reln->smgr_rlocator); @@ -311,7 +291,7 @@ tde_mdopen(SMgrRelation reln) mdopen(reln); - if (tde_smgr_is_encrypted(&reln->smgr_rlocator)) + if (IsSMGRRelationEncrypted(reln->smgr_rlocator)) { tdereln->encryption_status = RELATION_KEY_NOT_AVAILABLE; } From 9618f6934b30664ee387ec19d64ef97b82e651c7 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 18:37:18 +0200 Subject: [PATCH 402/796] Move SMGR specific logic out of the TDE map code Especially all code realted to the keys of temporary tables did not belong in the TDE map code and fit better in the SMGR code. Additionally we speed up pg_tde_is_encrypted() by relying on the SMGR to cache the relations. This might in some cases lead to blow up of the SMGR relation cache if you query every relation in the database but given the small size I am not overly worried. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 212 +++--------------- contrib/pg_tde/src/access/pg_tde_xlog.c | 3 +- contrib/pg_tde/src/common/pg_tde_utils.c | 6 +- contrib/pg_tde/src/encryption/enc_tde.c | 25 +++ .../pg_tde/src/include/access/pg_tde_tdemap.h | 9 +- .../pg_tde/src/include/encryption/enc_tde.h | 1 + contrib/pg_tde/src/include/smgr/pg_tde_smgr.h | 5 + contrib/pg_tde/src/smgr/pg_tde_smgr.c | 172 +++++++++++++- src/bin/pg_checksums/pg_checksums.c | 5 +- 9 files changed, 229 insertions(+), 209 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 4fd8a7f5cb9c2..0e6aecdb7095b 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -20,7 +20,6 @@ #include "access/xlog_internal.h" #include "access/xloginsert.h" #include "utils/builtins.h" -#include "utils/hsearch.h" #include "miscadmin.h" #include "access/pg_tde_tdemap.h" @@ -28,6 +27,7 @@ #include "catalog/tde_global_space.h" #include "catalog/tde_principal_key.h" #include "encryption/enc_aes.h" +#include "encryption/enc_tde.h" #include "keyring/keyring_api.h" #include @@ -66,24 +66,6 @@ typedef struct TDEFileHeader TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; -typedef struct -{ - RelFileLocator rel; - InternalKey key; -} TempRelKeyEntry; - -#ifndef FRONTEND - -/* Arbitrarily picked small number of temporary relations */ -#define INIT_TEMP_RELS 16 - -/* - * Each backend has a hashtable that stores the keys for all temporary tables. - */ -static HTAB *TempRelKeys = NULL; - -#endif - static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; @@ -98,72 +80,25 @@ static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); #ifndef FRONTEND -static InternalKey *pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator); -static InternalKey *pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator); -static void pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type); static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key); -static void pg_tde_free_key_map_entry(const RelFileLocator *rlocator); +static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); -InternalKey * -pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator) -{ - if (RelFileLocatorBackendIsTemp(*newrlocator)) - return pg_tde_create_smgr_key_temp(&newrlocator->locator); - else - return pg_tde_create_smgr_key_perm(&newrlocator->locator); -} - -static InternalKey * -pg_tde_create_smgr_key_temp(const RelFileLocator *newrlocator) -{ - InternalKey *rel_key_data = palloc_object(InternalKey); - TempRelKeyEntry *entry; - bool found; - - pg_tde_generate_internal_key(rel_key_data, TDE_KEY_TYPE_SMGR); - - if (TempRelKeys == NULL) - { - HASHCTL ctl; - - ctl.keysize = sizeof(RelFileLocator); - ctl.entrysize = sizeof(TempRelKeyEntry); - TempRelKeys = hash_create("pg_tde temporary relation keys", - INIT_TEMP_RELS, - &ctl, - HASH_ELEM | HASH_BLOBS); - } - - entry = (TempRelKeyEntry *) hash_search(TempRelKeys, - newrlocator, - HASH_ENTER, &found); - Assert(!found); - - entry->key = *rel_key_data; - - return rel_key_data; -} - -static InternalKey * -pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) +void +pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data, bool write_xlog) { - InternalKey *rel_key_data = palloc_object(InternalKey); TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); XLogRelKey xlrec = { - .rlocator = *newrlocator, + .rlocator = rel, }; - pg_tde_generate_internal_key(rel_key_data, TDE_KEY_TYPE_SMGR); - LWLockAcquire(lock_pk, LW_EXCLUSIVE); - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); + principal_key = GetPrincipalKey(rel.dbOid, LW_EXCLUSIVE); if (principal_key == NULL) { ereport(ERROR, @@ -171,65 +106,19 @@ pg_tde_create_smgr_key_perm(const RelFileLocator *newrlocator) errhint("create one using pg_tde_set_key before using encrypted tables")); } - pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); + pg_tde_write_key_map_entry(&rel, rel_key_data, principal_key); LWLockRelease(lock_pk); - /* - * It is fine to write the to WAL after writing to the file since we have - * not WAL logged the SMGR CREATE event either. - */ - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); - - return rel_key_data; -} - -void -pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator) -{ - InternalKey rel_key_data; - InternalKey *old_key; - TDEPrincipalKey *principal_key; - LWLock *lock_pk = tde_lwlock_enc_keys(); - - if ((old_key = pg_tde_get_key_from_file(newrlocator, TDE_KEY_TYPE_SMGR))) - { - pfree(old_key); - return; - } - - pg_tde_generate_internal_key(&rel_key_data, TDE_KEY_TYPE_SMGR); - - LWLockAcquire(lock_pk, LW_EXCLUSIVE); - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); - if (principal_key == NULL) + if (write_xlog) { - ereport(ERROR, - errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables")); + /* + * It is fine to write the to WAL after writing to the file since we + * have not WAL logged the SMGR CREATE event either. + */ + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); } - - pg_tde_write_key_map_entry(newrlocator, &rel_key_data, principal_key); - LWLockRelease(lock_pk); -} - -static void -pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type) -{ - int_key->type = entry_type; - int_key->start_lsn = InvalidXLogRecPtr; - - if (!RAND_bytes(int_key->key, INTERNAL_KEY_LEN)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate internal key: %s", - ERR_error_string(ERR_get_error(), NULL))); - if (!RAND_bytes(int_key->base_iv, INTERNAL_KEY_IV_LEN)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate IV: %s", - ERR_error_string(ERR_get_error(), NULL))); } const char * @@ -275,18 +164,6 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat LWLockRelease(tde_lwlock_enc_keys()); } -void -DeleteSMGRRelationKey(RelFileLocatorBackend rel) -{ - if (RelFileLocatorBackendIsTemp(rel)) - { - Assert(TempRelKeys); - hash_search(TempRelKeys, &rel.locator, HASH_REMOVE, NULL); - } - else - pg_tde_free_key_map_entry(&rel.locator); -} - /* * Deletes the key map file for a given database. */ @@ -463,7 +340,7 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, * concurrent in place updates leading to data conflicts. */ void -pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_data, TDEPrincipalKey *principal_key) +pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key) { char db_map_path[MAXPGPATH]; int map_fd; @@ -518,16 +395,14 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, InternalKey *rel_key_ * This fucntion is called by the pg_tde SMGR when storage is unlinked on * transaction commit/abort. */ -static void -pg_tde_free_key_map_entry(const RelFileLocator *rlocator) +void +pg_tde_free_key_map_entry(const RelFileLocator rlocator) { char db_map_path[MAXPGPATH]; File map_fd; off_t curr_pos = 0; - Assert(rlocator); - - pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); + pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); @@ -542,7 +417,7 @@ pg_tde_free_key_map_entry(const RelFileLocator *rlocator) if (!pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) break; - if (map_entry.type != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator->spcOid && map_entry.relNumber == rlocator->relNumber) + if (map_entry.type != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator.spcOid && map_entry.relNumber == rlocator.relNumber) { TDEMapEntry empty_map_entry = { .type = MAP_ENTRY_EMPTY, @@ -1084,57 +959,27 @@ pg_tde_get_principal_key_info(Oid dbOid) return signed_key_info; } -static InternalKey * -pg_tde_get_temporary_rel_key(const RelFileLocator *rel) -{ -#ifndef FRONTEND - TempRelKeyEntry *entry; - - if (TempRelKeys == NULL) - return NULL; - - entry = hash_search(TempRelKeys, rel, HASH_FIND, NULL); - - if (entry) - { - InternalKey *key = palloc_object(InternalKey); - - *key = entry->key; - return key; - } -#endif - - return NULL; -} - /* * Figures out whether a relation is encrypted or not, but without trying to * decrypt the key if it is. */ bool -IsSMGRRelationEncrypted(RelFileLocatorBackend rel) +pg_tde_has_smgr_key(RelFileLocator rel) { bool result; TDEMapEntry map_entry; char db_map_path[MAXPGPATH]; - Assert(rel.locator.relNumber != InvalidRelFileNumber); - - if (RelFileLocatorBackendIsTemp(rel)) -#ifndef FRONTEND - return TempRelKeys && hash_search(TempRelKeys, &rel.locator, HASH_FIND, NULL); -#else - return false; -#endif + Assert(rel.relNumber != InvalidRelFileNumber); - pg_tde_set_db_file_path(rel.locator.dbOid, db_map_path); + pg_tde_set_db_file_path(rel.dbOid, db_map_path); if (access(db_map_path, F_OK) == -1) return false; LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); - result = pg_tde_find_map_entry(&rel.locator, TDE_KEY_TYPE_SMGR, db_map_path, &map_entry); + result = pg_tde_find_map_entry(&rel, TDE_KEY_TYPE_SMGR, db_map_path, &map_entry); LWLockRelease(tde_lwlock_enc_keys()); return result; @@ -1144,14 +989,11 @@ IsSMGRRelationEncrypted(RelFileLocatorBackend rel) * Returns TDE key for a given relation. */ InternalKey * -GetSMGRRelationKey(RelFileLocatorBackend rel) +pg_tde_get_smgr_key(RelFileLocator rel) { - Assert(rel.locator.relNumber != InvalidRelFileNumber); + Assert(rel.relNumber != InvalidRelFileNumber); - if (RelFileLocatorBackendIsTemp(rel)) - return pg_tde_get_temporary_rel_key(&rel.locator); - else - return pg_tde_get_key_from_file(&rel.locator, TDE_KEY_TYPE_SMGR); + return pg_tde_get_key_from_file(&rel, TDE_KEY_TYPE_SMGR); } /* diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 9d817036bd8ef..0d0581d0604a3 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -25,6 +25,7 @@ #include "access/pg_tde_xlog.h" #include "encryption/enc_tde.h" +#include "smgr/pg_tde_smgr.h" static void tdeheap_rmgr_redo(XLogReaderState *record); static void tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record); @@ -52,7 +53,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - pg_tde_create_smgr_key_perm_redo(&xlrec->rlocator); + tde_smgr_create_key_redo(&xlrec->rlocator); } else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) { diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 109657f2b9d4a..e092557f90c90 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -16,8 +16,7 @@ #ifndef FRONTEND #include "fmgr.h" -#include "catalog/pg_class.h" -#include "access/pg_tde_tdemap.h" +#include "smgr/pg_tde_smgr.h" #include "access/relation.h" #include "utils/rel.h" @@ -28,7 +27,6 @@ pg_tde_is_encrypted(PG_FUNCTION_ARGS) Oid relationOid = PG_GETARG_OID(0); LOCKMODE lockmode = AccessShareLock; Relation rel = relation_open(relationOid, lockmode); - RelFileLocatorBackend rlocator = {.locator = rel->rd_locator,.backend = rel->rd_backend}; bool result; if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) @@ -42,7 +40,7 @@ pg_tde_is_encrypted(PG_FUNCTION_ARGS) errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("we cannot check if temporary relations from other backends are encrypted")); - result = IsSMGRRelationEncrypted(rlocator); + result = tde_smgr_rel_is_encrypted(RelationGetSmgr(rel)); relation_close(rel, lockmode); diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index 59e08dc1e9cc1..2676efb92aac8 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -7,6 +7,13 @@ #include "encryption/enc_aes.h" #include "storage/bufmgr.h" +#ifdef FRONTEND +#include "pg_tde_fe.h" +#endif + +#include +#include + #define AES_BLOCK_SIZE 16 #define NUM_AES_BLOCKS_IN_BATCH 200 #define DATA_BYTES_PER_AES_BATCH (NUM_AES_BLOCKS_IN_BATCH * AES_BLOCK_SIZE) @@ -23,6 +30,24 @@ iv_prefix_debug(const char *iv_prefix, char *out_hex) } #endif +void +pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type) +{ + int_key->type = entry_type; + int_key->start_lsn = InvalidXLogRecPtr; + + if (!RAND_bytes(int_key->key, INTERNAL_KEY_LEN)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate internal key: %s", + ERR_error_string(ERR_get_error(), NULL))); + if (!RAND_bytes(int_key->base_iv, INTERNAL_KEY_IV_LEN)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate IV: %s", + ERR_error_string(ERR_get_error(), NULL))); +} + /* * Encrypts/decrypts `data` with a given `key`. The result is written to `out`. * diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index b65b063be1700..e19f787af2b1e 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -83,8 +83,6 @@ extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); -extern InternalKey *pg_tde_create_smgr_key(const RelFileLocatorBackend *newrlocator); -extern void pg_tde_create_smgr_key_perm_redo(const RelFileLocator *newrlocator); extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType flags); #define PG_TDE_MAP_FILENAME "%d_keys" @@ -95,9 +93,10 @@ pg_tde_set_db_file_path(Oid dbOid, char *path) join_path_components(path, pg_tde_get_data_dir(), psprintf(PG_TDE_MAP_FILENAME, dbOid)); } -extern bool IsSMGRRelationEncrypted(RelFileLocatorBackend rel); -extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel); -extern void DeleteSMGRRelationKey(RelFileLocatorBackend rel); +extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key, bool write_xlog); +extern bool pg_tde_has_smgr_key(RelFileLocator rel); +extern InternalKey *pg_tde_get_smgr_key(RelFileLocator rel); +extern void pg_tde_free_key_map_entry(RelFileLocator rel); extern int pg_tde_count_relations(Oid dbOid); diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index ac417c5254ff9..879733cc0a1af 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -12,6 +12,7 @@ #include "access/pg_tde_tdemap.h" +extern void pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type); extern void pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr); #endif /* ENC_TDE_H */ diff --git a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h index 65b54e5a5e9fd..e0f09efc4c39b 100644 --- a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h +++ b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h @@ -9,6 +9,11 @@ #ifndef PG_TDE_SMGR_H #define PG_TDE_SMGR_H +#include "storage/relfilelocator.h" +#include "storage/smgr.h" + extern void RegisterStorageMgr(void); +extern void tde_smgr_create_key_redo(const RelFileLocator *rlocator); +extern bool tde_smgr_rel_is_encrypted(SMgrRelation reln); #endif /* PG_TDE_SMGR_H */ diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 9a6537b728c31..cf3de6cd5bcef 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -1,10 +1,13 @@ -#include "smgr/pg_tde_smgr.h" #include "postgres.h" + +#include "smgr/pg_tde_smgr.h" #include "storage/smgr.h" #include "storage/md.h" #include "catalog/catalog.h" #include "encryption/enc_aes.h" +#include "encryption/enc_tde.h" #include "access/pg_tde_tdemap.h" +#include "utils/hsearch.h" #include "pg_tde_event_capture.h" typedef enum TDEMgrRelationEncryptionStatus @@ -43,8 +46,82 @@ typedef struct TDESMgrRelation InternalKey relKey; } TDESMgrRelation; +typedef struct +{ + RelFileLocator rel; + InternalKey key; +} TempRelKeyEntry; + +#define INIT_TEMP_RELS 16 + +/* + * Each backend has a hashtable that stores the keys for all temproary tables. + */ +static HTAB *TempRelKeys = NULL; + +static SMgrId OurSMgrId = MaxSMgrId; + +static void tde_smgr_save_temp_key(const RelFileLocator *newrlocator, const InternalKey *key); +static InternalKey *tde_smgr_get_temp_key(const RelFileLocator *rel); +static bool tde_smgr_has_temp_key(const RelFileLocator *rel); +static void tde_smgr_remove_temp_key(const RelFileLocator *rel); static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); +static InternalKey * +tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) +{ + InternalKey *key = palloc_object(InternalKey); + + pg_tde_generate_internal_key(key, TDE_KEY_TYPE_SMGR); + + if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) + tde_smgr_save_temp_key(&smgr_rlocator->locator, key); + else + pg_tde_save_smgr_key(smgr_rlocator->locator, key, true); + + return key; +} + +void +tde_smgr_create_key_redo(const RelFileLocator *rlocator) +{ + InternalKey key; + + if (pg_tde_has_smgr_key(*rlocator)) + return; + + pg_tde_generate_internal_key(&key, TDE_KEY_TYPE_SMGR); + + pg_tde_save_smgr_key(*rlocator, &key, false); +} + +static bool +tde_smgr_is_encrypted(const RelFileLocatorBackend *smgr_rlocator) +{ + if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) + return tde_smgr_has_temp_key(&smgr_rlocator->locator); + else + return pg_tde_has_smgr_key(smgr_rlocator->locator); +} + +static InternalKey * +tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) +{ + if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) + return tde_smgr_get_temp_key(&smgr_rlocator->locator); + else + return pg_tde_get_smgr_key(smgr_rlocator->locator); +} + +static void +tde_smgr_remove_key(const RelFileLocatorBackend *smgr_rlocator) +{ + if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) + tde_smgr_remove_temp_key(&smgr_rlocator->locator); + else + pg_tde_free_key_map_entry(smgr_rlocator->locator); +} + static bool tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { @@ -66,13 +143,25 @@ tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocat .backend = smgr_rlocator->backend, }; - return IsSMGRRelationEncrypted(old_smgr_locator); + return tde_smgr_is_encrypted(&old_smgr_locator); } } return false; } +bool +tde_smgr_rel_is_encrypted(SMgrRelation reln) +{ + TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; + + if (reln->smgr_which != OurSMgrId) + return false; + + return tdereln->encryption_status == RELATION_KEY_AVAILABLE || + tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE; +} + static void tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void **buffers, BlockNumber nblocks, bool skipFsync) @@ -90,7 +179,7 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -139,8 +228,8 @@ tde_mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo) */ if (forknum == MAIN_FORKNUM || forknum == InvalidForkNumber) { - if (IsSMGRRelationEncrypted(rlocator)) - DeleteSMGRRelationKey(rlocator); + if (tde_smgr_is_encrypted(&rlocator)) + tde_smgr_remove_key(&rlocator); } } @@ -161,7 +250,7 @@ tde_mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -190,7 +279,7 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, return; else if (tdereln->encryption_status == RELATION_KEY_NOT_AVAILABLE) { - InternalKey *int_key = GetSMGRRelationKey(reln->smgr_rlocator); + InternalKey *int_key = tde_smgr_get_key(&reln->smgr_rlocator); tdereln->relKey = *int_key; tdereln->encryption_status = RELATION_KEY_AVAILABLE; @@ -259,10 +348,10 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool * Since event triggers do not fire on the standby or in recovery we * do not try to generate any new keys and instead trust the xlog. */ - InternalKey *key = GetSMGRRelationKey(reln->smgr_rlocator); + InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); if (!isRedo && !key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) - key = pg_tde_create_smgr_key(&reln->smgr_rlocator); + key = tde_smgr_create_key(&reln->smgr_rlocator); if (key) { @@ -291,7 +380,7 @@ tde_mdopen(SMgrRelation reln) mdopen(reln); - if (IsSMGRRelationEncrypted(reln->smgr_rlocator)) + if (tde_smgr_is_encrypted(&reln->smgr_rlocator)) { tdereln->encryption_status = RELATION_KEY_NOT_AVAILABLE; } @@ -327,7 +416,68 @@ RegisterStorageMgr(void) { if (storage_manager_id != MdSMgrId) elog(FATAL, "Another storage manager was loaded before pg_tde. Multiple storage managers is unsupported."); - storage_manager_id = smgr_register(&tde_smgr, sizeof(TDESMgrRelation)); + OurSMgrId = smgr_register(&tde_smgr, sizeof(TDESMgrRelation)); + storage_manager_id = OurSMgrId; +} + +static void +tde_smgr_save_temp_key(const RelFileLocator *newrlocator, const InternalKey *key) +{ + TempRelKeyEntry *entry; + bool found; + + if (TempRelKeys == NULL) + { + HASHCTL ctl; + + ctl.keysize = sizeof(RelFileLocator); + ctl.entrysize = sizeof(TempRelKeyEntry); + TempRelKeys = hash_create("pg_tde temporary relation keys", + INIT_TEMP_RELS, + &ctl, + HASH_ELEM | HASH_BLOBS); + } + + entry = (TempRelKeyEntry *) hash_search(TempRelKeys, + newrlocator, + HASH_ENTER, &found); + Assert(!found); + + entry->key = *key; +} + +static InternalKey * +tde_smgr_get_temp_key(const RelFileLocator *rel) +{ + TempRelKeyEntry *entry; + + if (TempRelKeys == NULL) + return NULL; + + entry = hash_search(TempRelKeys, rel, HASH_FIND, NULL); + + if (entry) + { + InternalKey *key = palloc_object(InternalKey); + + *key = entry->key; + return key; + } + + return NULL; +} + +static bool +tde_smgr_has_temp_key(const RelFileLocator *rel) +{ + return TempRelKeys && hash_search(TempRelKeys, rel, HASH_FIND, NULL); +} + +static void +tde_smgr_remove_temp_key(const RelFileLocator *rel) +{ + Assert(TempRelKeys); + hash_search(TempRelKeys, rel, HASH_REMOVE, NULL); } /* diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index 73c8f320a60ad..66919c4afa23c 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -138,10 +138,9 @@ pg_tde_init(const char *datadir) static bool is_pg_tde_encypted(Oid spcOid, Oid dbOid, RelFileNumber relNumber) { - RelFileLocator locator = {.spcOid = spcOid, .dbOid = dbOid,.relNumber = relNumber}; - RelFileLocatorBackend rlocator = {.locator = locator,.backend = INVALID_PROC_NUMBER}; + RelFileLocator locator = {.spcOid = spcOid,.dbOid = dbOid,.relNumber = relNumber}; - return IsSMGRRelationEncrypted(rlocator); + return pg_tde_has_smgr_key(locator); } #endif From 69f7a384086cab37035f0c302a42e679dad2c99c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 23 May 2025 19:49:42 +0200 Subject: [PATCH 403/796] Fold pg_tde_get_key_from_file() into its caller Since the caller of the function became a very thin wrapper having both functions no longer make any sense. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 99 ++++++++++------------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 0e6aecdb7095b..65adffbdb64c6 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -69,7 +69,6 @@ typedef struct TDEFileHeader static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; -static InternalKey *pg_tde_get_key_from_file(const RelFileLocator *rlocator, TDEMapEntryType key_type); static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); @@ -645,59 +644,6 @@ pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo #endif /* !FRONTEND */ -/* - * Reads the key of the required relation. It identifies its map entry and then simply - * reads the key data from the keydata file. - */ -static InternalKey * -pg_tde_get_key_from_file(const RelFileLocator *rlocator, TDEMapEntryType key_type) -{ - TDEMapEntry map_entry; - TDEPrincipalKey *principal_key; - LWLock *lock_pk = tde_lwlock_enc_keys(); - char db_map_path[MAXPGPATH]; - InternalKey *rel_key; - - Assert(rlocator); - - pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); - - if (access(db_map_path, F_OK) == -1) - return NULL; - - LWLockAcquire(lock_pk, LW_SHARED); - - if (!pg_tde_find_map_entry(rlocator, key_type, db_map_path, &map_entry)) - { - LWLockRelease(lock_pk); - return NULL; - } - - /* - * Get/generate a principal key, create the key for relation and get the - * encrypted key with bytes to write - * - * We should hold the lock until the internal key is loaded to be sure the - * retrieved key was encrypted with the obtained principal key. Otherwise, - * the next may happen: - GetPrincipalKey returns key "PKey_1". - Some - * other process rotates the Principal key and re-encrypt an Internal key - * with "PKey_2". - We read the Internal key and decrypt it with "PKey_1" - * (that's what we've got). As the result we return an invalid Internal - * key. - */ - principal_key = GetPrincipalKey(rlocator->dbOid, LW_SHARED); - if (principal_key == NULL) - ereport(ERROR, - errmsg("principal key not configured"), - errhint("create one using pg_tde_set_key before using encrypted tables")); - - rel_key = tde_decrypt_rel_key(principal_key, &map_entry); - - LWLockRelease(lock_pk); - - return rel_key; -} - /* * Returns true if we find a valid match; e.g. type is not set to * MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one provided in @@ -986,14 +932,55 @@ pg_tde_has_smgr_key(RelFileLocator rel) } /* - * Returns TDE key for a given relation. + * Reads the map entry of the relation and decrypts the key. */ InternalKey * pg_tde_get_smgr_key(RelFileLocator rel) { + TDEMapEntry map_entry; + TDEPrincipalKey *principal_key; + LWLock *lock_pk = tde_lwlock_enc_keys(); + char db_map_path[MAXPGPATH]; + InternalKey *rel_key; + Assert(rel.relNumber != InvalidRelFileNumber); - return pg_tde_get_key_from_file(&rel, TDE_KEY_TYPE_SMGR); + pg_tde_set_db_file_path(rel.dbOid, db_map_path); + + if (access(db_map_path, F_OK) == -1) + return NULL; + + LWLockAcquire(lock_pk, LW_SHARED); + + if (!pg_tde_find_map_entry(&rel, TDE_KEY_TYPE_SMGR, db_map_path, &map_entry)) + { + LWLockRelease(lock_pk); + return NULL; + } + + /* + * Get/generate a principal key, create the key for relation and get the + * encrypted key with bytes to write + * + * We should hold the lock until the internal key is loaded to be sure the + * retrieved key was encrypted with the obtained principal key. Otherwise, + * the next may happen: - GetPrincipalKey returns key "PKey_1". - Some + * other process rotates the Principal key and re-encrypt an Internal key + * with "PKey_2". - We read the Internal key and decrypt it with "PKey_1" + * (that's what we've got). As the result we return an invalid Internal + * key. + */ + principal_key = GetPrincipalKey(rel.dbOid, LW_SHARED); + if (principal_key == NULL) + ereport(ERROR, + errmsg("principal key not configured"), + errhint("create one using pg_tde_set_key before using encrypted tables")); + + rel_key = tde_decrypt_rel_key(principal_key, &map_entry); + + LWLockRelease(lock_pk); + + return rel_key; } /* From a84851e4c60069e141b06ff11bc8cb1396dd1a06 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 29 May 2025 15:47:37 +0300 Subject: [PATCH 404/796] Improved Docs Banner and added RC to GA upgrade warnings (#373) Added to banner and other chapters notes for the user to **NOT** upgrade RC to GA (hidden for now, prepared for GA release), do a fresh install for GA, and added more important notes regarding the fact that this is an RC build that should **NOT** be used in prod env. --- contrib/pg_tde/documentation/_resource/overrides/main.html | 7 ++++--- contrib/pg_tde/documentation/docs/index.md | 3 +-- contrib/pg_tde/documentation/docs/index/tde-limitations.md | 3 +++ contrib/pg_tde/documentation/docs/install.md | 4 ++++ .../documentation/docs/release-notes/release-notes.md | 2 +- contrib/pg_tde/documentation/variables.yml | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/documentation/_resource/overrides/main.html b/contrib/pg_tde/documentation/_resource/overrides/main.html index f9282a9e30d3a..1fca8c3d06631 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/main.html +++ b/contrib/pg_tde/documentation/_resource/overrides/main.html @@ -4,9 +4,10 @@ {% extends "base.html" %} {% block announce %} - This is the Release Candidate 2 of Percona Transparent Data Encryption (TDE) extension. It is - not recommended for production environments at this stage. We encourage you to test it and give your feedback. - This will help us improve the product and make it production-ready faster. + This is the Release Candidate 2 (RC2) of Percona Transparent Data Encryption (TDE) extension. +

It is not recommended for production environments at this stage.

+

We encourage you to test it and give your feedback. + This will help us improve the product and make it production-ready faster.

{% endblock %} {% block scripts %} diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index a2702fd6df4c7..13512dd28cb75 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -3,8 +3,7 @@ `pg_tde` is the open source, community driven and futureproof PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. `pg_tde` ensures that the data stored on disk is encrypted, and that no one can read it without the proper encryption keys, even if they gain access to the physical storage media. !!! important - - This is the {{release}} version of the extension and it is not meant for production use yet. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). + This is the {{release}} version of the extension and **it is not meant for production use yet**. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). [Overview](index/index.md){.md-button} [Get Started](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index 393167e651123..cc10f80519b22 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -6,4 +6,7 @@ * `pg_rewind` doesn't work with encrypted WAL for now. We plan to fix it in future releases. * `pg_tde` Release candidate is incompatible with `pg_tde`Beta2 due to significant changes in code. There is no direct upgrade flow from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. +!!! important + This is the {{release}} version of the extension and **it is not meant for production use yet**. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). + [Versions and Supported PostgreSQL Deployments](supported-versions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/install.md b/contrib/pg_tde/documentation/docs/install.md index 01ada03b178a1..11cc03623c32d 100644 --- a/contrib/pg_tde/documentation/docs/install.md +++ b/contrib/pg_tde/documentation/docs/install.md @@ -1,5 +1,9 @@ # Install pg_tde + + To install `pg_tde`, use one of the following methods: === ":octicons-terminal-16: Package manager" diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index 8a5cc02020cfe..f5306753eaa77 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -2,7 +2,7 @@ `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. -* [pg_tde Release Candidate 2 ({{date.RC}})](rc2.md) +* [pg_tde Release Candidate 2 (RC2) ({{date.RC2}})](rc2.md) * [pg_tde Release Candidate ({{date.RC}})](rc.md) * [pg_tde Beta2 (2024-12-16)](beta2.md) * [pg_tde Beta (2024-06-30)](beta.md) diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index 365412e50b1a3..c39754704fe0a 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -5,5 +5,5 @@ pgversion17: '17.5.1' tdebranch: TDE_REL_17_STABLE date: - RC2: '2025-05-21' + RC2: '2025-05-29' RC: '2025-03-27' From a4059f03dfb156fedd953afe715ea575f195680d Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 29 May 2025 19:19:46 +0300 Subject: [PATCH 405/796] Updated yum install command (#375) updated command line --- contrib/pg_tde/documentation/docs/yum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/docs/yum.md b/contrib/pg_tde/documentation/docs/yum.md index a998b7f1f7a3e..29af42f632bf2 100644 --- a/contrib/pg_tde/documentation/docs/yum.md +++ b/contrib/pg_tde/documentation/docs/yum.md @@ -62,7 +62,7 @@ You need the `percona-release` repository management tool that enables the desir Run the following command to install `pg_tde`: ```{.bash data-prompt="$"} - $ sudo yum -y install percona-postgresql17 + $ sudo yum -y install percona-postgresql17 percona-postgresql17-contrib ``` ## Next steps From 81100a31e198b00b36815b97330b58aae4428834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 30 May 2025 12:43:34 +0200 Subject: [PATCH 406/796] Return void from add/change key provider functions The returned provider id was not useful for end-users as they cannot do anything with it. They always use the provider name when interacting with these settings. If they really want to see these ids they can easily just use the functions to list all providers to see them. The reason for this change is that it might be confusing for users when the function to create a global provider returns -1 on success without any indication that it's just the generated id and not an error code. --- contrib/pg_tde/expected/access_control.out | 2 +- contrib/pg_tde/expected/alter_index.out | 2 +- contrib/pg_tde/expected/cache_alloc.out | 2 +- .../pg_tde/expected/change_access_method.out | 2 +- contrib/pg_tde/expected/create_database.out | 4 +- .../pg_tde/expected/default_principal_key.out | 2 +- .../pg_tde/expected/insert_update_delete.out | 2 +- contrib/pg_tde/expected/key_provider.out | 14 ++--- contrib/pg_tde/expected/kmip_test.out | 2 +- contrib/pg_tde/expected/partition_table.out | 2 +- .../pg_tde/expected/pg_tde_is_encrypted.out | 2 +- contrib/pg_tde/expected/recreate_storage.out | 2 +- contrib/pg_tde/expected/relocate.out | 2 +- contrib/pg_tde/expected/tablespace.out | 2 +- contrib/pg_tde/expected/toast_decrypt.out | 2 +- contrib/pg_tde/expected/vault_v2_test.out | 4 +- contrib/pg_tde/pg_tde--1.0-rc.sql | 56 +++++++++---------- contrib/pg_tde/src/catalog/tde_keyring.c | 4 +- contrib/pg_tde/t/expected/001_basic.out | 2 +- contrib/pg_tde/t/expected/002_rotate_key.out | 8 +-- .../pg_tde/t/expected/003_remote_config.out | 2 +- contrib/pg_tde/t/expected/004_file_config.out | 2 +- .../t/expected/006_remote_vault_config.out | 2 +- contrib/pg_tde/t/expected/007_tde_heap.out | 2 +- .../t/expected/008_key_rotate_tablespace.out | 2 +- contrib/pg_tde/t/expected/009_wal_encrypt.out | 2 +- .../t/expected/010_change_key_provider.out | 4 +- .../pg_tde/t/expected/011_unlogged_tables.out | 2 +- contrib/pg_tde/t/expected/012_replication.out | 4 +- .../pg_tde/t/expected/013_crash_recovery.out | 4 +- 30 files changed, 72 insertions(+), 72 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 75d58486431e4..4b385eb9cd78e 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) CREATE USER regress_pg_tde_access_control; diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index 424c390204cc9..72575e8a548e6 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index 215fa628deeec..86e060fae585a 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index d4841ee890c83..fd95f35489c89 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index d3b0e60225962..16acc9d8d4c2f 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -7,7 +7,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/template_provider.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); @@ -26,7 +26,7 @@ INSERT INTO test_plain (x) VALUES (30), (40); SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -6 + (1 row) SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index c3236e55f685e..114f5e5dba896 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -5 + (1 row) -- Should fail: no default principal key for the server yet diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index b97a7affe3aab..9db4133a07321 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 1829c09972540..fbf9eec4d90a0 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -12,7 +12,7 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -24,7 +24,7 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 2 + (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); @@ -69,13 +69,13 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -1 + (1 row) SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_keyring2.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -2 + (1 row) SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); @@ -271,7 +271,7 @@ ERROR: unexpected boolean in field "path" SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); pg_tde_add_global_key_provider_file ------------------------------------- - -3 + (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); @@ -286,7 +286,7 @@ ERROR: could not fetch key "server-key" used as server key from modified key pr SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1'); pg_tde_add_global_key_provider_file ------------------------------------- - -4 + (1 row) SELECT current_database() AS regress_database @@ -310,7 +310,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider'); diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index ffb428896f1b7..66cf91d658680 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -2,7 +2,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); pg_tde_add_database_key_provider_kmip --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index c5c5dd07e83a2..704bb98598c72 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -2,7 +2,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index 7a1a94c01e0a1..fc85af7de597a 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index aa468b7f57092..235e75b70473a 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/relocate.out b/contrib/pg_tde/expected/relocate.out index af00e872ce9db..ce611ce3d3fa8 100644 --- a/contrib/pg_tde/expected/relocate.out +++ b/contrib/pg_tde/expected/relocate.out @@ -6,7 +6,7 @@ CREATE EXTENSION pg_tde SCHEMA other; SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT other.pg_tde_grant_key_viewer_to_role('public'); diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index 4d7bffce68de6..de34caa969d70 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index 4ca99eea46751..e7d2d11370eda 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index b4ff2a1ffdfac..6c2766f6894e0 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- - 1 + (1 row) -- FAILS @@ -19,7 +19,7 @@ HINT: create one using pg_tde_set_key before using encrypted tables SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- - 2 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 3b6e62567b79f..64e4c0c100b6d 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -5,12 +5,12 @@ -- Key Provider Management CREATE FUNCTION pg_tde_add_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) -RETURNS INT +RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -20,7 +20,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -34,7 +34,7 @@ CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -51,7 +51,7 @@ CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -69,7 +69,7 @@ CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_ca_path TEXT, kmip_cert_path TEXT, kmip_key_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -88,7 +88,7 @@ CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_ca_path JSON, kmip_cert_path JSON, kmip_key_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -121,12 +121,12 @@ AS 'MODULE_PATHNAME'; -- Global Tablespace Key Provider Management CREATE FUNCTION pg_tde_add_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) -RETURNS INT +RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -136,7 +136,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -150,7 +150,7 @@ CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -167,7 +167,7 @@ CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -185,7 +185,7 @@ CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, kmip_ca_path TEXT, kmip_cert_path TEXT, kmip_key_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -204,7 +204,7 @@ CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, kmip_ca_path JSON, kmip_cert_path JSON, kmip_key_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -219,12 +219,12 @@ END; -- Key Provider Management CREATE FUNCTION pg_tde_change_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) -RETURNS INT +RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -234,7 +234,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -248,7 +248,7 @@ CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -265,7 +265,7 @@ CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -283,7 +283,7 @@ CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_ca_path TEXT, kmip_cert_path TEXT, kmip_key_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -302,7 +302,7 @@ CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_ca_path JSON, kmip_cert_path JSON, kmip_key_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -317,12 +317,12 @@ END; -- Global Tablespace Key Provider Management CREATE FUNCTION pg_tde_change_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) -RETURNS INT +RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -332,7 +332,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -346,7 +346,7 @@ CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -363,7 +363,7 @@ CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -381,7 +381,7 @@ CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, kmip_ca_path TEXT, kmip_cert_path TEXT, kmip_key_path TEXT) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in @@ -400,7 +400,7 @@ CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, kmip_ca_path JSON, kmip_cert_path JSON, kmip_key_path JSON) -RETURNS INT +RETURNS VOID LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index afd4236eba7a3..5004034de7d33 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -235,7 +235,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) modify_key_provider_info(&provider, dbOid, true); - PG_RETURN_INT32(provider.provider_id); + PG_RETURN_VOID(); } Datum @@ -294,7 +294,7 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) provider.provider_type = get_keyring_provider_from_typename(provider_type); save_new_key_provider_info(&provider, dbOid, true); - PG_RETURN_INT32(provider.provider_id); + PG_RETURN_VOID(); } Datum diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index fc7e373f111cc..4e8743c56e2b1 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -11,7 +11,7 @@ HINT: create one using pg_tde_set_key before using encrypted tables SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_001_basic.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index a0e4cae0ef20c..0a4b5c65eda0d 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -2,25 +2,25 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 2 + (1 row) SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2g.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -1 + (1 row) SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -2 + (1 row) SELECT pg_tde_list_all_database_key_providers(); diff --git a/contrib/pg_tde/t/expected/003_remote_config.out b/contrib/pg_tde/t/expected/003_remote_config.out index d547046d7d501..9205431bb2ade 100644 --- a/contrib/pg_tde/t/expected/003_remote_config.out +++ b/contrib/pg_tde/t/expected/003_remote_config.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello')); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); diff --git a/contrib/pg_tde/t/expected/004_file_config.out b/contrib/pg_tde/t/expected/004_file_config.out index c2910206332fe..8031b7df0570d 100644 --- a/contrib/pg_tde/t/expected/004_file_config.out +++ b/contrib/pg_tde/t/expected/004_file_config.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location')); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); diff --git a/contrib/pg_tde/t/expected/006_remote_vault_config.out b/contrib/pg_tde/t/expected/006_remote_vault_config.out index 3afa1d253d5f8..9a467d9b13876 100644 --- a/contrib/pg_tde/t/expected/006_remote_vault_config.out +++ b/contrib/pg_tde/t/expected/006_remote_vault_config.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token'), json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url'), to_json('secret'::text), NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'vault-provider'); diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/007_tde_heap.out index c3ea0e80ac6e7..d8dca8b986a2f 100644 --- a/contrib/pg_tde/t/expected/007_tde_heap.out +++ b/contrib/pg_tde/t/expected/007_tde_heap.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out index b329fc7ac610f..fdb057433b1df 100644 --- a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out @@ -4,7 +4,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/009_wal_encrypt.out index cdd8f87771e6e..27963ea824afd 100644 --- a/contrib/pg_tde/t/expected/009_wal_encrypt.out +++ b/contrib/pg_tde/t/expected/009_wal_encrypt.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -1 + (1 row) SELECT pg_tde_verify_server_key(); diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/010_change_key_provider.out index 73c6b280d1757..3ebd708be0850 100644 --- a/contrib/pg_tde/t/expected/010_change_key_provider.out +++ b/contrib/pg_tde/t/expected/010_change_key_provider.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_list_all_database_key_providers(); @@ -42,7 +42,7 @@ SELECT * FROM test_enc ORDER BY id; SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per'); pg_tde_change_database_key_provider_file ------------------------------------------ - 1 + (1 row) SELECT pg_tde_list_all_database_key_providers(); diff --git a/contrib/pg_tde/t/expected/011_unlogged_tables.out b/contrib/pg_tde/t/expected/011_unlogged_tables.out index 71c52786b134f..b507e48ff3ba4 100644 --- a/contrib/pg_tde/t/expected/011_unlogged_tables.out +++ b/contrib/pg_tde/t/expected/011_unlogged_tables.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/012_replication.out b/contrib/pg_tde/t/expected/012_replication.out index ca2938d34bb67..744953c213dc3 100644 --- a/contrib/pg_tde/t/expected/012_replication.out +++ b/contrib/pg_tde/t/expected/012_replication.out @@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); @@ -59,7 +59,7 @@ SELECT * FROM test_plain ORDER BY x; SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -1 + (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/013_crash_recovery.out b/contrib/pg_tde/t/expected/013_crash_recovery.out index 75cb4fa3bed81..f9e39b5748931 100644 --- a/contrib/pg_tde/t/expected/013_crash_recovery.out +++ b/contrib/pg_tde/t/expected/013_crash_recovery.out @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per'); pg_tde_add_global_key_provider_file ------------------------------------- - -1 + (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'global_keyring'); @@ -14,7 +14,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'gl SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per'); pg_tde_add_database_key_provider_file --------------------------------------- - 1 + (1 row) SELECT pg_tde_set_key_using_database_key_provider('db_key', 'db_keyring'); From 856da09dba838c07d37c7bfea22966645e02c964 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 30 May 2025 16:41:44 +0300 Subject: [PATCH 407/796] Minor fixes post RC2 (#377) yum with pgversion 17.5, also modified pg version to 17.5, and slight rewording for Indexes in the encrypts topic --- contrib/pg_tde/documentation/docs/apt.md | 14 +++++++------- .../documentation/docs/index/tde-encrypts.md | 2 +- contrib/pg_tde/documentation/docs/yum.md | 6 +++--- contrib/pg_tde/documentation/variables.yml | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/apt.md b/contrib/pg_tde/documentation/docs/apt.md index 0b2414c23569b..da8f9a9f9ac93 100644 --- a/contrib/pg_tde/documentation/docs/apt.md +++ b/contrib/pg_tde/documentation/docs/apt.md @@ -21,31 +21,31 @@ Check the [list of supported platforms](install.md#__tabbed_1_1) before continui Install them with the following command: ```{.bash data-prompt="$"} - $ sudo apt-get install -y wget gnupg2 curl lsb-release + sudo apt-get install -y wget gnupg2 curl lsb-release ``` 2. Fetch the `percona-release` package ```{.bash data-prompt="$"} - $ sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb + sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb ``` 3. Install `percona-release` ```{.bash data-prompt="$"} - $ sudo dpkg -i percona-release_latest.generic_all.deb + sudo dpkg -i percona-release_latest.generic_all.deb ``` 4. Enable the Percona Distribution for PostgreSQL repository ```{.bash data-prompt="$"} - $ sudo percona-release enable-only ppg-{{pgversion17}} + sudo percona-release enable-only ppg-{{pgversion17}} ``` 6. Update the local cache - ```bash - sudo apt-get update + ```{.bash data-prompt="$"} + sudo apt-get update ``` ## Install pg_tde {.power-number} @@ -67,7 +67,7 @@ Check the [list of supported platforms](install.md#__tabbed_1_1) before continui After all [preconditions](#preconditions) are met, run the following command to install `pg_tde`: ```{.bash data-prompt="$"} - $ sudo apt-get install -y percona-postgresql-17 + sudo apt-get install -y percona-postgresql-17 ``` ## Next steps diff --git a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md index 77023c35469a2..53bbb82b2198f 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md +++ b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md @@ -5,7 +5,7 @@ * **User data** in tables using the extension, including associated TOAST data. The table metadata (column names, data types, etc.) is not encrypted. * **Temporary tables** created during the query execution, for data tables created using the extension. * **Write-Ahead Log (WAL) data** for the entire database cluster. This includes WAL data in encrypted and non-encrypted tables. -* **Indexes** associated encrypted tables. +* **Indexes** on encrypted tables. * **Logical replication data** for encrypted tables (ensures encrypted content is preserved across replicas). [Table Access Methods and TDE](table-access-method.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/yum.md b/contrib/pg_tde/documentation/docs/yum.md index 29af42f632bf2..d87f81e521cbc 100644 --- a/contrib/pg_tde/documentation/docs/yum.md +++ b/contrib/pg_tde/documentation/docs/yum.md @@ -34,13 +34,13 @@ You need the `percona-release` repository management tool that enables the desir 1. Install `percona-release`: ```{.bash data-prompt="$"} - $ sudo yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm + sudo yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm ``` 2. Enable the repository. ```{.bash data-prompt="$"} - $ sudo percona-release enable-only ppg-{{pgversion17}} + sudo percona-release enable-only ppg-17.5 ``` ## Install pg_tde {.power-number} @@ -62,7 +62,7 @@ You need the `percona-release` repository management tool that enables the desir Run the following command to install `pg_tde`: ```{.bash data-prompt="$"} - $ sudo yum -y install percona-postgresql17 percona-postgresql17-contrib + sudo yum -y install percona-postgresql17-server percona-postgresql17-contrib ``` ## Next steps diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index c39754704fe0a..9d4aa480f8364 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -1,7 +1,7 @@ #Variables used throughout the docs release: 'RC2' -pgversion17: '17.5.1' +pgversion17: '17.5' tdebranch: TDE_REL_17_STABLE date: From ff5d4e5a20382fc6e9f4aef3b09cef152c1526b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 30 May 2025 16:55:09 +0200 Subject: [PATCH 408/796] Remove non-sensical comment The code in this file doesn't do any "relation fork management". --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 65adffbdb64c6..b03537ebccd94 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1,15 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_tdemap.c - * tde relation fork manager code - * - * - * IDENTIFICATION - * src/access/pg_tde_tdemap.c - * - *------------------------------------------------------------------------- - */ - #include "postgres.h" #include "access/pg_tde_tdemap.h" #include "common/file_perm.h" From 2c8faffe5f49e620c966da60003bfdb0b3b4b526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 30 May 2025 17:02:19 +0200 Subject: [PATCH 409/796] Clean up some comments referencing old key files pg_tde.dat and pg_tde.map is no longer a thing, so clean up some comments mentioning them. --- .../docs/command-line-tools/pg-waldump.md | 5 ++-- contrib/pg_tde/src/access/pg_tde_tdemap.c | 24 +++++++------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md index 6396249342840..b38441d58e640 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md @@ -5,9 +5,8 @@ To read encrypted WAL records, `pg_waldump` supports the following additional arguments: * `keyring_path`: the directory where keyring configuration files for WAL are stored. These files include: - * `pg_tde.map` - * `pg_tde.dat` - * `pg_tde_keyrings` + * `1664_keys` + * `1664_providers` !!! note diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index b03537ebccd94..b0e327eb83622 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -121,7 +121,7 @@ tde_sprint_key(InternalKey *k) } /* - * Generates a new internal key for WAL and adds it to the _dat file. + * Generates a new internal key for WAL and adds it to the key file. * * We have a special function for WAL as it is being called during recovery * start so there should be no XLog records and aquired locks. The key is @@ -152,7 +152,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat } /* - * Deletes the key map file for a given database. + * Deletes the key file for a given database. */ void pg_tde_delete_tde_files(Oid dbOid) @@ -183,7 +183,7 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) } /* - * Creates the key map file and saves the principal key information. + * Creates the key file and saves the principal key information. * * If the file pre-exist, it truncates the file before adding principal key * information. @@ -315,15 +315,7 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, } /* - * Calls the create map entry function to get an index into the keydata. This - * The keydata function will then write the encrypted key on the desired - * location. - * - * Key Map Table [pg_tde.map]: - * header: {Format Version, Principal Key Name} - * data: {OID, Flag, index of key in pg_tde.dat}... - * - * The caller must hold an exclusive lock on the map file to avoid + * The caller must hold an exclusive lock on the key file to avoid * concurrent in place updates leading to data conflicts. */ void @@ -601,7 +593,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) } /* - * Open for write and Validate File Header [pg_tde.*]: + * Open for write and Validate File Header: * header: {Format Version, Principal Key Name} * * Returns the file descriptor in case of a success. Otherwise, error @@ -736,7 +728,7 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) } /* - * Open for read and Validate File Header [pg_tde.*]: + * Open for read and Validate File Header: * header: {Format Version, Principal Key Name} * * Returns the file descriptor in case of a success. Otherwise, error @@ -762,7 +754,7 @@ pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr } /* - * Open a TDE file [pg_tde.*]: + * Open a TDE file: * * Returns the file descriptor in case of a success. Otherwise, error * is raised except when ignore_missing is true and the file does not exit. @@ -852,7 +844,7 @@ pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid } /* - * Get the principal key from the map file. The caller must hold + * Get the principal key from the key file. The caller must hold * a LW_SHARED or higher lock on files before calling this function. */ TDESignedPrincipalKeyInfo * From 2c7bf4b1925e9e3e99a516bc8d1d1127be403dfe Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 19:00:56 +0200 Subject: [PATCH 410/796] Fix broken code coverage job An extra space had snuck into the script. --- ci_scripts/make-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci_scripts/make-build.sh b/ci_scripts/make-build.sh index 63b139e805957..9fa762033138b 100755 --- a/ci_scripts/make-build.sh +++ b/ci_scripts/make-build.sh @@ -6,7 +6,7 @@ for arg in "$@" do case "$arg" in --enable-coverage) - ARGS +=" --enable-coverage" + ARGS+=" --enable-coverage" ;; esac done From 4c9e99731fb64f154bf065f81903f2773cca3bc5 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 19:07:31 +0200 Subject: [PATCH 411/796] Run installation of llvm as root it is unclear why it seemed to work but when adding "set -e" we got an error from the script. --- ci_scripts/ubuntu-deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index 4646bee3ad423..898028675e897 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -53,7 +53,7 @@ DEPS=( sudo apt-get update sudo apt-get install -y ${DEPS[@]} -bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" +sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" pip3 install meson # Vault From d4ad80fa327cb795b322af0f79e8b55dd7d45807 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 21:05:24 +0200 Subject: [PATCH 412/796] Disable checking indentation of upstream code The upstream code has been incorrectly indented since PostgreSQL 17.4 but the lack of set -e in the script hid that from us. --- ci_scripts/run-pgindent.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci_scripts/run-pgindent.sh b/ci_scripts/run-pgindent.sh index 4b9ba00f278b8..d93784dcbefbe 100755 --- a/ci_scripts/run-pgindent.sh +++ b/ci_scripts/run-pgindent.sh @@ -17,7 +17,8 @@ cd "$SCRIPT_DIR/.." export PATH=$SCRIPT_DIR/../src/tools/pgindent/:$INSTALL_DIR/bin/:$PATH # Check everything except pg_tde with the list in the repo -pgindent --typedefs=src/tools/pgindent/typedefs.list --excludes=<(echo "contrib/pg_tde") "$@" . +# TODO: Disabled due to incorrectly indented upsrteam as of 17.4 +#pgindent --typedefs=src/tools/pgindent/typedefs.list --excludes=<(echo "contrib/pg_tde") "$@" . # Check pg_tde with the fresh list extraxted from the object file pgindent --typedefs=combined.typedefs "$@" contrib/pg_tde From 61e53af14830ab500d3137e134786a4d7d9d98ad Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 19:03:25 +0200 Subject: [PATCH 413/796] Consistently use set -e in all our scripts It recently hid a bug in the code coverage job. --- ci_scripts/backup/pg_basebackup_test.sh | 1 + ci_scripts/configure-global-tde.sh | 1 + ci_scripts/dump-typedefs.sh | 2 ++ ci_scripts/make-build.sh | 2 ++ ci_scripts/make-test-global-tde.sh | 1 + ci_scripts/meson-build.sh | 2 ++ ci_scripts/run-pgindent.sh | 2 ++ ci_scripts/run-pgperltidy.sh | 2 ++ ci_scripts/setup-keyring-servers.sh | 2 ++ ci_scripts/ubuntu-deps.sh | 2 ++ 10 files changed, 17 insertions(+) diff --git a/ci_scripts/backup/pg_basebackup_test.sh b/ci_scripts/backup/pg_basebackup_test.sh index a9b4f4199fe95..23b2ca28231dc 100755 --- a/ci_scripts/backup/pg_basebackup_test.sh +++ b/ci_scripts/backup/pg_basebackup_test.sh @@ -1,4 +1,5 @@ #!/bin/bash + set -e # Exit on errors set -u # Treat unset variables as errors set -o pipefail # Stop on pipeline errors diff --git a/ci_scripts/configure-global-tde.sh b/ci_scripts/configure-global-tde.sh index 40d4cdbd26dd5..4d4b0648903c1 100644 --- a/ci_scripts/configure-global-tde.sh +++ b/ci_scripts/configure-global-tde.sh @@ -1,4 +1,5 @@ #!/bin/bash + set -e SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" diff --git a/ci_scripts/dump-typedefs.sh b/ci_scripts/dump-typedefs.sh index 6f854a4889570..eabe70815d9c8 100755 --- a/ci_scripts/dump-typedefs.sh +++ b/ci_scripts/dump-typedefs.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" cd "$SCRIPT_DIR/.." diff --git a/ci_scripts/make-build.sh b/ci_scripts/make-build.sh index 9fa762033138b..79b5cf9281666 100755 --- a/ci_scripts/make-build.sh +++ b/ci_scripts/make-build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + ARGS= for arg in "$@" diff --git a/ci_scripts/make-test-global-tde.sh b/ci_scripts/make-test-global-tde.sh index ee4a8787fbf5c..ef090a7ba7481 100755 --- a/ci_scripts/make-test-global-tde.sh +++ b/ci_scripts/make-test-global-tde.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e + ADD_FLAGS= for arg in "$@" diff --git a/ci_scripts/meson-build.sh b/ci_scripts/meson-build.sh index 1cd3145a04b93..6cce676fabf32 100755 --- a/ci_scripts/meson-build.sh +++ b/ci_scripts/meson-build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + ENABLE_COVERAGE= for arg in "$@" diff --git a/ci_scripts/run-pgindent.sh b/ci_scripts/run-pgindent.sh index d93784dcbefbe..77617e558cb0c 100755 --- a/ci_scripts/run-pgindent.sh +++ b/ci_scripts/run-pgindent.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" INSTALL_DIR="$SCRIPT_DIR/../../pginst" cd "$SCRIPT_DIR/../" diff --git a/ci_scripts/run-pgperltidy.sh b/ci_scripts/run-pgperltidy.sh index c59d50cc2a8f0..e7a84018129db 100755 --- a/ci_scripts/run-pgperltidy.sh +++ b/ci_scripts/run-pgperltidy.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" cd "$SCRIPT_DIR/../" diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index 81caf4003f89c..c9f523c8129e8 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + SCRIPT_DIR="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" cd /tmp diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index 898028675e897..d998289a55163 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + DEPS=( # Setup wget From 5df4e021df4ff0e0f994702a6ef8f357ed77fe0f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 22:57:30 +0200 Subject: [PATCH 414/796] Add test for duplicate key provider name Additionally remove some pointless queries from the test. --- contrib/pg_tde/expected/key_provider.out | 13 ++----------- contrib/pg_tde/sql/key_provider.sql | 6 +----- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index fbf9eec4d90a0..82a199ee18b9f 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -4,29 +4,20 @@ ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: unexpected field "foo" for external value "path" -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------- -(0 rows) - SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- (1 row) -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); pg_tde_add_database_key_provider_file --------------------------------------- (1 row) +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_dup.per'); +ERROR: Key provider "file-provider" already exists. SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+-------------------------------------------- diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 665aa28049be1..f2b1f5ff40941 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -3,16 +3,12 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -SELECT * FROM pg_tde_list_all_database_key_providers(); - SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); -SELECT * FROM pg_tde_list_all_database_key_providers(); - SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); +SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_dup.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_verify_key(); - SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); SELECT pg_tde_verify_key(); From 106033bf81e53f3f5ceaef9d2edd8beb72e123cc Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 23:28:54 +0200 Subject: [PATCH 415/796] Remove undocumented shorthand for when setting keys If we passed NULL as the pkey provider name we used to pick the current key provider which was an undcoumented feature which made the code and API harder to understand. Let's jsut remove it and see if people complain. --- contrib/pg_tde/expected/key_provider.out | 9 ++++++ contrib/pg_tde/pg_tde--1.0-rc.sql | 8 ++--- contrib/pg_tde/sql/key_provider.sql | 6 ++++ .../pg_tde/src/catalog/tde_principal_key.c | 31 +++++-------------- contrib/pg_tde/t/002_rotate_key.pl | 3 +- contrib/pg_tde/t/expected/002_rotate_key.out | 2 +- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 82a199ee18b9f..587c0665e7680 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -319,6 +319,15 @@ SELECT pg_tde_delete_database_key_provider(NULL); ERROR: provider_name cannot be null SELECT pg_tde_delete_global_key_provider(NULL); ERROR: provider_name cannot be null +-- Setting principal key fails if provider name is NULL +SELECT pg_tde_set_default_key_using_global_key_provider('key', NULL); +ERROR: key provider name cannot be null +SELECT pg_tde_set_key_using_database_key_provider('key', NULL); +ERROR: key provider name cannot be null +SELECT pg_tde_set_key_using_global_key_provider('key', NULL); +ERROR: key provider name cannot be null +SELECT pg_tde_set_server_key_using_global_key_provider('key', NULL); +ERROR: key provider name cannot be null -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); ERROR: key name cannot be null diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 64e4c0c100b6d..6df3771aeaaf5 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -419,22 +419,22 @@ STRICT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT DEFAULT NULL, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index f2b1f5ff40941..ee844af0fada6 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -156,6 +156,12 @@ DROP DATABASE db_using_database_provider; SELECT pg_tde_delete_database_key_provider(NULL); SELECT pg_tde_delete_global_key_provider(NULL); +-- Setting principal key fails if provider name is NULL +SELECT pg_tde_set_default_key_using_global_key_provider('key', NULL); +SELECT pg_tde_set_key_using_database_key_provider('key', NULL); +SELECT pg_tde_set_key_using_global_key_provider('key', NULL); +SELECT pg_tde_set_server_key_using_global_key_provider('key', NULL); + -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); SELECT pg_tde_set_key_using_database_key_provider(NULL, 'file-keyring'); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index ab4516a8a6e2e..5e73d35bbda35 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -228,10 +228,10 @@ void set_principal_key_with_keyring(const char *key_name, const char *provider_name, Oid providerOid, Oid dbOid, bool ensure_new_key) { - TDEPrincipalKey *curr_principal_key = NULL; - TDEPrincipalKey *new_principal_key = NULL; + TDEPrincipalKey *curr_principal_key; + TDEPrincipalKey *new_principal_key; LWLock *lock_files = tde_lwlock_enc_keys(); - bool already_has_key = false; + bool already_has_key; GenericKeyring *new_keyring; const KeyInfo *keyInfo = NULL; @@ -249,21 +249,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, curr_principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); already_has_key = (curr_principal_key != NULL); - if (provider_name == NULL && !already_has_key) - { - ereport(ERROR, - errmsg("provider_name is a required parameter when creating the first principal key for a database")); - } - - if (provider_name != NULL) - { - new_keyring = GetKeyProviderByName(provider_name, providerOid); - } - else - { - new_keyring = GetKeyProviderByID(curr_principal_key->keyInfo.keyringId, - curr_principal_key->keyInfo.databaseId); - } + new_keyring = GetKeyProviderByName(provider_name, providerOid); { KeyringReturnCodes kr_ret; @@ -292,11 +278,6 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (keyInfo == NULL) keyInfo = KeyringGenerateNewKeyAndStore(new_keyring, key_name, PRINCIPAL_KEY_LEN); - if (keyInfo == NULL) - { - ereport(ERROR, errmsg("failed to retrieve/create principal key.")); - } - new_principal_key = palloc_object(TDEPrincipalKey); new_principal_key->keyInfo.databaseId = dbOid; new_principal_key->keyInfo.keyringId = new_keyring->keyring_id; @@ -549,6 +530,10 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key name \"\" is too short")); + if (provider_name == NULL) + ereport(ERROR, + errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("key provider name cannot be null")); ereport(LOG, errmsg("Setting principal key [%s : %s] for the database", key_name, provider_name)); diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/002_rotate_key.pl index eb188448a999c..02da31aca98b8 100644 --- a/contrib/pg_tde/t/002_rotate_key.pl +++ b/contrib/pg_tde/t/002_rotate_key.pl @@ -46,7 +46,8 @@ # Rotate key PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1');"); + "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1', 'file-vault');" +); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); PGTDE::append_to_result_file("-- server restart"); diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/002_rotate_key.out index 0a4b5c65eda0d..4f7a729433615 100644 --- a/contrib/pg_tde/t/expected/002_rotate_key.out +++ b/contrib/pg_tde/t/expected/002_rotate_key.out @@ -45,7 +45,7 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) -SELECT pg_tde_set_key_using_database_key_provider('rotated-key1'); +SELECT pg_tde_set_key_using_database_key_provider('rotated-key1', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- From 84254ebc450bc4180631e2e966e3a6be93d36a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Sat, 31 May 2025 11:07:51 +0200 Subject: [PATCH 416/796] Remove STRICT from functions that has no input It doesn't do anything for functions that doesn't have any input parameters. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 6df3771aeaaf5..24854c92d77fb 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -107,7 +107,7 @@ CREATE FUNCTION pg_tde_list_all_database_key_providers OUT provider_type TEXT, OUT options JSON) RETURNS SETOF RECORD -LANGUAGE C STRICT +LANGUAGE C AS 'MODULE_PATHNAME'; CREATE FUNCTION pg_tde_list_all_global_key_providers @@ -116,7 +116,7 @@ CREATE FUNCTION pg_tde_list_all_global_key_providers OUT provider_type TEXT, OUT options JSON) RETURNS SETOF RECORD -LANGUAGE C STRICT +LANGUAGE C AS 'MODULE_PATHNAME'; -- Global Tablespace Key Provider Management From 84d1b5663fc1acaa65f4d2e5b050e2c9152a853f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Sat, 31 May 2025 11:00:17 +0200 Subject: [PATCH 417/796] Fix non-functioning helper sql function This function did not work as it tried to create a vault-v2 provider using kmip options. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 24854c92d77fb..a2eb240bcadac 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -405,7 +405,7 @@ LANGUAGE SQL BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. - SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, + SELECT pg_tde_change_global_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, 'caPath' VALUE kmip_ca_path, From 225a23cd35e9a444619b2b0d9b63bbd3194afe2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 8 May 2025 10:04:40 +0200 Subject: [PATCH 418/796] Revoke all from public on c functions by default Even though we do have some access control in most of our C functions, it seems dangerous to not revoke them from public by default. A whitelist of allowed functions seems safer than a black list. Also include non-C functions which are security definer. --- contrib/pg_tde/expected/access_control.out | 13 ++++++++++- contrib/pg_tde/pg_tde--1.0-rc.sql | 26 ++++++++++++++++++---- contrib/pg_tde/sql/access_control.sql | 15 +++++++++++-- contrib/pg_tde/t/001_basic.pl | 17 ++++++++++++++ contrib/pg_tde/t/expected/001_basic.out | 18 +++++++++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 4b385eb9cd78e..9a10fb7906b91 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -75,7 +75,18 @@ SELECT pg_tde_verify_server_key(); ERROR: principal key not configured for current database SELECT pg_tde_verify_default_key(); ERROR: principal key not configured for current database --- only superuser +-- Only superusers can execute key management functions, regardless of role grants +RESET ROLE; +GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +SET ROLE regress_pg_tde_access_control; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); ERROR: must be superuser to modify key providers SELECT pg_tde_change_global_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index a2eb240bcadac..b53b6bcce36a3 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -8,6 +8,7 @@ CREATE FUNCTION pg_tde_add_database_key_provider(provider_type TEXT, provider_na RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) FROM PUBLIC; CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS VOID @@ -109,6 +110,7 @@ CREATE FUNCTION pg_tde_list_all_database_key_providers RETURNS SETOF RECORD LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_list_all_database_key_providers() FROM PUBLIC; CREATE FUNCTION pg_tde_list_all_global_key_providers (OUT id INT, @@ -118,12 +120,14 @@ CREATE FUNCTION pg_tde_list_all_global_key_providers RETURNS SETOF RECORD LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_list_all_global_key_providers() FROM PUBLIC; -- Global Tablespace Key Provider Management CREATE FUNCTION pg_tde_add_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) FROM PUBLIC; CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS VOID @@ -222,6 +226,7 @@ CREATE FUNCTION pg_tde_change_database_key_provider(provider_type TEXT, provider RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) FROM PUBLIC; CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS VOID @@ -320,6 +325,7 @@ CREATE FUNCTION pg_tde_change_global_key_provider(provider_type TEXT, provider_n RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_change_global_key_provider(TEXT, TEXT, JSON) FROM PUBLIC; CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path TEXT) RETURNS VOID @@ -423,36 +429,44 @@ CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provid RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; + CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C; +REVOKE ALL ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; CREATE FUNCTION pg_tde_verify_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_verify_key() FROM PUBLIC; CREATE FUNCTION pg_tde_verify_server_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_verify_server_key() FROM PUBLIC; CREATE FUNCTION pg_tde_verify_default_key() RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_verify_default_key() FROM PUBLIC; CREATE FUNCTION pg_tde_key_info() RETURNS TABLE ( key_name TEXT, @@ -461,6 +475,7 @@ RETURNS TABLE ( key_name TEXT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_key_info() FROM PUBLIC; CREATE FUNCTION pg_tde_server_key_info() RETURNS TABLE ( key_name TEXT, @@ -469,6 +484,7 @@ RETURNS TABLE ( key_name TEXT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_server_key_info() FROM PUBLIC; CREATE FUNCTION pg_tde_default_key_info() RETURNS TABLE ( key_name TEXT, @@ -477,16 +493,19 @@ RETURNS TABLE ( key_name TEXT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_default_key_info() FROM PUBLIC; CREATE FUNCTION pg_tde_delete_global_key_provider(provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_delete_global_key_provider(TEXT) FROM PUBLIC; CREATE FUNCTION pg_tde_delete_database_key_provider(provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_delete_database_key_provider(TEXT) FROM PUBLIC; CREATE FUNCTION pg_tde_version() RETURNS TEXT LANGUAGE C AS 'MODULE_PATHNAME'; @@ -495,6 +514,7 @@ CREATE FUNCTION pg_tdeam_handler(internal) RETURNS TABLE_AM_HANDLER LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tdeam_handler(internal) FROM PUBLIC; CREATE ACCESS METHOD tde_heap TYPE TABLE HANDLER pg_tdeam_handler; COMMENT ON ACCESS METHOD tde_heap IS 'tde_heap table access method'; @@ -503,11 +523,13 @@ CREATE FUNCTION pg_tde_ddl_command_start_capture() RETURNS EVENT_TRIGGER LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_ddl_command_start_capture() FROM PUBLIC; CREATE FUNCTION pg_tde_ddl_command_end_capture() RETURNS EVENT_TRIGGER LANGUAGE C AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_ddl_command_end_capture() FROM PUBLIC; CREATE EVENT TRIGGER pg_tde_ddl_start ON ddl_command_start @@ -588,7 +610,3 @@ BEGIN EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_default_key() FROM %I', target_role); END; $$; - --- Revoking all the privileges from the public role -SELECT pg_tde_revoke_database_key_management_from_role('public'); -SELECT pg_tde_revoke_key_viewer_from_role('public'); diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index dab6e2d6cc7a1..dfcea5e2781b7 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -34,7 +34,19 @@ SELECT pg_tde_verify_key(); SELECT pg_tde_verify_server_key(); SELECT pg_tde_verify_default_key(); --- only superuser +-- Only superusers can execute key management functions, regardless of role grants +RESET ROLE; +GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; + +SET ROLE regress_pg_tde_access_control; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_change_global_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_delete_database_key_provider('local-file-provider'); @@ -44,7 +56,6 @@ SELECT pg_tde_delete_global_key_provider('global-file-provider'); SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); - RESET ROLE; SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/001_basic.pl index 611e4b077e670..567ff508934e1 100644 --- a/contrib/pg_tde/t/001_basic.pl +++ b/contrib/pg_tde/t/001_basic.pl @@ -16,6 +16,23 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +# Only whitelisted C or security definer functions are granted to public by default +PGTDE::psql( + $node, 'postgres', + q{ + SELECT + pg_proc.oid::regprocedure + FROM + pg_catalog.pg_proc + JOIN pg_catalog.pg_language ON prolang = pg_language.oid + LEFT JOIN LATERAL aclexplode(proacl) ON TRUE + WHERE + proname LIKE 'pg_tde%' AND + (lanname = 'c' OR prosecdef) AND + (grantee IS NULL OR grantee = 0) + ORDER BY pg_proc.oid::regprocedure::text; + }); + PGTDE::psql($node, 'postgres', "SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde';"); diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/001_basic.out index 4e8743c56e2b1..99020c9439217 100644 --- a/contrib/pg_tde/t/expected/001_basic.out +++ b/contrib/pg_tde/t/expected/001_basic.out @@ -1,4 +1,22 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; + SELECT + pg_proc.oid::regprocedure + FROM + pg_catalog.pg_proc + JOIN pg_catalog.pg_language ON prolang = pg_language.oid + LEFT JOIN LATERAL aclexplode(proacl) ON TRUE + WHERE + proname LIKE 'pg_tde%' AND + (lanname = 'c' OR prosecdef) AND + (grantee IS NULL OR grantee = 0) + ORDER BY pg_proc.oid::regprocedure::text; + oid +------------------------------- + pg_tde_is_encrypted(regclass) + pg_tde_version() +(2 rows) + + SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; extname | extversion ---------+------------ From ad8a0f0457390f4f113c7e5f9605dee16dad4d49 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 21:39:55 +0200 Subject: [PATCH 419/796] PG-1605, PG-1606 Remove grant/revoke helper functions These helper functions were tricky to use correctly and did not add much vaule. Emulating a role this way does not really work in PostgreSQL. If people want this behavior they should use a real role. --- .../pg_tde/documentation/docs/functions.md | 20 ----- contrib/pg_tde/expected/access_control.out | 74 ------------------- contrib/pg_tde/expected/relocate.out | 6 -- contrib/pg_tde/pg_tde--1.0-rc.sql | 62 ---------------- contrib/pg_tde/sql/access_control.sql | 32 +------- contrib/pg_tde/sql/relocate.sql | 2 - 6 files changed, 1 insertion(+), 195 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 33d4f6922cfcf..ea5095fa8acb8 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -11,26 +11,6 @@ However, database owners can run the “view keys” and “set principal key” * `GRANT EXECUTE` * `REVOKE EXECUTE` -The following functions are also provided for easier management of functionality groups: - -### Database local key management - -Use these functions to grant or revoke permissions to manage the key of the current database. They enable or disable all functions related to the key of the current database: - -* `pg_tde_grant_database_key_management_to_role(role)` -* `pg_tde_revoke_database_key_management_from_role(role)` - -### Global scope key management - -Managment of the global scope is restricted to superusers only. - -### Inspections - -Use these functions to grant or revoke the use of query functions, which do not modify the encryption settings: - -* `pg_tde_grant_key_viewer_to_role(role)` -* `pg_tde_revoke_key_viewer_from_role(role)` - ## Key provider management A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 9a10fb7906b91..8996168f54fc6 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -27,56 +27,7 @@ ERROR: permission denied for function pg_tde_verify_server_key SELECT pg_tde_verify_default_key(); ERROR: permission denied for function pg_tde_verify_default_key RESET ROLE; -SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); - pg_tde_grant_database_key_management_to_role ----------------------------------------------- - -(1 row) - -SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); - pg_tde_grant_key_viewer_to_role ---------------------------------- - -(1 row) - -SET ROLE regress_pg_tde_access_control; --- should now be allowed -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------------+---------------+------------------------------------------- - 1 | local-file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} -(1 row) - -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); - key_name | key_provider_name | key_provider_id --------------+---------------------+----------------- - test-db-key | local-file-provider | 1 -(1 row) - -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_default_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key -SELECT pg_tde_verify_key(); - pg_tde_verify_key -------------------- - -(1 row) - -SELECT pg_tde_verify_server_key(); -ERROR: principal key not configured for current database -SELECT pg_tde_verify_default_key(); -ERROR: principal key not configured for current database -- Only superusers can execute key management functions, regardless of role grants -RESET ROLE; GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; @@ -106,29 +57,4 @@ ERROR: must be superuser to access global key providers SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers RESET ROLE; -SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); - pg_tde_revoke_key_viewer_from_role ------------------------------------- - -(1 row) - -SET ROLE regress_pg_tde_access_control; --- verify the view access is revoked -SELECT pg_tde_list_all_database_key_providers(); -ERROR: permission denied for function pg_tde_list_all_database_key_providers -SELECT pg_tde_list_all_global_key_providers(); -ERROR: permission denied for function pg_tde_list_all_global_key_providers -SELECT pg_tde_key_info(); -ERROR: permission denied for function pg_tde_key_info -SELECT pg_tde_server_key_info(); -ERROR: permission denied for function pg_tde_server_key_info -SELECT pg_tde_default_key_info(); -ERROR: permission denied for function pg_tde_default_key_info -SELECT pg_tde_verify_key(); -ERROR: permission denied for function pg_tde_verify_key -SELECT pg_tde_verify_server_key(); -ERROR: permission denied for function pg_tde_verify_server_key -SELECT pg_tde_verify_default_key(); -ERROR: permission denied for function pg_tde_verify_default_key -RESET ROLE; DROP EXTENSION pg_tde CASCADE; diff --git a/contrib/pg_tde/expected/relocate.out b/contrib/pg_tde/expected/relocate.out index ce611ce3d3fa8..740a69696e89b 100644 --- a/contrib/pg_tde/expected/relocate.out +++ b/contrib/pg_tde/expected/relocate.out @@ -9,12 +9,6 @@ SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_te (1 row) -SELECT other.pg_tde_grant_key_viewer_to_role('public'); - pg_tde_grant_key_viewer_to_role ---------------------------------- - -(1 row) - ALTER EXTENSION pg_tde SET SCHEMA public; ERROR: extension "pg_tde" does not support SET SCHEMA DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index b53b6bcce36a3..352ca049f636f 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -548,65 +548,3 @@ LANGUAGE C AS 'MODULE_PATHNAME'; SELECT pg_tde_extension_initialize(); DROP FUNCTION pg_tde_extension_initialize(); - -CREATE FUNCTION pg_tde_grant_database_key_management_to_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) TO %I', target_role); -END; -$$; - -CREATE FUNCTION pg_tde_grant_key_viewer_to_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_key_info() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_server_key_info() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_default_key_info() TO %I', target_role); - - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_key() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_server_key() TO %I', target_role); - EXECUTE format('GRANT EXECUTE ON FUNCTION pg_tde_verify_default_key() TO %I', target_role); -END; -$$; - -CREATE FUNCTION pg_tde_revoke_database_key_management_from_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) FROM %I', target_role); -END; -$$; - -CREATE FUNCTION pg_tde_revoke_key_viewer_from_role( - target_role TEXT) -RETURNS VOID -LANGUAGE plpgsql -SET search_path = @extschema@ -AS $$ -BEGIN - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_database_key_providers() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_list_all_global_key_providers() FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_key_info() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_server_key_info() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_default_key_info() FROM %I', target_role); - - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_key() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_server_key() FROM %I', target_role); - EXECUTE format('REVOKE EXECUTE ON FUNCTION pg_tde_verify_default_key() FROM %I', target_role); -END; -$$; diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index dfcea5e2781b7..90ca5e9c60bc7 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -19,23 +19,7 @@ SELECT pg_tde_verify_default_key(); RESET ROLE; -SELECT pg_tde_grant_database_key_management_to_role('regress_pg_tde_access_control'); -SELECT pg_tde_grant_key_viewer_to_role('regress_pg_tde_access_control'); - -SET ROLE regress_pg_tde_access_control; - --- should now be allowed -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); -SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_key_info(); -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_default_key_info(); -SELECT pg_tde_verify_key(); -SELECT pg_tde_verify_server_key(); -SELECT pg_tde_verify_default_key(); - -- Only superusers can execute key management functions, regardless of role grants -RESET ROLE; GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; @@ -47,6 +31,7 @@ GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, B GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; + SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_change_global_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_delete_database_key_provider('local-file-provider'); @@ -56,21 +41,6 @@ SELECT pg_tde_delete_global_key_provider('global-file-provider'); SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); -RESET ROLE; - -SELECT pg_tde_revoke_key_viewer_from_role('regress_pg_tde_access_control'); - -SET ROLE regress_pg_tde_access_control; - --- verify the view access is revoked -SELECT pg_tde_list_all_database_key_providers(); -SELECT pg_tde_list_all_global_key_providers(); -SELECT pg_tde_key_info(); -SELECT pg_tde_server_key_info(); -SELECT pg_tde_default_key_info(); -SELECT pg_tde_verify_key(); -SELECT pg_tde_verify_server_key(); -SELECT pg_tde_verify_default_key(); RESET ROLE; diff --git a/contrib/pg_tde/sql/relocate.sql b/contrib/pg_tde/sql/relocate.sql index d9ce03b34f937..f3a79b279789e 100644 --- a/contrib/pg_tde/sql/relocate.sql +++ b/contrib/pg_tde/sql/relocate.sql @@ -8,8 +8,6 @@ CREATE EXTENSION pg_tde SCHEMA other; SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT other.pg_tde_grant_key_viewer_to_role('public'); - ALTER EXTENSION pg_tde SET SCHEMA public; DROP EXTENSION pg_tde; From 591d6048e0f8ecd87e1b627d88feb2cb59eaf436 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 3 Jun 2025 13:50:52 +0200 Subject: [PATCH 420/796] Remove ifdef within the same ifdef Since the whole function is within the same ifdef this ifdef is pointless. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 5e73d35bbda35..b7d894d823ddb 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -679,9 +679,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) /* Make the tuple into a datum */ result = HeapTupleGetDatum(tuple); -#ifndef FRONTEND pfree(keyring); -#endif PG_RETURN_DATUM(result); } From 356f619ad211941e4174a88772d16d1fe95b9e4e Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 29 May 2025 13:24:51 +0300 Subject: [PATCH 421/796] Vault access token must be always in file --- ci_scripts/setup-keyring-servers.sh | 7 +--- .../pg-tde-change-key-provider.md | 2 +- .../pg_tde/documentation/docs/functions.md | 12 +++--- .../vault.md | 6 +-- .../docs/how-to/multi-tenant-setup.md | 4 +- .../documentation/docs/wal-encryption.md | 4 +- contrib/pg_tde/expected/vault_v2_test.out | 7 ++-- contrib/pg_tde/pg_tde--1.0-rc.sql | 32 ++++++++-------- contrib/pg_tde/sql/vault_v2_test.sql | 8 ++-- contrib/pg_tde/src/catalog/tde_keyring.c | 37 +++++++++++++++++-- .../src/catalog/tde_keyring_parse_opts.c | 12 +++--- .../pg_tde/src/include/keyring/keyring_api.h | 1 + .../pg_tde/src/pg_tde_change_key_provider.c | 4 +- .../t/016_pg_tde_change_key_provider.pl | 8 ++-- 14 files changed, 84 insertions(+), 60 deletions(-) diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index c9f523c8129e8..05821fbd3cd7b 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -20,8 +20,5 @@ pykmip-server -f "$SCRIPT_DIR/../contrib/pg_tde/pykmip-server.conf" -l /tmp/kmip TV=$(mktemp) { exec >$TV; vault server -dev; } & sleep 10 -export ROOT_TOKEN=$(cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n) -echo "export ROOT_TOKEN=$ROOT_TOKEN" -if [ -v GITHUB_ACTIONS ]; then - echo "ROOT_TOKEN=$ROOT_TOKEN" >> $GITHUB_ENV -fi +echo $(cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n) > /tmp/vault_test_token +echo "write vault token to /tmp/vault_test_token" diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md index a897634c41c37..f0dc00e582e47 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md @@ -31,6 +31,6 @@ Depending on the provider type, the additional parameters are: ```bash pg_tde_change_key_provider [-D ] file -pg_tde_change_key_provider [-D ] vault [] +pg_tde_change_key_provider [-D ] vault [] pg_tde_change_key_provider [-D ] kmip [] ``` diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index ea5095fa8acb8..3037d2cc1b3f2 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -63,14 +63,14 @@ Use the following functions to add the Vault provider: ```sql SELECT pg_tde_add_database_key_provider_vault_v2( 'provider-name', - 'secret_token', + 'secret_token_path', 'url','mount', 'ca_path' ); SELECT pg_tde_add_global_key_provider_vault_v2( 'provider-name', - 'secret_token', + 'secret_token_path', 'url','mount', 'ca_path' ); @@ -81,7 +81,7 @@ These functions change the Vault provider: ```sql SELECT pg_tde_change_database_key_provider_vault_v2( 'provider-name', - 'secret_token', + 'secret_token_path', 'url', 'mount', 'ca_path' @@ -89,7 +89,7 @@ SELECT pg_tde_change_database_key_provider_vault_v2( SELECT pg_tde_change_global_key_provider_vault_v2( 'provider-name', - 'secret_token', + 'secret_token_path', 'url', 'mount', 'ca_path' @@ -101,13 +101,11 @@ where: * `provider-name` is the name of the key provider * `url` is the URL of the Vault server * `mount` is the mount point on the Vault server where the key provider should store the keys -* `secret_token` is an access token with read and write access to the above mount point +* `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * **[optional]** `ca_path` is the path of the CA file used for SSL verification All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). -!!! important - Never specify the secret token directly, use a remote parameter instead. #### Adding or modifying KMIP providers diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index bc4df08d6da95..71aaaf075a56d 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -10,7 +10,7 @@ You can configure `pg_tde` to use HashiCorp Vault as a global key provider for m ```sql SELECT pg_tde_add_global_key_provider_vault_v2( 'provider-name', - 'secret_token', + 'secret_token_path', 'url', 'mount', 'ca_path' @@ -20,7 +20,7 @@ SELECT pg_tde_add_global_key_provider_vault_v2( ## Parameter descriptions * `provider-name` is the name to identify this key provider -* `secret_token` is an access token with read and write access to the above mount point +* `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys * [optional] `ca_path` is the path of the CA file used for SSL verification @@ -30,7 +30,7 @@ The following example is for testing purposes only. Use secure tokens and proper ```sql SELECT pg_tde_add_global_key_provider_vault_v2( 'my-vault', - 'hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', + '/path/to/token_file', 'http://vault.vault.svc.cluster.local:8200', 'secret/data', NULL diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 8f7c14f17a421..d6221f66a6b86 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -84,14 +84,14 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token','url','mount','ca_path'); + SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token_path','url','mount','ca_path'); ``` where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys - * `secret_token` is an access token with read and write access to the above mount point + * `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification :material-information: Warning: This example is for testing purposes only: diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index a40831fcf110b..21f37ed73ae85 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -40,7 +40,7 @@ Before turning WAL encryption on, you must follow the steps below to create your === "With HashiCorp Vault" ```sql - SELECT pg_tde_add_global_key_provider_vault_v2('provider-name', 'secret_token', 'url', 'mount', 'ca_path'); + SELECT pg_tde_add_global_key_provider_vault_v2('provider-name', 'secret_token_path', 'url', 'mount', 'ca_path'); ``` where: @@ -48,7 +48,7 @@ Before turning WAL encryption on, you must follow the steps below to create your * `provider-name` is the name you define for the key provider * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys - * `secret_token` is an access token with read and write access to the above mount point + * `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * [optional] `ca_path` is the path of the CA file used for SSL verification === "With keyring file" diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 6c2766f6894e0..56f1a2ddfd1e5 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,6 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -\getenv root_token ROOT_TOKEN -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect','/tmp/vault_test_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -16,7 +15,7 @@ CREATE TABLE test_enc( ) USING tde_heap; ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2','/tmp/vault_test_token','http://127.0.0.1:8200','secret',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -52,6 +51,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', '/tmp/vault_test_token', 'http://127.0.0.1:61', 'secret', NULL); ERROR: HTTP(S) request to keyring provider "will-not-work" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 352ca049f636f..e360cfb72146f 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -31,7 +31,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, - vault_token TEXT, + vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) @@ -42,13 +42,13 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('url' VALUE COALESCE(vault_url, ''), - 'token' VALUE COALESCE(vault_token, ''), + 'tokenPath' VALUE COALESCE(vault_token_path, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, - vault_token JSON, + vault_token_path JSON, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) @@ -59,7 +59,7 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'token' VALUE vault_token, + 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); END; @@ -150,7 +150,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, - vault_token TEXT, + vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) @@ -161,13 +161,13 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, json_object('url' VALUE COALESCE(vault_url, ''), - 'token' VALUE COALESCE(vault_token, ''), + 'tokenPath' VALUE COALESCE(vault_token_path, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, - vault_token JSON, + vault_token_path JSON, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) @@ -178,7 +178,7 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'token' VALUE vault_token, + 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); END; @@ -249,7 +249,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, - vault_token TEXT, + vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) @@ -260,13 +260,13 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('url' VALUE COALESCE(vault_url, ''), - 'token' VALUE COALESCE(vault_token, ''), + 'tokenPath' VALUE COALESCE(vault_token_path, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, - vault_token JSON, + vault_token_path JSON, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) @@ -277,7 +277,7 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'token' VALUE vault_token, + 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); END; @@ -348,7 +348,7 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, - vault_token TEXT, + vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, vault_ca_path TEXT) @@ -359,13 +359,13 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, json_object('url' VALUE COALESCE(vault_url, ''), - 'token' VALUE COALESCE(vault_token, ''), + 'tokenPath' VALUE COALESCE(vault_token_path, ''), 'mountPath' VALUE COALESCE(vault_mount_path, ''), 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, - vault_token JSON, + vault_token_path JSON, vault_url JSON, vault_mount_path JSON, vault_ca_path JSON) @@ -376,7 +376,7 @@ BEGIN ATOMIC -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'token' VALUE vault_token, + 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, 'caPath' VALUE vault_ca_path)); END; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 205aa1118533f..1ffe2a54ccb2e 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -1,8 +1,6 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -\getenv root_token ROOT_TOKEN - -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect','/tmp/vault_test_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); -- FAILS SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); @@ -12,7 +10,7 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2','/tmp/vault_test_token','http://127.0.0.1:8200','secret',NULL); SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); CREATE TABLE test_enc( @@ -32,6 +30,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', '/tmp/vault_test_token', 'http://127.0.0.1:61', 'secret', NULL); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 5004034de7d33..96acd757b56c7 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -62,6 +62,7 @@ static GenericKeyring *load_keyring_provider_options(ProviderType provider_type, static KmipKeyring *load_kmip_keyring_provider_options(char *keyring_options); static VaultV2Keyring *load_vaultV2_keyring_provider_options(char *keyring_options); static int open_keyring_infofile(Oid dbOid, int flags); +static char *get_file_value(const char *path, const char *field_name); #ifdef FRONTEND @@ -870,19 +871,22 @@ load_vaultV2_keyring_provider_options(char *keyring_options) (GenericKeyring *) vaultV2_keyring, keyring_options, strlen(keyring_options)); - if (vaultV2_keyring->vault_token == NULL || vaultV2_keyring->vault_token[0] == '\0' || + if (vaultV2_keyring->vault_token_path == NULL || vaultV2_keyring->vault_token_path[0] == '\0' || vaultV2_keyring->vault_url == NULL || vaultV2_keyring->vault_url[0] == '\0' || vaultV2_keyring->vault_mount_path == NULL || vaultV2_keyring->vault_mount_path[0] == '\0') { ereport(WARNING, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing in the keyring options:%s%s%s", - (vaultV2_keyring->vault_token != NULL && vaultV2_keyring->vault_token[0] != '\0') ? "" : " token", + (vaultV2_keyring->vault_token_path != NULL && vaultV2_keyring->vault_token_path[0] != '\0') ? "" : " tokenPath", (vaultV2_keyring->vault_url != NULL && vaultV2_keyring->vault_url[0] != '\0') ? "" : " url", (vaultV2_keyring->vault_mount_path != NULL && vaultV2_keyring->vault_mount_path[0] != '\0') ? "" : " mountPath")); return NULL; } + /* TODO: the vault_token mem should be protected from paging to the swap */ + vaultV2_keyring->vault_token = get_file_value(vaultV2_keyring->vault_token_path, "vault_token"); + return vaultV2_keyring; } @@ -916,6 +920,33 @@ load_kmip_keyring_provider_options(char *keyring_options) return kmip_keyring; } +#define MAX_FILE_DATA_LENGTH 1024 + +static char * +get_file_value(const char *path, const char *field_name) +{ + int fd = -1; + char *val; + + fd = BasicOpenFile(path, O_RDONLY); + if (fd < 0) + { + elog(ERROR, "failed to open file \"%s\" for \"%s\"", path, field_name); + } + + val = palloc0(MAX_FILE_DATA_LENGTH); + if (pg_pread(fd, val, MAX_FILE_DATA_LENGTH, 0) == -1) + { + close(fd); + elog(ERROR, "failed to read file \"%s\" for \"%s\"", path, field_name); + } + /* remove trailing whitespace */ + val[strcspn(val, " \t\n\r")] = '\0'; + + close(fd); + return val; +} + static void debug_print_kerying(GenericKeyring *keyring) { @@ -928,7 +959,7 @@ debug_print_kerying(GenericKeyring *keyring) elog(DEBUG2, "File Keyring Path: %s", ((FileKeyring *) keyring)->file_name); break; case VAULT_V2_KEY_PROVIDER: - elog(DEBUG2, "Vault Keyring Token: %s", ((VaultV2Keyring *) keyring)->vault_token); + elog(DEBUG2, "Vault Keyring Token Path: %s", ((VaultV2Keyring *) keyring)->vault_token_path); elog(DEBUG2, "Vault Keyring URL: %s", ((VaultV2Keyring *) keyring)->vault_url); elog(DEBUG2, "Vault Keyring Mount Path: %s", ((VaultV2Keyring *) keyring)->vault_mount_path); elog(DEBUG2, "Vault Keyring CA Path: %s", ((VaultV2Keyring *) keyring)->vault_ca_path); diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 38a67ba0c78ea..35eeeae84edaa 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -64,7 +64,7 @@ typedef enum JsonKeyringField /* Settings specific for the individual key provider types. */ JK_FILE_PATH, - JK_VAULT_TOKEN, + JK_VAULT_TOKEN_PATH, JK_VAULT_URL, JK_VAULT_MOUNT_PATH, JK_VAULT_CA_PATH, @@ -93,7 +93,7 @@ static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { */ [JK_FILE_PATH] = "path", - [JK_VAULT_TOKEN] = "token", + [JK_VAULT_TOKEN_PATH] = "tokenPath", [JK_VAULT_URL] = "url", [JK_VAULT_MOUNT_PATH] = "mountPath", [JK_VAULT_CA_PATH] = "caPath", @@ -358,8 +358,8 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) break; case VAULT_V2_KEY_PROVIDER: - if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_TOKEN]) == 0) - parse->top_level_field = JK_VAULT_TOKEN; + if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_TOKEN_PATH]) == 0) + parse->top_level_field = JK_VAULT_TOKEN_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_URL]) == 0) parse->top_level_field = JK_VAULT_URL; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_MOUNT_PATH]) == 0) @@ -498,8 +498,8 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * file->file_name = value; break; - case JK_VAULT_TOKEN: - vault->vault_token = value; + case JK_VAULT_TOKEN_PATH: + vault->vault_token_path = value; break; case JK_VAULT_URL: vault->vault_url = value; diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index ef322709823c5..a137f67f94041 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -74,6 +74,7 @@ typedef struct VaultV2Keyring { GenericKeyring keyring; /* Must be the first field */ char *vault_token; + char *vault_token_path; char *vault_url; char *vault_ca_path; char *vault_mount_path; diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index be3e37c1519d2..341c182e93e51 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -27,7 +27,7 @@ help(void) puts("Depending on the provider type, the complete parameter list is:"); puts(""); puts("pg_tde_change_key_provider [-D ] file "); - puts("pg_tde_change_key_provider [-D ] vault-v2 []"); + puts("pg_tde_change_key_provider [-D ] vault-v2 []"); puts("pg_tde_change_key_provider [-D ] kmip []"); puts(""); printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); @@ -196,7 +196,7 @@ main(int argc, char *argv[]) if (!build_json(json, 4, "url", argv[4 + argstart], - "token", argv[5 + argstart], + "tokenPath", argv[5 + argstart], "mountPath", argv[6 + argstart], "caPath", (argc - argstart > 7 ? argv[7 + argstart] : ""))) { diff --git a/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl b/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl index c197f38f15f03..175df058b17c5 100644 --- a/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl @@ -64,7 +64,7 @@ 'database-provider', 'vault-v2', 'http://vault-server.example:8200/', - 'secret-token', + '/tmp/vault_test_token', 'mount-path', '/tmp/ca_path', ], @@ -85,8 +85,8 @@ 'postgres', q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} )); -is($options->{token}, 'secret-token', - 'token is set correctly for vault-v2 provider'); +is($options->{tokenPath}, '/tmp/vault_test_token', + 'tokenPath is set correctly for vault-v2 provider'); is( $options->{url}, 'http://vault-server.example:8200/', 'url is set correctly for vault-v2 provider'); @@ -147,7 +147,7 @@ 'global-provider', 'vault-v2', 'http://vault-server.example:8200/', - 'secret-token', + '/tmp/vault_test_token', 'mount-path', '/tmp/ca_path', ], From 75aad06e5678d1c3bf905a680e5539f1537b45d7 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Fri, 30 May 2025 15:52:58 +0300 Subject: [PATCH 422/796] Remove support for external fields from key provider options --- ci_scripts/setup-keyring-servers.sh | 8 +- contrib/pg_tde/README.md | 6 +- .../documentation/docs/architecture/index.md | 7 +- .../pg_tde/documentation/docs/functions.md | 4 - .../keyring.md | 2 +- .../docs/how-to/external-parameters.md | 33 -- .../docs/how-to/multi-tenant-setup.md | 1 - contrib/pg_tde/documentation/mkdocs.yml | 1 - contrib/pg_tde/expected/key_provider.out | 52 +--- contrib/pg_tde/expected/vault_v2_test.out | 7 +- contrib/pg_tde/meson.build | 25 +- contrib/pg_tde/pg_tde--1.0-rc.sql | 144 --------- contrib/pg_tde/sql/key_provider.sql | 30 +- contrib/pg_tde/sql/vault_v2_test.sql | 8 +- contrib/pg_tde/src/catalog/tde_keyring.c | 22 +- .../src/catalog/tde_keyring_parse_opts.c | 281 ++---------------- contrib/pg_tde/src/include/pg_tde_fe.h | 2 + ...tensions.pl => 003_multiple_extensions.pl} | 0 contrib/pg_tde/t/003_remote_config.pl | 96 ------ contrib/pg_tde/t/004_file_config.pl | 56 ---- .../t/{007_tde_heap.pl => 004_tde_heap.pl} | 0 ...espace.pl => 005_key_rotate_tablespace.pl} | 0 contrib/pg_tde/t/006_remote_vault_config.pl | 105 ------- ...{009_wal_encrypt.pl => 006_wal_encrypt.pl} | 0 ...provider.pl => 007_change_key_provider.pl} | 0 ...ogged_tables.pl => 008_unlogged_tables.pl} | 0 ...{012_replication.pl => 009_replication.pl} | 0 ...rash_recovery.pl => 010_crash_recovery.pl} | 0 ...ldump_basic.pl => 011_pg_waldump_basic.pl} | 0 ...fullpage.pl => 012_pg_waldump_fullpage.pl} | 0 ...r.pl => 013_pg_tde_change_key_provider.pl} | 8 +- ...nsions.out => 003_multiple_extensions.out} | 0 .../{007_tde_heap.out => 004_tde_heap.out} | 0 ...pace.out => 005_key_rotate_tablespace.out} | 0 ...09_wal_encrypt.out => 006_wal_encrypt.out} | 0 ...ovider.out => 007_change_key_provider.out} | 0 ...ged_tables.out => 008_unlogged_tables.out} | 0 ...12_replication.out => 009_replication.out} | 0 ...sh_recovery.out => 010_crash_recovery.out} | 0 39 files changed, 92 insertions(+), 806 deletions(-) delete mode 100644 contrib/pg_tde/documentation/docs/how-to/external-parameters.md rename contrib/pg_tde/t/{005_multiple_extensions.pl => 003_multiple_extensions.pl} (100%) delete mode 100644 contrib/pg_tde/t/003_remote_config.pl delete mode 100644 contrib/pg_tde/t/004_file_config.pl rename contrib/pg_tde/t/{007_tde_heap.pl => 004_tde_heap.pl} (100%) rename contrib/pg_tde/t/{008_key_rotate_tablespace.pl => 005_key_rotate_tablespace.pl} (100%) delete mode 100644 contrib/pg_tde/t/006_remote_vault_config.pl rename contrib/pg_tde/t/{009_wal_encrypt.pl => 006_wal_encrypt.pl} (100%) rename contrib/pg_tde/t/{010_change_key_provider.pl => 007_change_key_provider.pl} (100%) rename contrib/pg_tde/t/{011_unlogged_tables.pl => 008_unlogged_tables.pl} (100%) rename contrib/pg_tde/t/{012_replication.pl => 009_replication.pl} (100%) rename contrib/pg_tde/t/{013_crash_recovery.pl => 010_crash_recovery.pl} (100%) rename contrib/pg_tde/t/{014_pg_waldump_basic.pl => 011_pg_waldump_basic.pl} (100%) rename contrib/pg_tde/t/{015_pg_waldump_fullpage.pl => 012_pg_waldump_fullpage.pl} (100%) rename contrib/pg_tde/t/{016_pg_tde_change_key_provider.pl => 013_pg_tde_change_key_provider.pl} (98%) rename contrib/pg_tde/t/expected/{005_multiple_extensions.out => 003_multiple_extensions.out} (100%) rename contrib/pg_tde/t/expected/{007_tde_heap.out => 004_tde_heap.out} (100%) rename contrib/pg_tde/t/expected/{008_key_rotate_tablespace.out => 005_key_rotate_tablespace.out} (100%) rename contrib/pg_tde/t/expected/{009_wal_encrypt.out => 006_wal_encrypt.out} (100%) rename contrib/pg_tde/t/expected/{010_change_key_provider.out => 007_change_key_provider.out} (100%) rename contrib/pg_tde/t/expected/{011_unlogged_tables.out => 008_unlogged_tables.out} (100%) rename contrib/pg_tde/t/expected/{012_replication.out => 009_replication.out} (100%) rename contrib/pg_tde/t/expected/{013_crash_recovery.out => 010_crash_recovery.out} (100%) diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index 05821fbd3cd7b..356d98b586ce9 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -20,5 +20,9 @@ pykmip-server -f "$SCRIPT_DIR/../contrib/pg_tde/pykmip-server.conf" -l /tmp/kmip TV=$(mktemp) { exec >$TV; vault server -dev; } & sleep 10 -echo $(cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n) > /tmp/vault_test_token -echo "write vault token to /tmp/vault_test_token" +export ROOT_TOKEN_FILE=$(mktemp) +cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n > $ROOT_TOKEN_FILE +echo "export ROOT_TOKEN_FILE=$ROOT_TOKEN_FILE" +if [ -v GITHUB_ACTIONS ]; then + echo "ROOT_TOKEN_FILE=$ROOT_TOKEN_FILE" >> $GITHUB_ENV +fi diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index ec367e2cdcab3..cdcb0bbe47fbc 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -116,9 +116,9 @@ _See [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-bu -- pg_tde_add_database_key_provider_vault_v2(provider_name, vault_token, vault_url, vault_mount_path, vault_ca_path) SELECT pg_tde_add_database_key_provider_vault_v2( 'vault-provider', - json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/token' ), - json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/url' ), - to_json('secret'::text), NULL); + '/path/to/token_file', + 'https://your.vault.server', + 'secret', NULL); -- For File key provider -- pg_tde_add_database_key_provider_file(provider_name, file_path); diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index e5e1612094610..d2061a8f7058c 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -168,11 +168,10 @@ For such situations, `pg_tde` also provides [command line tools](../command-line ### Sensitive key provider information -Key provider information authentication details is a sensitive information. It is not safe to store it together with the database in the `$PGDATA` directory, or even on the same server. +!!! important -To safeguard key providers' sensitive information, `pg_tde` supports references to external services. Instead of specifying authentication details directly, users specify the reference to the external service where it is stored. `pg_tde` then downloads the provider's authentication details when needed. - -The currently supported external services are HTTP and external file references. + Authentication details for key providers are sensitive and must be protected. + Do not store these credentials in the `$PGDATA` directory alongside the database. Instead, ensure they are stored in a secure location with strict file system permissions to prevent unauthorized access. ## User interface diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 3037d2cc1b3f2..5dae0deaee72c 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -104,7 +104,6 @@ where: * `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * **[optional]** `ca_path` is the path of the CA file used for SSL verification -All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). #### Adding or modifying KMIP providers @@ -166,7 +165,6 @@ where: !!! note The specified access parameters require permission to read and write keys at the server. -All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). ### Adding or modifying local keyfile providers @@ -210,8 +208,6 @@ where: * `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider. * `/path/to/the/key/provider/data.file` is the path to the key provider file. -!!! note - All parameters can be either strings, or JSON objects [referencing remote parameters](how-to/external-parameters.md). ### Delete a provider diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md index f8cb931b81ea8..82829aefd1d12 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md @@ -1,6 +1,6 @@ # Keyring File Configuration -This setup is intended for development and stores the keys unencrypted in the specified data file. See [how to use external reference to parameters](../how-to/external-parameters.md) to add an extra security layer to your setup. +This setup is intended for development and stores the keys unencrypted in the specified data file. !!! note While keyfiles may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. diff --git a/contrib/pg_tde/documentation/docs/how-to/external-parameters.md b/contrib/pg_tde/documentation/docs/how-to/external-parameters.md deleted file mode 100644 index 6177a91223532..0000000000000 --- a/contrib/pg_tde/documentation/docs/how-to/external-parameters.md +++ /dev/null @@ -1,33 +0,0 @@ -# Use External Reference to Parameters - -To allow storing secrets or any other parameters in a more secure, external location, `pg_tde` -allows users to specify an external reference instead of hardcoded parameters. - -In the Alpha1 version, `pg_tde` supports the following external storage methods: - -* `file`, which just stores the data in a simple file specified by a `path`. The file should be -readable to the postgres process. -* `remote`, which uses a HTTP request to retrieve the parameter from the specified `url`. - -## Examples - -To use the file provider with a file location specified by the `remote` method, -use the following command: - -```sql -SELECT pg_tde_add_database_key_provider_file( - 'file-provider', - json_object( 'type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello' ) - ); -``` - -Or to use the `file` method, use the following command: - -```sql -SELECT pg_tde_add_database_key_provider_file( - 'file-provider', - json_object( 'type' VALUE 'remote', 'path' VALUE '/tmp/datafile-location' ) - );" -``` - -Any parameter specified to the `add_key_provider` function can be a `json_object` instead of the string, similar to the above examples. diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index d6221f66a6b86..b128faef55871 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -135,4 +135,3 @@ You must do these steps for every database where you have created the extension. !!! note The key is auto-generated. - :material-information: Info: The key provider configuration is stored in the database catalog in an unencrypted table. See [how to use external reference to parameters](external-parameters.md) to add an extra security layer to your setup. diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index f0a31fda328e8..b087c856aca9d 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -188,7 +188,6 @@ nav: - "pg_checksums": command-line-tools/pg-tde-checksums.md - "Uninstall pg_tde": how-to/uninstall.md - "Configure Multi-tenancy": how-to/multi-tenant-setup.md - - "Use Reference to External Parameters": how-to/external-parameters.md - "Decrypt an Encrypted Table": how-to/decrypt.md - faq.md - "Release Notes": diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 587c0665e7680..47ba57bdca8de 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -3,7 +3,7 @@ SELECT * FROM pg_tde_key_info(); ERROR: Principal key does not exists for the database HINT: Use set_key interface to set the principal key SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: unexpected field "foo" for external value "path" +ERROR: key provider value cannot be an object SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- @@ -49,7 +49,7 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); (2 rows) SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); -ERROR: unexpected field "foo" for external value "path" +ERROR: key provider value cannot be an object SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options ----+----------------+---------------+-------------------------------------------- @@ -177,33 +177,13 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); ERROR: key provider options must be an object SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); ERROR: key provider options must be an object --- Creating key providers fails if an external value object doesn't have all required keys +-- Creating key providers fails if vaules are not scalar SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); -ERROR: external value must contain "type" in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": null}}'); -ERROR: external value must contain "type" in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); -ERROR: external remote value must contain "url" in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); -ERROR: external remote value must contain "url" in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); -ERROR: external file value must contain "path" in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); -ERROR: external file value must contain "path" in field "path" --- Creating key providers fails if values are array instead of scalar +ERROR: key provider value cannot be an object SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); ERROR: unexpected array in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": ["array"]}}'); -ERROR: unexpected array in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); -ERROR: unexpected array in field "path" --- Creating key providers fails if values are boolean SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": true}'); ERROR: unexpected boolean in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": true}}'); -ERROR: unexpected boolean in field "path" -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); -ERROR: unexpected boolean in field "path" -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); ERROR: provider type cannot be null @@ -231,33 +211,13 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); ERROR: key provider options must be an object SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); ERROR: key provider options must be an object --- Modifying key providers fails if an external value object doesn't have all required keys +-- Modifying key providers fails if vaules are not scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); -ERROR: external value must contain "type" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": null}}'); -ERROR: external value must contain "type" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); -ERROR: external remote value must contain "url" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote", "url": null}}'); -ERROR: external remote value must contain "url" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); -ERROR: external file value must contain "path" in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": null}}'); -ERROR: external file value must contain "path" in field "path" --- Modifying key providers fails if values are array instead of scalar +ERROR: key provider value cannot be an object SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); ERROR: unexpected array in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": ["array"]}}'); -ERROR: unexpected array in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); -ERROR: unexpected array in field "path" --- Modifying key providers fails if values are boolean SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": true}'); ERROR: unexpected boolean in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}'); -ERROR: unexpected boolean in field "path" -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); -ERROR: unexpected boolean in field "path" -- Modifying key providers fails if new settings can't fetch existing server key SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); pg_tde_add_global_key_provider_file diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 56f1a2ddfd1e5..7042a6fa43642 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,5 +1,6 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect','/tmp/vault_test_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +\getenv root_token_file ROOT_TOKEN_FILE +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -15,7 +16,7 @@ CREATE TABLE test_enc( ) USING tde_heap; ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2','/tmp/vault_test_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','http://127.0.0.1:8200','secret',NULL); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -51,6 +52,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', '/tmp/vault_test_token', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); ERROR: HTTP(S) request to keyring provider "will-not-work" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index f5709c08706dd..7cfa87c353036 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -105,20 +105,17 @@ sql_tests = [ tap_tests = [ 't/001_basic.pl', 't/002_rotate_key.pl', - 't/003_remote_config.pl', - 't/004_file_config.pl', - 't/005_multiple_extensions.pl', - 't/006_remote_vault_config.pl', - 't/007_tde_heap.pl', - 't/008_key_rotate_tablespace.pl', - 't/009_wal_encrypt.pl', - 't/010_change_key_provider.pl', - 't/011_unlogged_tables.pl', - 't/012_replication.pl', - 't/013_crash_recovery.pl', - 't/014_pg_waldump_basic.pl', - 't/015_pg_waldump_fullpage.pl', - 't/016_pg_tde_change_key_provider.pl', + 't/003_multiple_extensions.pl', + 't/004_tde_heap.pl', + 't/005_key_rotate_tablespace.pl', + 't/006_wal_encrypt.pl', + 't/007_change_key_provider.pl', + 't/008_unlogged_tables.pl', + 't/009_replication.pl', + 't/010_crash_recovery.pl', + 't/011_pg_waldump_basic.pl', + 't/012_pg_waldump_fullpage.pl', + 't/013_pg_tde_change_key_provider.pl', ] tests += { diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index e360cfb72146f..909543f5fe2eb 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -47,23 +47,6 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, - vault_token_path JSON, - vault_url JSON, - vault_mount_path JSON, - vault_ca_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, - json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, - 'mountPath' VALUE vault_mount_path, - 'caPath' VALUE vault_ca_path)); -END; - CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, @@ -83,25 +66,6 @@ BEGIN ATOMIC 'keyPath' VALUE COALESCE(kmip_key_path, ''))); END; -CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, - kmip_host JSON, - kmip_port JSON, - kmip_ca_path JSON, - kmip_cert_path JSON, - kmip_key_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. - SELECT pg_tde_add_database_key_provider('kmip', provider_name, - json_object('host' VALUE kmip_host, - 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, - 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); -END; - CREATE FUNCTION pg_tde_list_all_database_key_providers (OUT id INT, OUT provider_name TEXT, @@ -166,23 +130,6 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, - vault_token_path JSON, - vault_url JSON, - vault_mount_path JSON, - vault_ca_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, - 'mountPath' VALUE vault_mount_path, - 'caPath' VALUE vault_ca_path)); -END; - CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, @@ -202,25 +149,6 @@ BEGIN ATOMIC 'keyPath' VALUE COALESCE(kmip_key_path, ''))); END; -CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, - kmip_host JSON, - kmip_port JSON, - kmip_ca_path JSON, - kmip_cert_path JSON, - kmip_key_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. - SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('host' VALUE kmip_host, - 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, - 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); -END; - -- Key Provider Management CREATE FUNCTION pg_tde_change_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) RETURNS VOID @@ -265,23 +193,6 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, - vault_token_path JSON, - vault_url JSON, - vault_mount_path JSON, - vault_ca_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, - json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, - 'mountPath' VALUE vault_mount_path, - 'caPath' VALUE vault_ca_path)); -END; - CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, @@ -301,25 +212,6 @@ BEGIN ATOMIC 'keyPath' VALUE COALESCE(kmip_key_path, ''))); END; -CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, - kmip_host JSON, - kmip_port JSON, - kmip_ca_path JSON, - kmip_cert_path JSON, - kmip_key_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. - SELECT pg_tde_change_database_key_provider('kmip', provider_name, - json_object('host' VALUE kmip_host, - 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, - 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); -END; - -- Global Tablespace Key Provider Management CREATE FUNCTION pg_tde_change_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) RETURNS VOID @@ -364,23 +256,6 @@ BEGIN ATOMIC 'caPath' VALUE COALESCE(vault_ca_path, ''))); END; -CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, - vault_token_path JSON, - vault_url JSON, - vault_mount_path JSON, - vault_ca_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. - SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, - json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, - 'mountPath' VALUE vault_mount_path, - 'caPath' VALUE vault_ca_path)); -END; - CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, @@ -400,25 +275,6 @@ BEGIN ATOMIC 'keyPath' VALUE COALESCE(kmip_key_path, ''))); END; -CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, - kmip_host JSON, - kmip_port JSON, - kmip_ca_path JSON, - kmip_cert_path JSON, - kmip_key_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. - SELECT pg_tde_change_global_key_provider('kmip', provider_name, - json_object('host' VALUE kmip_host, - 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, - 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); -END; - CREATE FUNCTION pg_tde_is_encrypted(relation REGCLASS) RETURNS BOOLEAN STRICT diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index ee844af0fada6..819da4d61cb6b 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -73,23 +73,10 @@ SELECT pg_tde_add_database_key_provider('file', 'provider', '["array"]'); SELECT pg_tde_add_database_key_provider('file', 'provider', 'true'); SELECT pg_tde_add_database_key_provider('file', 'provider', 'null'); --- Creating key providers fails if an external value object doesn't have all required keys +-- Creating key providers fails if vaules are not scalar SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": null}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote"}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "remote", "url": null}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file"}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": null}}'); - --- Creating key providers fails if values are array instead of scalar SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": ["array"]}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": ["array"]}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": ["array"]}}'); - --- Creating key providers fails if values are boolean SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": true}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": true}}'); -SELECT pg_tde_add_database_key_provider('file', 'provider', '{"path": {"type": "file", "path": true}}'); -- Modifying key providers fails if any required parameter is NULL SELECT pg_tde_change_database_key_provider(NULL, 'file-keyring', '{}'); @@ -109,23 +96,10 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '["array"]') SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'true'); SELECT pg_tde_change_database_key_provider('file', 'file-provider', 'null'); --- Modifying key providers fails if an external value object doesn't have all required keys +-- Modifying key providers fails if vaules are not scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": null}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote"}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "remote", "url": null}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file"}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": null}}'); - --- Modifying key providers fails if values are array instead of scalar SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": ["array"]}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": ["array"]}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": ["array"]}}'); - --- Modifying key providers fails if values are boolean SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": true}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}'); -SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}'); -- Modifying key providers fails if new settings can't fetch existing server key SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 1ffe2a54ccb2e..d0cce4719fccd 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -1,6 +1,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect','/tmp/vault_test_token','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +\getenv root_token_file ROOT_TOKEN_FILE + +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); -- FAILS SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); @@ -10,7 +12,7 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2','/tmp/vault_test_token','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','http://127.0.0.1:8200','secret',NULL); SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); CREATE TABLE test_enc( @@ -30,6 +32,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', '/tmp/vault_test_token', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 96acd757b56c7..b4043e476720d 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -23,6 +23,7 @@ #include "utils/fmgroids.h" #include "common/pg_tde_utils.h" #include "miscadmin.h" +#include "storage/fd.h" #include "unistd.h" #include "utils/builtins.h" #include "pg_tde.h" @@ -925,25 +926,28 @@ load_kmip_keyring_provider_options(char *keyring_options) static char * get_file_value(const char *path, const char *field_name) { - int fd = -1; + FILE *fd; char *val; - fd = BasicOpenFile(path, O_RDONLY); - if (fd < 0) + fd = AllocateFile(path, "r"); + if (fd == NULL) { - elog(ERROR, "failed to open file \"%s\" for \"%s\"", path, field_name); + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for \"%s\": %m", path, field_name))); } - val = palloc0(MAX_FILE_DATA_LENGTH); - if (pg_pread(fd, val, MAX_FILE_DATA_LENGTH, 0) == -1) + val = palloc(MAX_FILE_DATA_LENGTH); + if (fgets(val, MAX_FILE_DATA_LENGTH, fd) == NULL && ferror(fd)) { - close(fd); - elog(ERROR, "failed to read file \"%s\" for \"%s\"", path, field_name); + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\" for \"%s\": %m", path, field_name))); } /* remove trailing whitespace */ val[strcspn(val, " \t\n\r")] = '\0'; - close(fd); + FreeFile(fd); return val; } diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 35eeeae84edaa..7414da764f27d 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -3,17 +3,7 @@ * tde_keyring_parse_opts.c * Parser routines for the keyring JSON options * - * Each value in the JSON document can be either scalar (string) - a value itself - * or a reference to the external object that contains the value. - * - * Examples: - * {"path" : "/tmp/keyring_data_file"} - * {"path" : {"type" : "file", "path" : "/tmp/datafile-location"}} - * in the latter one, /tmp/datafile-location contains not keyring data but the - * location of such. - * - * A field type can be "file", in this case, we expect "path" field. Or "remote", - * when "url" field is expected. + * We expect one-dimentional JSON object with scalar fields * * IDENTIFICATION * contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -22,45 +12,29 @@ */ #include "postgres.h" -#include "common/file_perm.h" #include "common/jsonapi.h" #include "mb/pg_wchar.h" -#include "storage/fd.h" #include "utils/jsonfuncs.h" #include "catalog/tde_keyring.h" -#include "keyring/keyring_curl.h" #ifdef FRONTEND #include "pg_tde_fe.h" #endif -#include - -#define MAX_CONFIG_FILE_DATA_LENGTH 1024 - /* * JSON parser state */ typedef enum JsonKeyringSemState { JK_EXPECT_TOP_LEVEL_OBJECT, - JK_EXPECT_TOP_FIELD, - JK_EXPECT_EXTERN_VAL, + JK_EXPECT_FIELD, } JsonKeyringSemState; -#define KEYRING_REMOTE_FIELD_TYPE "remote" -#define KEYRING_FILE_FIELD_TYPE "file" - typedef enum JsonKeyringField { JK_FIELD_UNKNOWN, - /* These are for the objects that can point to a file or a remote url. */ - JK_FIELD_TYPE, - JK_FIELD_URL, - JK_FIELD_PATH, - /* Settings specific for the individual key provider types. */ JK_FILE_PATH, @@ -82,10 +56,6 @@ typedef enum JsonKeyringField static const char *JK_FIELD_NAMES[JK_FIELDS_TOTAL] = { [JK_FIELD_UNKNOWN] = "unknownField", - [JK_FIELD_TYPE] = "type", - [JK_FIELD_URL] = "url", - [JK_FIELD_PATH] = "path", - /* * These values should match pg_tde_add_database_key_provider_vault_v2, * pg_tde_add_database_key_provider_file and @@ -112,33 +82,17 @@ typedef struct JsonKeyringState /* Caller's options to be set from JSON values. */ GenericKeyring *provider_opts; - /* The current field in the top level object */ - JsonKeyringField top_level_field; - - /* Current field in any external field object, if any. */ - JsonKeyringField extern_field; + JsonKeyringField current_field; JsonKeyringSemState state; - - /* - * The rest of the scalar fields might be in the JSON document but has no - * direct value for the caller. Although we need them for the values - * extraction or state tracking. - */ - char *field_type; - char *extern_url; - char *extern_path; } JsonKeyringState; static JsonParseErrorType json_kring_scalar(void *state, char *token, JsonTokenType tokentype); static JsonParseErrorType json_kring_array_start(void *state); static JsonParseErrorType json_kring_object_field_start(void *state, char *fname, bool isnull); static JsonParseErrorType json_kring_object_start(void *state); -static JsonParseErrorType json_kring_object_end(void *state); static void json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char *value); -static char *get_remote_kring_value(const char *url, const char *field_name); -static char *get_file_kring_value(const char *path, const char *field_name); /* * Parses json input for the given provider type and sets the provided options. @@ -163,7 +117,7 @@ ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, ch */ sem.semstate = &parse; sem.object_start = json_kring_object_start; - sem.object_end = json_kring_object_end; + sem.object_end = NULL; sem.array_start = json_kring_array_start; sem.array_end = NULL; sem.object_field_start = json_kring_object_field_start; @@ -207,11 +161,10 @@ json_kring_array_start(void *state) errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key provider options must be an object")); break; - case JK_EXPECT_TOP_FIELD: - case JK_EXPECT_EXTERN_VAL: + case JK_EXPECT_FIELD: ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unexpected array in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + errmsg("unexpected array in field \"%s\"", JK_FIELD_NAMES[parse->current_field])); break; } @@ -222,11 +175,6 @@ json_kring_array_start(void *state) /* * Invoked at the start of each object in the JSON document. - * - * In the top level object, we expect either scalar (string) values or objects - * referencing the external value of the field. If we are already parsing top - * level fields, we expect an "external field object" e.g. ({"type" : "remote", - * "url" : "http://localhost:8888/hello"}) */ static JsonParseErrorType json_kring_object_start(void *state) @@ -236,100 +184,21 @@ json_kring_object_start(void *state) switch (parse->state) { case JK_EXPECT_TOP_LEVEL_OBJECT: - parse->state = JK_EXPECT_TOP_FIELD; - break; - case JK_EXPECT_TOP_FIELD: - parse->state = JK_EXPECT_EXTERN_VAL; + parse->state = JK_EXPECT_FIELD; break; - case JK_EXPECT_EXTERN_VAL: - Assert(false); - elog(ERROR, "invalid semantic state"); + case JK_EXPECT_FIELD: + elog(ERROR, "key provider value cannot be an object"); break; } return JSON_SUCCESS; } -/* - * Invoked at the end of each object in the JSON document. - * - * If we're done parsing an external field object we fetch the value from the - * source and assign it to the top level object field. - */ -static JsonParseErrorType -json_kring_object_end(void *state) -{ - JsonKeyringState *parse = state; - - /* - * we're done with the nested object and if it's an external field, the - * value should be extracted and assigned to the parent "field". for - * example if : "field" : {"type" : "remote", "url" : - * "http://localhost:8888/hello"} or "field" : {"type" : "file", "path" : - * "/tmp/datafile-location"} the "field"'s value should be the content of - * "path" or "url" respectively - */ - switch (parse->state) - { - case JK_EXPECT_TOP_LEVEL_OBJECT: - Assert(false); - elog(ERROR, "invalid semantic state"); - break; - case JK_EXPECT_TOP_FIELD: - /* We're done parsing the top level object */ - break; - case JK_EXPECT_EXTERN_VAL: - { - char *value = NULL; - - if (!parse->field_type) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("external value must contain \"type\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); - - if (strcmp(parse->field_type, KEYRING_REMOTE_FIELD_TYPE) == 0) - { - if (!parse->extern_url) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("external remote value must contain \"url\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); - - value = get_remote_kring_value(parse->extern_url, JK_FIELD_NAMES[parse->top_level_field]); - pfree(parse->extern_url); - parse->extern_url = NULL; - } - if (strcmp(parse->field_type, KEYRING_FILE_FIELD_TYPE) == 0) - { - if (!parse->extern_path) - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("external file value must contain \"path\" in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); - - value = get_file_kring_value(parse->extern_path, JK_FIELD_NAMES[parse->top_level_field]); - pfree(parse->extern_path); - parse->extern_path = NULL; - } - - pfree(parse->field_type); - parse->field_type = NULL; - - Assert(value != NULL); - - json_kring_assign_scalar(parse, parse->top_level_field, value); - parse->state = JK_EXPECT_TOP_FIELD; - break; - } - } - - return JSON_SUCCESS; -} - /* * Invoked at the start of each object field in the JSON document. * - * Based on the given field name and the semantic state (we expect a top-level - * field or an external object) we set the state so that when we get the value, - * we know what is it and where to assign it. + * Based on the given field name and the semantic state we set the state so + * that when we get the value, we know what is it and where to assign it. */ static JsonParseErrorType json_kring_object_field_start(void *state, char *fname, bool isnull) @@ -342,15 +211,15 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) Assert(false); elog(ERROR, "invalid semantic state"); break; - case JK_EXPECT_TOP_FIELD: + case JK_EXPECT_FIELD: switch (parse->provider_type) { case FILE_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_FILE_PATH]) == 0) - parse->top_level_field = JK_FILE_PATH; + parse->current_field = JK_FILE_PATH; else { - parse->top_level_field = JK_FIELD_UNKNOWN; + parse->current_field = JK_FIELD_UNKNOWN; ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unexpected field \"%s\" for file provider", fname)); @@ -359,16 +228,16 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) case VAULT_V2_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_TOKEN_PATH]) == 0) - parse->top_level_field = JK_VAULT_TOKEN_PATH; + parse->current_field = JK_VAULT_TOKEN_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_URL]) == 0) - parse->top_level_field = JK_VAULT_URL; + parse->current_field = JK_VAULT_URL; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_MOUNT_PATH]) == 0) - parse->top_level_field = JK_VAULT_MOUNT_PATH; + parse->current_field = JK_VAULT_MOUNT_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_VAULT_CA_PATH]) == 0) - parse->top_level_field = JK_VAULT_CA_PATH; + parse->current_field = JK_VAULT_CA_PATH; else { - parse->top_level_field = JK_FIELD_UNKNOWN; + parse->current_field = JK_FIELD_UNKNOWN; ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unexpected field \"%s\" for vault-v2 provider", fname)); @@ -377,18 +246,18 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) case KMIP_KEY_PROVIDER: if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_HOST]) == 0) - parse->top_level_field = JK_KMIP_HOST; + parse->current_field = JK_KMIP_HOST; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_PORT]) == 0) - parse->top_level_field = JK_KMIP_PORT; + parse->current_field = JK_KMIP_PORT; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_CA_PATH]) == 0) - parse->top_level_field = JK_KMIP_CA_PATH; + parse->current_field = JK_KMIP_CA_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_CERT_PATH]) == 0) - parse->top_level_field = JK_KMIP_CERT_PATH; + parse->current_field = JK_KMIP_CERT_PATH; else if (strcmp(fname, JK_FIELD_NAMES[JK_KMIP_KEY_PATH]) == 0) - parse->top_level_field = JK_KMIP_KEY_PATH; + parse->current_field = JK_KMIP_KEY_PATH; else { - parse->top_level_field = JK_FIELD_UNKNOWN; + parse->current_field = JK_FIELD_UNKNOWN; ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unexpected field \"%s\" for vault-v2 provider", fname)); @@ -399,22 +268,6 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) return JSON_INVALID_TOKEN; } break; - - case JK_EXPECT_EXTERN_VAL: - if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_TYPE]) == 0) - parse->extern_field = JK_FIELD_TYPE; - else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_URL]) == 0) - parse->extern_field = JK_FIELD_URL; - else if (strcmp(fname, JK_FIELD_NAMES[JK_FIELD_PATH]) == 0) - parse->extern_field = JK_FIELD_PATH; - else - { - parse->extern_field = JK_FIELD_UNKNOWN; - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unexpected field \"%s\" for external value \"%s\"", fname, JK_FIELD_NAMES[parse->top_level_field])); - } - break; } return JSON_SUCCESS; @@ -430,22 +283,13 @@ static JsonParseErrorType json_kring_scalar(void *state, char *token, JsonTokenType tokentype) { JsonKeyringState *parse = state; - JsonKeyringField *field = NULL; char *value; - switch (parse->state) + if (parse->state == JK_EXPECT_TOP_LEVEL_OBJECT) { - case JK_EXPECT_TOP_LEVEL_OBJECT: - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("key provider options must be an object")); - break; - case JK_EXPECT_TOP_FIELD: - field = &parse->top_level_field; - break; - case JK_EXPECT_EXTERN_VAL: - field = &parse->extern_field; - break; + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key provider options must be an object")); } switch (tokentype) @@ -458,7 +302,7 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) case JSON_TOKEN_FALSE: ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unexpected boolean in field \"%s\"", JK_FIELD_NAMES[parse->top_level_field])); + errmsg("unexpected boolean in field \"%s\"", JK_FIELD_NAMES[parse->current_field])); break; case JSON_TOKEN_NULL: value = NULL; @@ -470,7 +314,7 @@ json_kring_scalar(void *state, char *token, JsonTokenType tokentype) break; } - json_kring_assign_scalar(parse, *field, value); + json_kring_assign_scalar(parse, parse->current_field, value); return JSON_SUCCESS; } @@ -484,16 +328,6 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * switch (field) { - case JK_FIELD_TYPE: - parse->field_type = value; - break; - case JK_FIELD_URL: - parse->extern_url = value; - break; - case JK_FIELD_PATH: - parse->extern_path = value; - break; - case JK_FILE_PATH: file->file_name = value; break; @@ -532,56 +366,3 @@ json_kring_assign_scalar(JsonKeyringState *parse, JsonKeyringField field, char * elog(ERROR, "json keyring: unexpected scalar field %d", field); } } - -static char * -get_remote_kring_value(const char *url, const char *field_name) -{ - long httpCode; - CurlString outStr; - - outStr.ptr = palloc0(1); - outStr.len = 0; - - if (!curlSetupSession(url, NULL, &outStr)) - { - elog(ERROR, "CURL error for remote object \"%s\"", field_name); - } - if (curl_easy_perform(keyringCurl) != CURLE_OK) - { - elog(ERROR, "HTTP request error for remote object \"%s\"", field_name); - } - if (curl_easy_getinfo(keyringCurl, CURLINFO_RESPONSE_CODE, &httpCode) != CURLE_OK) - { - elog(ERROR, "HTTP error for remote object \"%s\", HTTP code \"%li\"", field_name, httpCode); - } - - /* remove trailing whitespace */ - outStr.ptr[strcspn(outStr.ptr, " \t\n\r")] = '\0'; - - return outStr.ptr; -} - -static char * -get_file_kring_value(const char *path, const char *field_name) -{ - int fd = -1; - char *val; - - fd = BasicOpenFile(path, O_RDONLY); - if (fd < 0) - { - elog(ERROR, "failed to open file \"%s\" for \"%s\"", path, field_name); - } - - val = palloc0(MAX_CONFIG_FILE_DATA_LENGTH); - if (pg_pread(fd, val, MAX_CONFIG_FILE_DATA_LENGTH, 0) == -1) - { - close(fd); - elog(ERROR, "failed to read file \"%s\" for \"%s\"", path, field_name); - } - /* remove trailing whitespace */ - val[strcspn(val, " \t\n\r")] = '\0'; - - close(fd); - return val; -} diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index da5631ac75159..53269ea9123be 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -86,6 +86,8 @@ static int tde_fe_error_level = 0; #define tde_lwlock_enc_keys() NULL #define BasicOpenFile(fileName, fileFlags) open(fileName, fileFlags, PG_FILE_MODE_OWNER) +#define AllocateFile(name, mode) fopen(name, mode) +#define FreeFile(file) fclose(file) #define pg_fsync(fd) fsync(fd) #endif /* FRONTEND */ diff --git a/contrib/pg_tde/t/005_multiple_extensions.pl b/contrib/pg_tde/t/003_multiple_extensions.pl similarity index 100% rename from contrib/pg_tde/t/005_multiple_extensions.pl rename to contrib/pg_tde/t/003_multiple_extensions.pl diff --git a/contrib/pg_tde/t/003_remote_config.pl b/contrib/pg_tde/t/003_remote_config.pl deleted file mode 100644 index d3fbfa677ca95..0000000000000 --- a/contrib/pg_tde/t/003_remote_config.pl +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use File::Basename; -use Test::More; -use lib 't'; -use pgtde; - -{ - - package MyWebServer; - - use HTTP::Server::Simple::CGI; - use base qw(HTTP::Server::Simple::CGI); - - my %dispatch = ('/hello' => \&resp_hello,); - - sub handle_request - { - my $self = shift; - my $cgi = shift; - - my $path = $cgi->path_info(); - my $handler = $dispatch{$path}; - - if (ref($handler) eq "CODE") - { - print "HTTP/1.0 200 OK\r\n"; - $handler->($cgi); - - } - else - { - print "HTTP/1.0 404 Not found\r\n"; - print $cgi->header, - $cgi->start_html('Not found'), - $cgi->h1('Not found'), - $cgi->end_html; - } - } - - sub resp_hello - { - my $cgi = shift; - print $cgi->header, "/tmp/http_datafile\r\n"; - } - -} - -my $pid = MyWebServer->new(8888)->background(); -END { kill('TERM', $pid); } - -PGTDE::setup_files_dir(basename($0)); - -my $node = PostgreSQL::Test::Cluster->new('main'); -$node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); -$node->start; - -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); - -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello'));" -); -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');" -); - -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5), (6);'); - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); - -PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); - -PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); - -$node->stop; - -# Compare the expected and out file -my $compare = PGTDE->compare_results(); - -is($compare, 0, - "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." -); - -done_testing(); diff --git a/contrib/pg_tde/t/004_file_config.pl b/contrib/pg_tde/t/004_file_config.pl deleted file mode 100644 index 82a0779e6d196..0000000000000 --- a/contrib/pg_tde/t/004_file_config.pl +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use File::Basename; -use Test::More; -use lib 't'; -use pgtde; - -PGTDE::setup_files_dir(basename($0)); - -open my $conf2, '>>', "/tmp/datafile-location"; -print $conf2 "/tmp/keyring_data_file\n"; -close $conf2; - -my $node = PostgreSQL::Test::Cluster->new('main'); -$node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); -$node->start; - -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); - -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location'));" -); -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');" -); - -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc1 (k) VALUES (5), (6);'); - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id;'); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc1 ORDER BY id;'); - -PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc1;'); - -PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); - -$node->stop; - -# Compare the expected and out file -my $compare = PGTDE->compare_results(); - -is($compare, 0, - "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." -); - -done_testing(); diff --git a/contrib/pg_tde/t/007_tde_heap.pl b/contrib/pg_tde/t/004_tde_heap.pl similarity index 100% rename from contrib/pg_tde/t/007_tde_heap.pl rename to contrib/pg_tde/t/004_tde_heap.pl diff --git a/contrib/pg_tde/t/008_key_rotate_tablespace.pl b/contrib/pg_tde/t/005_key_rotate_tablespace.pl similarity index 100% rename from contrib/pg_tde/t/008_key_rotate_tablespace.pl rename to contrib/pg_tde/t/005_key_rotate_tablespace.pl diff --git a/contrib/pg_tde/t/006_remote_vault_config.pl b/contrib/pg_tde/t/006_remote_vault_config.pl deleted file mode 100644 index c84029637e245..0000000000000 --- a/contrib/pg_tde/t/006_remote_vault_config.pl +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use Env; -use File::Basename; -use Test::More; -use lib 't'; -use pgtde; - -{ - - package MyWebServer; - - use HTTP::Server::Simple::CGI; - use base qw(HTTP::Server::Simple::CGI); - - my %dispatch = ( - '/token' => \&resp_token, - '/url' => \&resp_url,); - - sub handle_request - { - my $self = shift; - my $cgi = shift; - - my $path = $cgi->path_info(); - my $handler = $dispatch{$path}; - - if (ref($handler) eq "CODE") - { - print "HTTP/1.0 200 OK\r\n"; - $handler->($cgi); - - } - else - { - print "HTTP/1.0 404 Not found\r\n"; - print $cgi->header, - $cgi->start_html('Not found'), - $cgi->h1('Not found'), - $cgi->end_html; - } - } - - sub resp_token - { - my $cgi = shift; - print $cgi->header, "$ENV{'ROOT_TOKEN'}\r\n"; - } - - sub resp_url - { - my $cgi = shift; - print $cgi->header, "http://127.0.0.1:8200\r\n"; - } - -} - -my $pid = MyWebServer->new(8889)->background(); -END { kill('TERM', $pid); } - -PGTDE::setup_files_dir(basename($0)); - -my $node = PostgreSQL::Test::Cluster->new('main'); -$node->init; -$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); -$node->start; - -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); - -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token'), json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url'), to_json('secret'::text), NULL);" -); -PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'vault-provider');" -); - -PGTDE::psql($node, 'postgres', - 'CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap;' -); - -PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc2 (k) VALUES (5), (6);'); - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); - -PGTDE::append_to_result_file("-- server restart"); -$node->restart; - -PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc2 ORDER BY id;'); - -PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc2;'); - -PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde;'); - -$node->stop; - -# Compare the expected and out file -my $compare = PGTDE->compare_results(); - -is($compare, 0, - "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." -); - -done_testing(); diff --git a/contrib/pg_tde/t/009_wal_encrypt.pl b/contrib/pg_tde/t/006_wal_encrypt.pl similarity index 100% rename from contrib/pg_tde/t/009_wal_encrypt.pl rename to contrib/pg_tde/t/006_wal_encrypt.pl diff --git a/contrib/pg_tde/t/010_change_key_provider.pl b/contrib/pg_tde/t/007_change_key_provider.pl similarity index 100% rename from contrib/pg_tde/t/010_change_key_provider.pl rename to contrib/pg_tde/t/007_change_key_provider.pl diff --git a/contrib/pg_tde/t/011_unlogged_tables.pl b/contrib/pg_tde/t/008_unlogged_tables.pl similarity index 100% rename from contrib/pg_tde/t/011_unlogged_tables.pl rename to contrib/pg_tde/t/008_unlogged_tables.pl diff --git a/contrib/pg_tde/t/012_replication.pl b/contrib/pg_tde/t/009_replication.pl similarity index 100% rename from contrib/pg_tde/t/012_replication.pl rename to contrib/pg_tde/t/009_replication.pl diff --git a/contrib/pg_tde/t/013_crash_recovery.pl b/contrib/pg_tde/t/010_crash_recovery.pl similarity index 100% rename from contrib/pg_tde/t/013_crash_recovery.pl rename to contrib/pg_tde/t/010_crash_recovery.pl diff --git a/contrib/pg_tde/t/014_pg_waldump_basic.pl b/contrib/pg_tde/t/011_pg_waldump_basic.pl similarity index 100% rename from contrib/pg_tde/t/014_pg_waldump_basic.pl rename to contrib/pg_tde/t/011_pg_waldump_basic.pl diff --git a/contrib/pg_tde/t/015_pg_waldump_fullpage.pl b/contrib/pg_tde/t/012_pg_waldump_fullpage.pl similarity index 100% rename from contrib/pg_tde/t/015_pg_waldump_fullpage.pl rename to contrib/pg_tde/t/012_pg_waldump_fullpage.pl diff --git a/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl b/contrib/pg_tde/t/013_pg_tde_change_key_provider.pl similarity index 98% rename from contrib/pg_tde/t/016_pg_tde_change_key_provider.pl rename to contrib/pg_tde/t/013_pg_tde_change_key_provider.pl index 175df058b17c5..284611d178498 100644 --- a/contrib/pg_tde/t/016_pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/013_pg_tde_change_key_provider.pl @@ -22,6 +22,8 @@ q{SELECT oid FROM pg_catalog.pg_database WHERE datname = 'postgres'}); my $options; +my $token_file = $ENV{ROOT_TOKEN_FILE}; + $node->stop; command_like( @@ -64,7 +66,7 @@ 'database-provider', 'vault-v2', 'http://vault-server.example:8200/', - '/tmp/vault_test_token', + $token_file, 'mount-path', '/tmp/ca_path', ], @@ -85,7 +87,7 @@ 'postgres', q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} )); -is($options->{tokenPath}, '/tmp/vault_test_token', +is($options->{tokenPath}, $token_file, 'tokenPath is set correctly for vault-v2 provider'); is( $options->{url}, 'http://vault-server.example:8200/', @@ -147,7 +149,7 @@ 'global-provider', 'vault-v2', 'http://vault-server.example:8200/', - '/tmp/vault_test_token', + $token_file, 'mount-path', '/tmp/ca_path', ], diff --git a/contrib/pg_tde/t/expected/005_multiple_extensions.out b/contrib/pg_tde/t/expected/003_multiple_extensions.out similarity index 100% rename from contrib/pg_tde/t/expected/005_multiple_extensions.out rename to contrib/pg_tde/t/expected/003_multiple_extensions.out diff --git a/contrib/pg_tde/t/expected/007_tde_heap.out b/contrib/pg_tde/t/expected/004_tde_heap.out similarity index 100% rename from contrib/pg_tde/t/expected/007_tde_heap.out rename to contrib/pg_tde/t/expected/004_tde_heap.out diff --git a/contrib/pg_tde/t/expected/008_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/005_key_rotate_tablespace.out similarity index 100% rename from contrib/pg_tde/t/expected/008_key_rotate_tablespace.out rename to contrib/pg_tde/t/expected/005_key_rotate_tablespace.out diff --git a/contrib/pg_tde/t/expected/009_wal_encrypt.out b/contrib/pg_tde/t/expected/006_wal_encrypt.out similarity index 100% rename from contrib/pg_tde/t/expected/009_wal_encrypt.out rename to contrib/pg_tde/t/expected/006_wal_encrypt.out diff --git a/contrib/pg_tde/t/expected/010_change_key_provider.out b/contrib/pg_tde/t/expected/007_change_key_provider.out similarity index 100% rename from contrib/pg_tde/t/expected/010_change_key_provider.out rename to contrib/pg_tde/t/expected/007_change_key_provider.out diff --git a/contrib/pg_tde/t/expected/011_unlogged_tables.out b/contrib/pg_tde/t/expected/008_unlogged_tables.out similarity index 100% rename from contrib/pg_tde/t/expected/011_unlogged_tables.out rename to contrib/pg_tde/t/expected/008_unlogged_tables.out diff --git a/contrib/pg_tde/t/expected/012_replication.out b/contrib/pg_tde/t/expected/009_replication.out similarity index 100% rename from contrib/pg_tde/t/expected/012_replication.out rename to contrib/pg_tde/t/expected/009_replication.out diff --git a/contrib/pg_tde/t/expected/013_crash_recovery.out b/contrib/pg_tde/t/expected/010_crash_recovery.out similarity index 100% rename from contrib/pg_tde/t/expected/013_crash_recovery.out rename to contrib/pg_tde/t/expected/010_crash_recovery.out From ab2091b0fc338966b2589ed0777d2faca96c1a0f Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 4 Jun 2025 16:12:57 +0300 Subject: [PATCH 423/796] Remove numbering from TAP test names Numbered TAP tests were done so following the core. However, there is no reason for this, and it creates complications when, for example, we need to remove some tests. Besides, regression tests are in alphabetical order. --- contrib/pg_tde/meson.build | 26 +++++++-------- contrib/pg_tde/t/{001_basic.pl => basic.pl} | 0 ...key_provider.pl => change_key_provider.pl} | 0 ...10_crash_recovery.pl => crash_recovery.pl} | 0 .../pg_tde/t/expected/003_remote_config.out | 32 ------------------- contrib/pg_tde/t/expected/004_file_config.out | 32 ------------------- .../t/expected/006_remote_vault_config.out | 32 ------------------- .../t/expected/{001_basic.out => basic.out} | 0 ...y_provider.out => change_key_provider.out} | 0 ..._crash_recovery.out => crash_recovery.out} | 0 ...blespace.out => key_rotate_tablespace.out} | 0 ...extensions.out => multiple_extensions.out} | 0 .../{009_replication.out => replication.out} | 0 .../{002_rotate_key.out => rotate_key.out} | 0 .../{004_tde_heap.out => tde_heap.out} | 0 ...nlogged_tables.out => unlogged_tables.out} | 0 .../{006_wal_encrypt.out => wal_encrypt.out} | 0 ...tablespace.pl => key_rotate_tablespace.pl} | 0 ...e_extensions.pl => multiple_extensions.pl} | 0 ...vider.pl => pg_tde_change_key_provider.pl} | 0 ...g_waldump_basic.pl => pg_waldump_basic.pl} | 0 ...ump_fullpage.pl => pg_waldump_fullpage.pl} | 0 .../t/{009_replication.pl => replication.pl} | 0 .../t/{002_rotate_key.pl => rotate_key.pl} | 0 .../pg_tde/t/{004_tde_heap.pl => tde_heap.pl} | 0 ..._unlogged_tables.pl => unlogged_tables.pl} | 0 .../t/{006_wal_encrypt.pl => wal_encrypt.pl} | 0 27 files changed, 13 insertions(+), 109 deletions(-) rename contrib/pg_tde/t/{001_basic.pl => basic.pl} (100%) rename contrib/pg_tde/t/{007_change_key_provider.pl => change_key_provider.pl} (100%) rename contrib/pg_tde/t/{010_crash_recovery.pl => crash_recovery.pl} (100%) delete mode 100644 contrib/pg_tde/t/expected/003_remote_config.out delete mode 100644 contrib/pg_tde/t/expected/004_file_config.out delete mode 100644 contrib/pg_tde/t/expected/006_remote_vault_config.out rename contrib/pg_tde/t/expected/{001_basic.out => basic.out} (100%) rename contrib/pg_tde/t/expected/{007_change_key_provider.out => change_key_provider.out} (100%) rename contrib/pg_tde/t/expected/{010_crash_recovery.out => crash_recovery.out} (100%) rename contrib/pg_tde/t/expected/{005_key_rotate_tablespace.out => key_rotate_tablespace.out} (100%) rename contrib/pg_tde/t/expected/{003_multiple_extensions.out => multiple_extensions.out} (100%) rename contrib/pg_tde/t/expected/{009_replication.out => replication.out} (100%) rename contrib/pg_tde/t/expected/{002_rotate_key.out => rotate_key.out} (100%) rename contrib/pg_tde/t/expected/{004_tde_heap.out => tde_heap.out} (100%) rename contrib/pg_tde/t/expected/{008_unlogged_tables.out => unlogged_tables.out} (100%) rename contrib/pg_tde/t/expected/{006_wal_encrypt.out => wal_encrypt.out} (100%) rename contrib/pg_tde/t/{005_key_rotate_tablespace.pl => key_rotate_tablespace.pl} (100%) rename contrib/pg_tde/t/{003_multiple_extensions.pl => multiple_extensions.pl} (100%) rename contrib/pg_tde/t/{013_pg_tde_change_key_provider.pl => pg_tde_change_key_provider.pl} (100%) rename contrib/pg_tde/t/{011_pg_waldump_basic.pl => pg_waldump_basic.pl} (100%) rename contrib/pg_tde/t/{012_pg_waldump_fullpage.pl => pg_waldump_fullpage.pl} (100%) rename contrib/pg_tde/t/{009_replication.pl => replication.pl} (100%) rename contrib/pg_tde/t/{002_rotate_key.pl => rotate_key.pl} (100%) rename contrib/pg_tde/t/{004_tde_heap.pl => tde_heap.pl} (100%) rename contrib/pg_tde/t/{008_unlogged_tables.pl => unlogged_tables.pl} (100%) rename contrib/pg_tde/t/{006_wal_encrypt.pl => wal_encrypt.pl} (100%) diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 7cfa87c353036..7abf96de07030 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -103,19 +103,19 @@ sql_tests = [ ] tap_tests = [ - 't/001_basic.pl', - 't/002_rotate_key.pl', - 't/003_multiple_extensions.pl', - 't/004_tde_heap.pl', - 't/005_key_rotate_tablespace.pl', - 't/006_wal_encrypt.pl', - 't/007_change_key_provider.pl', - 't/008_unlogged_tables.pl', - 't/009_replication.pl', - 't/010_crash_recovery.pl', - 't/011_pg_waldump_basic.pl', - 't/012_pg_waldump_fullpage.pl', - 't/013_pg_tde_change_key_provider.pl', + 't/basic.pl', + 't/change_key_provider.pl', + 't/crash_recovery.pl', + 't/key_rotate_tablespace.pl', + 't/multiple_extensions.pl', + 't/pg_tde_change_key_provider.pl', + 't/pg_waldump_basic.pl', + 't/pg_waldump_fullpage.pl', + 't/replication.pl', + 't/rotate_key.pl', + 't/tde_heap.pl', + 't/unlogged_tables.pl', + 't/wal_encrypt.pl', ] tests += { diff --git a/contrib/pg_tde/t/001_basic.pl b/contrib/pg_tde/t/basic.pl similarity index 100% rename from contrib/pg_tde/t/001_basic.pl rename to contrib/pg_tde/t/basic.pl diff --git a/contrib/pg_tde/t/007_change_key_provider.pl b/contrib/pg_tde/t/change_key_provider.pl similarity index 100% rename from contrib/pg_tde/t/007_change_key_provider.pl rename to contrib/pg_tde/t/change_key_provider.pl diff --git a/contrib/pg_tde/t/010_crash_recovery.pl b/contrib/pg_tde/t/crash_recovery.pl similarity index 100% rename from contrib/pg_tde/t/010_crash_recovery.pl rename to contrib/pg_tde/t/crash_recovery.pl diff --git a/contrib/pg_tde/t/expected/003_remote_config.out b/contrib/pg_tde/t/expected/003_remote_config.out deleted file mode 100644 index 9205431bb2ade..0000000000000 --- a/contrib/pg_tde/t/expected/003_remote_config.out +++ /dev/null @@ -1,32 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8888/hello')); - pg_tde_add_database_key_provider_file ---------------------------------------- - -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc2 (k) VALUES (5), (6); -SELECT * FROM test_enc2 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - --- server restart -SELECT * FROM test_enc2 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - -DROP TABLE test_enc2; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/004_file_config.out b/contrib/pg_tde/t/expected/004_file_config.out deleted file mode 100644 index 8031b7df0570d..0000000000000 --- a/contrib/pg_tde/t/expected/004_file_config.out +++ /dev/null @@ -1,32 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location')); - pg_tde_add_database_key_provider_file ---------------------------------------- - -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc1 (k) VALUES (5), (6); -SELECT * FROM test_enc1 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - --- server restart -SELECT * FROM test_enc1 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - -DROP TABLE test_enc1; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/006_remote_vault_config.out b/contrib/pg_tde/t/expected/006_remote_vault_config.out deleted file mode 100644 index 9a467d9b13876..0000000000000 --- a/contrib/pg_tde/t/expected/006_remote_vault_config.out +++ /dev/null @@ -1,32 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-provider', json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/token'), json_object('type' VALUE 'remote', 'url' VALUE 'http://localhost:8889/url'), to_json('secret'::text), NULL); - pg_tde_add_database_key_provider_vault_v2 -------------------------------------------- - -(1 row) - -SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'vault-provider'); - pg_tde_set_key_using_database_key_provider --------------------------------------------- - -(1 row) - -CREATE TABLE test_enc2 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; -INSERT INTO test_enc2 (k) VALUES (5), (6); -SELECT * FROM test_enc2 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - --- server restart -SELECT * FROM test_enc2 ORDER BY id; - id | k -----+--- - 1 | 5 - 2 | 6 -(2 rows) - -DROP TABLE test_enc2; -DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/001_basic.out b/contrib/pg_tde/t/expected/basic.out similarity index 100% rename from contrib/pg_tde/t/expected/001_basic.out rename to contrib/pg_tde/t/expected/basic.out diff --git a/contrib/pg_tde/t/expected/007_change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out similarity index 100% rename from contrib/pg_tde/t/expected/007_change_key_provider.out rename to contrib/pg_tde/t/expected/change_key_provider.out diff --git a/contrib/pg_tde/t/expected/010_crash_recovery.out b/contrib/pg_tde/t/expected/crash_recovery.out similarity index 100% rename from contrib/pg_tde/t/expected/010_crash_recovery.out rename to contrib/pg_tde/t/expected/crash_recovery.out diff --git a/contrib/pg_tde/t/expected/005_key_rotate_tablespace.out b/contrib/pg_tde/t/expected/key_rotate_tablespace.out similarity index 100% rename from contrib/pg_tde/t/expected/005_key_rotate_tablespace.out rename to contrib/pg_tde/t/expected/key_rotate_tablespace.out diff --git a/contrib/pg_tde/t/expected/003_multiple_extensions.out b/contrib/pg_tde/t/expected/multiple_extensions.out similarity index 100% rename from contrib/pg_tde/t/expected/003_multiple_extensions.out rename to contrib/pg_tde/t/expected/multiple_extensions.out diff --git a/contrib/pg_tde/t/expected/009_replication.out b/contrib/pg_tde/t/expected/replication.out similarity index 100% rename from contrib/pg_tde/t/expected/009_replication.out rename to contrib/pg_tde/t/expected/replication.out diff --git a/contrib/pg_tde/t/expected/002_rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out similarity index 100% rename from contrib/pg_tde/t/expected/002_rotate_key.out rename to contrib/pg_tde/t/expected/rotate_key.out diff --git a/contrib/pg_tde/t/expected/004_tde_heap.out b/contrib/pg_tde/t/expected/tde_heap.out similarity index 100% rename from contrib/pg_tde/t/expected/004_tde_heap.out rename to contrib/pg_tde/t/expected/tde_heap.out diff --git a/contrib/pg_tde/t/expected/008_unlogged_tables.out b/contrib/pg_tde/t/expected/unlogged_tables.out similarity index 100% rename from contrib/pg_tde/t/expected/008_unlogged_tables.out rename to contrib/pg_tde/t/expected/unlogged_tables.out diff --git a/contrib/pg_tde/t/expected/006_wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out similarity index 100% rename from contrib/pg_tde/t/expected/006_wal_encrypt.out rename to contrib/pg_tde/t/expected/wal_encrypt.out diff --git a/contrib/pg_tde/t/005_key_rotate_tablespace.pl b/contrib/pg_tde/t/key_rotate_tablespace.pl similarity index 100% rename from contrib/pg_tde/t/005_key_rotate_tablespace.pl rename to contrib/pg_tde/t/key_rotate_tablespace.pl diff --git a/contrib/pg_tde/t/003_multiple_extensions.pl b/contrib/pg_tde/t/multiple_extensions.pl similarity index 100% rename from contrib/pg_tde/t/003_multiple_extensions.pl rename to contrib/pg_tde/t/multiple_extensions.pl diff --git a/contrib/pg_tde/t/013_pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl similarity index 100% rename from contrib/pg_tde/t/013_pg_tde_change_key_provider.pl rename to contrib/pg_tde/t/pg_tde_change_key_provider.pl diff --git a/contrib/pg_tde/t/011_pg_waldump_basic.pl b/contrib/pg_tde/t/pg_waldump_basic.pl similarity index 100% rename from contrib/pg_tde/t/011_pg_waldump_basic.pl rename to contrib/pg_tde/t/pg_waldump_basic.pl diff --git a/contrib/pg_tde/t/012_pg_waldump_fullpage.pl b/contrib/pg_tde/t/pg_waldump_fullpage.pl similarity index 100% rename from contrib/pg_tde/t/012_pg_waldump_fullpage.pl rename to contrib/pg_tde/t/pg_waldump_fullpage.pl diff --git a/contrib/pg_tde/t/009_replication.pl b/contrib/pg_tde/t/replication.pl similarity index 100% rename from contrib/pg_tde/t/009_replication.pl rename to contrib/pg_tde/t/replication.pl diff --git a/contrib/pg_tde/t/002_rotate_key.pl b/contrib/pg_tde/t/rotate_key.pl similarity index 100% rename from contrib/pg_tde/t/002_rotate_key.pl rename to contrib/pg_tde/t/rotate_key.pl diff --git a/contrib/pg_tde/t/004_tde_heap.pl b/contrib/pg_tde/t/tde_heap.pl similarity index 100% rename from contrib/pg_tde/t/004_tde_heap.pl rename to contrib/pg_tde/t/tde_heap.pl diff --git a/contrib/pg_tde/t/008_unlogged_tables.pl b/contrib/pg_tde/t/unlogged_tables.pl similarity index 100% rename from contrib/pg_tde/t/008_unlogged_tables.pl rename to contrib/pg_tde/t/unlogged_tables.pl diff --git a/contrib/pg_tde/t/006_wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl similarity index 100% rename from contrib/pg_tde/t/006_wal_encrypt.pl rename to contrib/pg_tde/t/wal_encrypt.pl From 04e09f2b94d27286efbde6320372c1e23d85bd72 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 15 May 2025 15:26:49 +0200 Subject: [PATCH 424/796] PG-1464 Add CodeChecker integration Add CI workflow for CodeChecker. It runs clang-tidy static analyzer and stores results on CodeChecker server. --- .github/workflows/codechecker.yml | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/codechecker.yml diff --git a/.github/workflows/codechecker.yml b/.github/workflows/codechecker.yml new file mode 100644 index 0000000000000..f04d622f7605e --- /dev/null +++ b/.github/workflows/codechecker.yml @@ -0,0 +1,56 @@ +name: CodeChecker +on: + pull_request: + push: + branches: + - TDE_REL_17_STABLE + +env: + CC: clang + LD: clang + +jobs: + run: + name: Run + runs-on: ubuntu-22.04 + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: ci_scripts/ubuntu-deps.sh + + - name: Install CodeChecker + run: | + ## CodeChecker version should match version installed on server side. + pip3 install codechecker==6.21 + + - name: Configure CodeChecker + run: | + echo "::add-mask::${{ secrets.CODECHECKER_ENDPOINT }}" + echo "::add-mask::${{ secrets.CODECHECKER_ENGINEERING_CREDENTIALS }}" + cat > ~/.codechecker.passwords.json << EOL + { + "client_autologin": true, + "credentials": { + "${{secrets.CODECHECKER_ENDPOINT}}": "${{secrets.CODECHECKER_ENGINEERING_CREDENTIALS}}" + } + } + EOL + + - name: Set cc alternative + run: | + sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 + sudo update-alternatives --set cc /usr/bin/clang + + - name: Build postgres + run: ci_scripts/meson-build.sh debug + + - name: Run CodeChecker + run: CodeChecker analyze build/compile_commands.json --enable sensitive --output ./reports --file ${{ github.workspace }}/contrib/pg_tde + + - name: Upload CodeChecker reports + run: | + CodeChecker store ./reports --url=https://codechecker.percona.com/pg_tde --name=${GITHUB_REF_NAME} --tag=${GITHUB_SHA} --force From dc849084af6fa8614f4940fc4cbe4fa1ddfe187b Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 4 Jun 2025 19:04:54 +0200 Subject: [PATCH 425/796] PG-1464 Run CodeChecker only on main branch There is no actual reason to run CodeChecker for PRs at this moment. Maybe sometime later we will enable it back. That will require a solution on how to properly run such workflow for extenal PRs and keep secrets safe. --- .github/workflows/codechecker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codechecker.yml b/.github/workflows/codechecker.yml index f04d622f7605e..62d7e62cc5abf 100644 --- a/.github/workflows/codechecker.yml +++ b/.github/workflows/codechecker.yml @@ -1,6 +1,5 @@ name: CodeChecker on: - pull_request: push: branches: - TDE_REL_17_STABLE From 226dd2e7b030b0a3234e34ee7995e51a261569d4 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 4 Jun 2025 15:34:13 +0200 Subject: [PATCH 426/796] PG-1592 Return all nulls for key info when no key Since no key being configured is not an error we instead of raising the error we return a tuple with all fields set to NULL. This is a pattern used by other PostgreSQL functions. Also add a more explicit test for pg_tde_verify_server_key(). --- .../pg_tde/expected/default_principal_key.out | 21 +++++-- contrib/pg_tde/expected/key_provider.out | 7 ++- .../pg_tde/src/catalog/tde_principal_key.c | 61 ++++++++----------- contrib/pg_tde/t/expected/rotate_key.out | 42 +++++++++---- contrib/pg_tde/t/expected/wal_encrypt.out | 12 ++++ contrib/pg_tde/t/wal_encrypt.pl | 8 +++ 6 files changed, 96 insertions(+), 55 deletions(-) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 114f5e5dba896..b36393ff0aa51 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -12,8 +12,11 @@ ERROR: principal key not configured for current database -- Should fail: no default principal key for the server yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- @@ -48,8 +51,11 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, @@ -74,8 +80,11 @@ CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + -- Should succeed: "localizes" the default principal key for the database CREATE TABLE test_enc( id SERIAL, diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 47ba57bdca8de..89d115e51f59f 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -1,7 +1,10 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); -ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_name | key_provider_name | key_provider_id | key_creation_time +----------+-------------------+-----------------+------------------- + | | | +(1 row) + SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); ERROR: key provider value cannot be an object SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index b7d894d823ddb..516ab7ddb4c00 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -625,64 +625,55 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) Datum values[6]; bool isnull[6]; HeapTuple tuple; - Datum result; TDEPrincipalKey *principal_key; - TimestampTz ts; - GenericKeyring *keyring; - /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context that cannot accept type record")); LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); + principal_key = GetPrincipalKeyNoDefault(dbOid, LW_SHARED); + if (principal_key == NULL) { - ereport(ERROR, - errmsg("Principal key does not exists for the database"), - errhint("Use set_key interface to set the principal key")); + memset(isnull, true, sizeof(isnull)); } + else + { + GenericKeyring *keyring = GetKeyProviderByID(principal_key->keyInfo.keyringId, principal_key->keyInfo.databaseId); + TimestampTz ts; - keyring = GetKeyProviderByID(principal_key->keyInfo.keyringId, principal_key->keyInfo.databaseId); + values[0] = CStringGetTextDatum(principal_key->keyInfo.name); + isnull[0] = false; - /* Initialize the values and null flags */ + if (keyring) + { + values[1] = CStringGetTextDatum(keyring->provider_name); + isnull[1] = false; + } + else + isnull[1] = true; - /* TEXT: Principal key name */ - values[0] = CStringGetTextDatum(principal_key->keyInfo.name); - isnull[0] = false; - /* TEXT: Keyring provider name */ - if (keyring) - { - values[1] = CStringGetTextDatum(keyring->provider_name); - isnull[1] = false; - } - else - isnull[1] = true; + values[2] = Int32GetDatum(principal_key->keyInfo.keyringId); + isnull[2] = false; - /* INTEGERT: key provider id */ - values[2] = Int32GetDatum(principal_key->keyInfo.keyringId); - isnull[2] = false; + ts = (TimestampTz) principal_key->keyInfo.creationTime.tv_sec - ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); + ts = (ts * USECS_PER_SEC) + principal_key->keyInfo.creationTime.tv_usec; + values[3] = TimestampTzGetDatum(ts); + isnull[3] = false; - /* TIMESTAMP TZ: Principal key creation time */ - ts = (TimestampTz) principal_key->keyInfo.creationTime.tv_sec - ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); - ts = (ts * USECS_PER_SEC) + principal_key->keyInfo.creationTime.tv_usec; - values[3] = TimestampTzGetDatum(ts); - isnull[3] = false; + pfree(keyring); + } LWLockRelease(tde_lwlock_enc_keys()); - /* Form the tuple */ tuple = heap_form_tuple(tupdesc, values, isnull); - /* Make the tuple into a datum */ - result = HeapTupleGetDatum(tuple); - - pfree(keyring); - - PG_RETURN_DATUM(result); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); } + #endif /* FRONTEND */ /* diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 4f7a729433615..288d9d7a3993e 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -66,8 +66,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT * FROM test_enc ORDER BY id; id | k ----+--- @@ -96,8 +99,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT * FROM test_enc ORDER BY id; id | k ----+--- @@ -126,8 +132,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT * FROM test_enc ORDER BY id; id | k ----+--- @@ -156,8 +165,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT * FROM test_enc ORDER BY id; id | k ----+--- @@ -176,8 +188,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -191,8 +206,11 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); (1 row) SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); -psql::1: ERROR: Principal key does not exists for the database -HINT: Use set_key interface to set the principal key + key_provider_id | key_provider_name | key_name +-----------------+-------------------+---------- + | | +(1 row) + DROP TABLE test_enc; ALTER SYSTEM RESET pg_tde.inherit_global_providers; -- server restart diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 27963ea824afd..15f878e0d24a8 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -7,6 +7,12 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test SELECT pg_tde_verify_server_key(); psql::1: ERROR: principal key not configured for current database +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); + key_name | key_provider_name | key_provider_id +----------+-------------------+----------------- + | | +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -19,6 +25,12 @@ SELECT pg_tde_verify_server_key(); (1 row) +SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); + key_name | key_provider_name | key_provider_id +------------+-------------------+----------------- + server-key | file-keyring-010 | -1 +(1 row) + ALTER SYSTEM SET pg_tde.wal_encrypt = on; -- server restart with wal encryption SHOW pg_tde.wal_encrypt; diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index c554fb0f3bf58..2799b3b6d238b 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -25,12 +25,20 @@ PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); +PGTDE::psql($node, 'postgres', + 'SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info();' +); + PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');" ); PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); +PGTDE::psql($node, 'postgres', + 'SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info();' +); + PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); PGTDE::append_to_result_file("-- server restart with wal encryption"); From d37fa69d1db416b4df7ab77a67a0b0e862b14622 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 4 Jun 2025 19:10:10 +0200 Subject: [PATCH 427/796] Clean up static functions in tde_keyring.c Several functions which should have been static were not decleared as such and they also took a pointless write_xlog argument which always was true. Plus add a couple of missing static annotations. --- contrib/pg_tde/src/catalog/tde_keyring.c | 293 +++++++++--------- .../pg_tde/src/include/catalog/tde_keyring.h | 6 - 2 files changed, 147 insertions(+), 152 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b4043e476720d..6ddde83343fb1 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -90,6 +90,10 @@ static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_list_all_key_providers_internal(PG_FUNCTION_ARGS, const char *fname, Oid dbOid); static Size required_shared_mem_size(void); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); +static void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId); +static void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId); +static void delete_key_provider_info(char *provider_name, Oid databaseId); +static void check_provider_record(KeyringProviderRecord *provider_record); #define PG_TDE_LIST_PROVIDERS_COLS 4 @@ -235,7 +239,7 @@ pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) pfree(keyring); - modify_key_provider_info(&provider, dbOid, true); + modify_key_provider_info(&provider, dbOid); PG_RETURN_VOID(); } @@ -252,7 +256,7 @@ pg_tde_add_global_key_provider(PG_FUNCTION_ARGS) return pg_tde_add_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } -Datum +static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) { char *provider_type; @@ -294,7 +298,7 @@ pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid) memcpy(provider.provider_name, provider_name, nlen); memcpy(provider.options, options, olen); provider.provider_type = get_keyring_provider_from_typename(provider_type); - save_new_key_provider_info(&provider, dbOid, true); + save_new_key_provider_info(&provider, dbOid); PG_RETURN_VOID(); } @@ -311,7 +315,7 @@ pg_tde_delete_global_key_provider(PG_FUNCTION_ARGS) return pg_tde_delete_key_provider_internal(fcinfo, GLOBAL_DATA_TDE_OID); } -Datum +static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) { char *provider_name; @@ -343,7 +347,7 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) errmsg("Can't delete a provider which is currently in use")); } - delete_key_provider_info(provider_name, db_oid, true); + delete_key_provider_info(provider_name, db_oid); PG_RETURN_VOID(); } @@ -431,144 +435,11 @@ GetKeyProviderByID(int provider_id, Oid dbOid) return keyring; } -#endif /* !FRONTEND */ - -void -write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) -{ - off_t bytes_written; - int fd; - char kp_info_path[MAXPGPATH]; - - Assert(record != NULL); - Assert(record->offset_in_file >= 0); - Assert(LWLockHeldByMeInMode(tde_provider_info_lock(), LW_EXCLUSIVE)); - - get_keyring_infofile_path(kp_info_path, record->database_id); - fd = BasicOpenFile(kp_info_path, O_CREAT | O_RDWR | PG_BINARY); - if (fd < 0) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not open tde file \"%s\": %m", kp_info_path)); - } - - /* - * emit the xlog here. So that we can handle partial file write errors but - * cannot make new WAL entries during recovery. - */ - if (write_xlog) - { -#ifndef FRONTEND - XLogBeginInsert(); - XLogRegisterData((char *) record, sizeof(KeyringProviderRecordInFile)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); -#else - Assert(false); -#endif - } - - bytes_written = pg_pwrite(fd, &(record->provider), - sizeof(KeyringProviderRecord), - record->offset_in_file); - if (bytes_written != sizeof(KeyringProviderRecord)) - { - close(fd); - ereport(ERROR, - errcode_for_file_access(), - errmsg("key provider info file \"%s\" can't be written: %m", - kp_info_path)); - } - if (pg_fsync(fd) != 0) - { - close(fd); - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", kp_info_path)); - } - close(fd); -} - -static void -check_provider_record(KeyringProviderRecord *provider_record) -{ - GenericKeyring *provider; - - if (provider_record->provider_type == UNKNOWN_KEY_PROVIDER) - { - ereport(ERROR, - errcode(ERRCODE_DATA_EXCEPTION), - errmsg("Invalid provider type.")); - } - - /* Validate that the provider record can be properly parsed. */ - provider = load_keyring_provider_from_record(provider_record); - - if (provider == NULL) - { - ereport(ERROR, - errcode(ERRCODE_DATA_EXCEPTION), - errmsg("Invalid provider options.")); - } - - KeyringValidate(provider); - -#ifndef FRONTEND /* We can't scan the pg_database catalog from - * frontend. */ - if (provider->keyring_id != 0) - { - /* - * If we are modifying an existing provider, verify that all of the - * keys already in use are the same. - */ - pg_tde_verify_provider_keys_in_use(provider); - } -#endif - - pfree(provider); -} - -/* Returns true if the record is found, false otherwise. */ -bool -get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, - KeyringProviderRecordInFile *record) -{ - off_t current_file_offset = 0; - off_t next_file_offset = 0; - int fd; - KeyringProviderRecord existing_provider; - - Assert(provider_name != NULL); - Assert(record != NULL); - - fd = open_keyring_infofile(database_id, O_RDONLY); - - while (fetch_next_key_provider(fd, &next_file_offset, &existing_provider)) - { - /* Ignore deleted provider records */ - if (existing_provider.provider_type != UNKNOWN_KEY_PROVIDER - && strcmp(existing_provider.provider_name, provider_name) == 0) - { - record->database_id = database_id; - record->offset_in_file = current_file_offset; - record->provider = existing_provider; - close(fd); - return true; - } - - current_file_offset = next_file_offset; - } - - /* No matching key provider found */ - close(fd); - return false; -} - /* * Save the key provider info to the file */ -void -save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) +static void +save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId) { off_t next_file_offset; int fd; @@ -631,8 +502,8 @@ save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool LWLockRelease(tde_provider_info_lock()); } -void -modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool write_xlog) +static void +modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId) { KeyringProviderRecordInFile record; @@ -658,13 +529,13 @@ modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId, bool w } record.provider = *provider; - write_key_provider_info(&record, write_xlog); + write_key_provider_info(&record, true); LWLockRelease(tde_provider_info_lock()); } -void -delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog) +static void +delete_key_provider_info(char *provider_name, Oid databaseId) { int provider_id; KeyringProviderRecordInFile record; @@ -684,11 +555,141 @@ delete_key_provider_info(char *provider_name, Oid databaseId, bool write_xlog) provider_id = record.provider.provider_id; memset(&(record.provider), 0, sizeof(KeyringProviderRecord)); record.provider.provider_id = provider_id; - write_key_provider_info(&record, write_xlog); + write_key_provider_info(&record, true); LWLockRelease(tde_provider_info_lock()); } +static void +check_provider_record(KeyringProviderRecord *provider_record) +{ + GenericKeyring *provider; + + if (provider_record->provider_type == UNKNOWN_KEY_PROVIDER) + { + ereport(ERROR, + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("Invalid provider type.")); + } + + /* Validate that the provider record can be properly parsed. */ + provider = load_keyring_provider_from_record(provider_record); + + if (provider == NULL) + { + ereport(ERROR, + errcode(ERRCODE_DATA_EXCEPTION), + errmsg("Invalid provider options.")); + } + + KeyringValidate(provider); + + if (provider->keyring_id != 0) + { + /* + * If we are modifying an existing provider, verify that all of the + * keys already in use are the same. + */ + pg_tde_verify_provider_keys_in_use(provider); + } + + pfree(provider); +} + +#endif /* !FRONTEND */ + +void +write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) +{ + off_t bytes_written; + int fd; + char kp_info_path[MAXPGPATH]; + + Assert(record != NULL); + Assert(record->offset_in_file >= 0); + Assert(LWLockHeldByMeInMode(tde_provider_info_lock(), LW_EXCLUSIVE)); + + get_keyring_infofile_path(kp_info_path, record->database_id); + fd = BasicOpenFile(kp_info_path, O_CREAT | O_RDWR | PG_BINARY); + if (fd < 0) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", kp_info_path)); + } + + /* + * emit the xlog here. So that we can handle partial file write errors but + * cannot make new WAL entries during recovery. + */ + if (write_xlog) + { +#ifndef FRONTEND + XLogBeginInsert(); + XLogRegisterData((char *) record, sizeof(KeyringProviderRecordInFile)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_WRITE_KEY_PROVIDER); +#else + Assert(false); +#endif + } + + bytes_written = pg_pwrite(fd, &(record->provider), + sizeof(KeyringProviderRecord), + record->offset_in_file); + if (bytes_written != sizeof(KeyringProviderRecord)) + { + close(fd); + ereport(ERROR, + errcode_for_file_access(), + errmsg("key provider info file \"%s\" can't be written: %m", + kp_info_path)); + } + if (pg_fsync(fd) != 0) + { + close(fd); + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", kp_info_path)); + } + close(fd); +} + +/* Returns true if the record is found, false otherwise. */ +bool +get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, + KeyringProviderRecordInFile *record) +{ + off_t current_file_offset = 0; + off_t next_file_offset = 0; + int fd; + KeyringProviderRecord existing_provider; + + Assert(provider_name != NULL); + Assert(record != NULL); + + fd = open_keyring_infofile(database_id, O_RDONLY); + + while (fetch_next_key_provider(fd, &next_file_offset, &existing_provider)) + { + /* Ignore deleted provider records */ + if (existing_provider.provider_type != UNKNOWN_KEY_PROVIDER + && strcmp(existing_provider.provider_name, provider_name) == 0) + { + record->database_id = database_id; + record->offset_in_file = current_file_offset; + record->provider = existing_provider; + close(fd); + return true; + } + + current_file_offset = next_file_offset; + } + + /* No matching key provider found */ + close(fd); + return false; +} + #ifdef FRONTEND GenericKeyring * GetKeyProviderByID(int provider_id, Oid dbOid) diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 75b490b3218f5..11c24027747e6 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -35,12 +35,6 @@ extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); extern ProviderType get_keyring_provider_from_typename(char *provider_type); extern void InitializeKeyProviderInfo(void); extern void key_provider_startup_cleanup(Oid databaseId); -extern void save_new_key_provider_info(KeyringProviderRecord *provider, - Oid databaseId, bool write_xlog); -extern void modify_key_provider_info(KeyringProviderRecord *provider, - Oid databaseId, bool write_xlog); -extern void delete_key_provider_info(char *provider_name, - Oid databaseId, bool write_xlog); extern bool get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, KeyringProviderRecordInFile *record); From 4173798994058d6e297f2fc39ba51b50fd1ba6cf Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 26 May 2025 15:52:31 +0200 Subject: [PATCH 428/796] Stop coalescing NULL to empty string in key provider functions Since we can pass NULL values in the functions which takes JSON it makes no sense to not do so in the functions which take text. By doing this we only risk not excercising some code paths. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 72 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 909543f5fe2eb..f4e91e1313fb0 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -17,7 +17,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('file', provider_name, - json_object('path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path JSON) @@ -41,10 +41,10 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, - json_object('url' VALUE COALESCE(vault_url, ''), - 'tokenPath' VALUE COALESCE(vault_token_path, ''), - 'mountPath' VALUE COALESCE(vault_mount_path, ''), - 'caPath' VALUE COALESCE(vault_ca_path, ''))); + json_object('url' VALUE vault_url, + 'tokenPath' VALUE vault_token_path, + 'mountPath' VALUE vault_mount_path, + 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, @@ -59,11 +59,11 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('kmip', provider_name, - json_object('host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE COALESCE(kmip_ca_path, ''), - 'certPath' VALUE COALESCE(kmip_cert_path, ''), - 'keyPath' VALUE COALESCE(kmip_key_path, ''))); + 'caPath' VALUE kmip_ca_path, + 'certPath' VALUE kmip_cert_path, + 'keyPath' VALUE kmip_key_path)); END; CREATE FUNCTION pg_tde_list_all_database_key_providers @@ -100,7 +100,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('file', provider_name, - json_object('path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path JSON) @@ -124,10 +124,10 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, - json_object('url' VALUE COALESCE(vault_url, ''), - 'tokenPath' VALUE COALESCE(vault_token_path, ''), - 'mountPath' VALUE COALESCE(vault_mount_path, ''), - 'caPath' VALUE COALESCE(vault_ca_path, ''))); + json_object('url' VALUE vault_url, + 'tokenPath' VALUE vault_token_path, + 'mountPath' VALUE vault_mount_path, + 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, @@ -142,11 +142,11 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('kmip', provider_name, - json_object('host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE COALESCE(kmip_ca_path, ''), - 'certPath' VALUE COALESCE(kmip_cert_path, ''), - 'keyPath' VALUE COALESCE(kmip_key_path, ''))); + 'caPath' VALUE kmip_ca_path, + 'certPath' VALUE kmip_cert_path, + 'keyPath' VALUE kmip_key_path)); END; -- Key Provider Management @@ -163,7 +163,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('file', provider_name, - json_object('path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path JSON) @@ -187,10 +187,10 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, - json_object('url' VALUE COALESCE(vault_url, ''), - 'tokenPath' VALUE COALESCE(vault_token_path, ''), - 'mountPath' VALUE COALESCE(vault_mount_path, ''), - 'caPath' VALUE COALESCE(vault_ca_path, ''))); + json_object('url' VALUE vault_url, + 'tokenPath' VALUE vault_token_path, + 'mountPath' VALUE vault_mount_path, + 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, @@ -205,11 +205,11 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('kmip', provider_name, - json_object('host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE COALESCE(kmip_ca_path, ''), - 'certPath' VALUE COALESCE(kmip_cert_path, ''), - 'keyPath' VALUE COALESCE(kmip_key_path, ''))); + 'caPath' VALUE kmip_ca_path, + 'certPath' VALUE kmip_cert_path, + 'keyPath' VALUE kmip_key_path)); END; -- Global Tablespace Key Provider Management @@ -226,7 +226,7 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_file_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('file', provider_name, - json_object('path' VALUE COALESCE(file_path, ''))); + json_object('path' VALUE file_path)); END; CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path JSON) @@ -250,10 +250,10 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, - json_object('url' VALUE COALESCE(vault_url, ''), - 'tokenPath' VALUE COALESCE(vault_token_path, ''), - 'mountPath' VALUE COALESCE(vault_mount_path, ''), - 'caPath' VALUE COALESCE(vault_ca_path, ''))); + json_object('url' VALUE vault_url, + 'tokenPath' VALUE vault_token_path, + 'mountPath' VALUE vault_mount_path, + 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, @@ -268,11 +268,11 @@ BEGIN ATOMIC -- JSON keys in the options must be matched to the keys in -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('kmip', provider_name, - json_object('host' VALUE COALESCE(kmip_host, ''), + json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE COALESCE(kmip_ca_path, ''), - 'certPath' VALUE COALESCE(kmip_cert_path, ''), - 'keyPath' VALUE COALESCE(kmip_key_path, ''))); + 'caPath' VALUE kmip_ca_path, + 'certPath' VALUE kmip_cert_path, + 'keyPath' VALUE kmip_key_path)); END; CREATE FUNCTION pg_tde_is_encrypted(relation REGCLASS) From 9686b82968625a874e1de15e358cfe71e1ad2cb7 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 6 Jun 2025 12:09:35 +0300 Subject: [PATCH 429/796] README updates (#389) Updated the pg_tde README based on @Robinyo feedback, removed unnecessary config steps and replaced with links to docs for up to date information, reorganized content and information for clarity and descriptions, improved SEO. --- contrib/pg_tde/README.md | 168 ++++++++------------------------------- 1 file changed, 33 insertions(+), 135 deletions(-) diff --git a/contrib/pg_tde/README.md b/contrib/pg_tde/README.md index cdcb0bbe47fbc..a87118052f0f6 100644 --- a/contrib/pg_tde/README.md +++ b/contrib/pg_tde/README.md @@ -1,168 +1,66 @@ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/percona/pg_tde/badge)](https://scorecard.dev/viewer/?uri=github.com/percona/pg_tde) [![codecov](https://codecov.io/github/percona/postgres/graph/badge.svg?token=Wow78BMYdP)](https://codecov.io/github/percona/postgres) [![Forum](https://img.shields.io/badge/Forum-join-brightgreen)](https://forums.percona.com/) +[![Docs](https://img.shields.io/badge/docs-pg_tde-blue)](https://docs.percona.com/pg-tde/) # pg_tde: Transparent Database Encryption for PostgreSQL -The PostgreSQL extension provides data at rest encryption. It is currently in an experimental phase and is under active development. [We need your feedback!](https://github.com/percona/pg_tde/discussions/151) +The PostgreSQL extension provides data at rest encryption. It is currently in an experimental phase and is under active development. [We need your feedback!](https://github.com/percona/postgres/discussions) + +## Table of Contents -## Table of contents 1. [Overview](#overview) 2. [Documentation](#documentation) -1. [Percona Server for PostgreSQL](#percona-server-for-postgresql) -3. [Build from sources](#building-from-sources-for-community-postgresql) +3. [Percona Server for PostgreSQL](#percona-server-for-postgresql) 4. [Run in docker](#run-in-docker) -5. [Setting up](#setting-up) -6. [Helper functions](#helper-functions) +5. [Set up pg_tde](#set-up-pg_tde) +6. [Downloads](#downloads) +7. [Additional functions](#additional-functions) ## Overview -Transparent Data Encryption offers encryption at the file level and solves the problem of protecting data at rest. The encryption is transparent for users allowing them to access and manipulate the data and not to worry about the encryption process. As a key provider, the extension supports the keyringfile and [Hashicorp Vault](https://www.vaultproject.io/). -### This extension provides one `access method`: +Transparent Data Encryption offers encryption at the file level and solves the problem of protecting data at rest. The encryption is transparent for users allowing them to access and manipulate the data and not to worry about the encryption process. The extension supports [keyringfile and external Key Management Systems (KMS) through a Global Key Provider interface](../pg_tde/documentation/docs/global-key-provider-configuration/index.md). + +### This extension provides the `tde_heap access method` + +This access method: -#### `tde_heap` access method - Works only with [Percona Server for PostgreSQL 17](https://docs.percona.com/postgresql/17/postgresql-server.html) - Uses extended Storage Manager and WAL APIs - Encrypts tuples, WAL and indexes -- **Doesn't** encrypt temporary files and statistics **yet** +- It **does not** encrypt temporary files and statistics **yet** ## Documentation -Full and comprehensive documentation about `pg_tde` is available at https://docs.percona.com/pg-tde/index.html. +For more information about `pg_tde`, [see the official documentation](https://docs.percona.com/pg-tde/index.html). ## Percona Server for PostgreSQL Percona provides binary packages of `pg_tde` extension only for Percona Server for PostgreSQL. Learn how to install them or build `pg_tde` from sources for PSPG in the [documentation](https://docs.percona.com/pg-tde/install.html). -## Building from sources for community PostgreSQL - 1. Install required dependencies (replace XX with 16 or 17) - - On Debian and Ubuntu: - ```sh - sudo apt install make gcc autoconf git libcurl4-openssl-dev postgresql-server-dev-XX - ``` - - - On RHEL 8 compatible OS: - ```sh - sudo yum install epel-release - yum --enablerepo=powertools install git make gcc autoconf libcurl-devel perl-IPC-Run redhat-rpm-config openssl-devel postgresqlXX-devel - ``` - - - On MacOS: - ```sh - brew install make autoconf curl gettext postresql@XX - ``` - - 2. Install or build postgresql 16 or 17 - 3. If postgres is installed in a non standard directory, set the `PG_CONFIG` environment variable to point to the `pg_config` executable - - 4. Clone the repository, build and install it with the following commands: - - ```sh - git clone https://github.com/percona/pg_tde - ``` - - 5. Compile and install the extension - - ```sh - cd pg_tde - make USE_PGXS=1 - sudo make USE_PGXS=1 install - ``` - ## Run in Docker -There is a [docker image](https://hub.docker.com/r/perconalab/pg_tde) with `pg_tde` based community [PostgreSQL 16](https://hub.docker.com/_/postgres) - -``` -docker run --name pg-tde -e POSTGRES_PASSWORD=mysecretpassword -d perconalab/pg_tde -``` -Docker file is available [here](https://github.com/percona/pg_tde/blob/main/docker/Dockerfile) - - -_See [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-builds-for-developers) for more info on the build infrastructure._ - -## Setting up - - 1. Add extension to the `shared_preload_libraries`: - 1. Via configuration file `postgresql.conf ` - ``` - shared_preload_libraries=pg_tde - ``` - 2. Via SQL using [ALTER SYSTEM](https://www.postgresql.org/docs/current/sql-altersystem.html) command - ```sql - ALTER SYSTEM SET shared_preload_libraries = 'pg_tde'; - ``` - 2. Start or restart the `postgresql` cluster to apply the changes. - * On Debian and Ubuntu: - - ```sh - sudo systemctl restart postgresql-17 - ``` - - * On RHEL 8 compatible OS (replace XX with your version): - ```sh - sudo systemctl restart postgresql-XX.service - ``` - 3. [CREATE EXTENSION](https://www.postgresql.org/docs/current/sql-createextension.html) with SQL (requires superuser or a database owner privileges): - - ```sql - CREATE EXTENSION pg_tde; - ``` - 4. Create a key provider. Currently `pg_tde` supports `File` and `Vault-V2` key providers. You can add the required key provider using one of the functions. - - - ```sql - -- For Vault-V2 key provider - -- pg_tde_add_database_key_provider_vault_v2(provider_name, vault_token, vault_url, vault_mount_path, vault_ca_path) - SELECT pg_tde_add_database_key_provider_vault_v2( - 'vault-provider', - '/path/to/token_file', - 'https://your.vault.server', - 'secret', NULL); - - -- For File key provider - -- pg_tde_add_database_key_provider_file(provider_name, file_path); - SELECT pg_tde_add_database_key_provider_file('file','/tmp/pgkeyring'); - ``` - - **Note: The `File` provided is intended for development and stores the keys unencrypted in the specified data file.** - - 5. Set the principal key for the database using the `pg_tde_set_key` function. - - ```sql - -- pg_tde_set_key_using_database_key_provider(key_name, provider_name); - SELECT pg_tde_set_key_using_database_key_provider('my-key','file'); - ``` - - 6. Specify `tde_heap` access method during table creation - ```sql - CREATE TABLE albums ( - album_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - artist_id INTEGER, - title TEXT NOT NULL, - released DATE NOT NULL - ) USING tde_heap; - ``` - 7. You can encrypt existing table. It requires rewriting the table, so for large tables, it might take a considerable amount of time. - ```sql - ALTER TABLE table_name SET ACCESS METHOD tde_heap; - ``` - - -## Latest test release - -To download the latest build of the main branch, use the `HEAD` release from [releases](https://github.com/percona/pg_tde/releases). +To run `pg_tde` in Docker, follow the instructions in the [official pg_tde Docker documentation](https://docs.percona.com/postgresql/17/docker.html#enable-encryption). -Builds are available in a tar.gz format, containing only the required files, and as a deb package. -The deb package is built against the pgdg16 release, but this dependency is not yet enforced in the package. +_For details on the build process and developer setup, see [Make Builds for Developers](https://github.com/percona/pg_tde/wiki/Make-builds-for-developers)._ +## Set up pg_tde -## Helper functions +For more information on setting up and configuring `pg_tde`, see the [official pg_tde setup topic](https://docs.percona.com/pg-tde/setup.html). -The extension provides the following helper functions: +The guide also includes instructions for: + +- Installing and enabling the extension +- Setting up key providers +- Creating encrypted tables + +## Downloads + +To download the latest build of the main branch, use the `HEAD` release from [releases](https://github.com/percona/postgres/releases). + +Builds are available in a tar.gz format, containing only the required files, and as a deb package. +The deb package is built against the pgdg17 release, but this dependency is not yet enforced in the package. -### pg_tde_is_encrypted(tablename) +## Additional functions -Returns `t` if the relation is encrypted, if unencrypted `f` or `NULL` if the -relation lacks storage, i.e. views, foreign tables, and partitioning tables and -indexes. +Learn more about the helper functions available in `pg_tde`, including how to check table encryption status, in the [Functions topic](https://docs.percona.com/pg-tde/functions.html?h=pg_tde_is_encrypted#encryption-status-check). From 5c33f38fa6bee54927d084ad2cdaa72faf6573e3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 5 Jun 2025 09:13:05 +0200 Subject: [PATCH 430/796] PG-1651 Do not try the fetch key from old provider when changing provider settings We used to verify that all keys are still accessible by fetching the key from cache and if not cached from the current provider settings when changing the key provider setting but this makes things fragile and prone to user error in the case where a principal key is not cached. Instead we can rely on the AEAD tag in the header of each file, which has two advantages 1) the cached and non-cache code path works the same and 2) we do not need to care about if the old provider is accessible, only that the new one is and that it contains the right key (i.e. one that can verify the header tag). --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 +- .../pg_tde/src/catalog/tde_principal_key.c | 42 ++++++------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/t/change_key_provider.pl | 29 +++++++-- .../pg_tde/t/expected/change_key_provider.out | 59 ++++++++++++++++--- 5 files changed, 100 insertions(+), 36 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index b0e327eb83622..d54784ce02959 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -696,9 +696,9 @@ pg_tde_count_relations(Oid dbOid) } bool -pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) +pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data) { - return AesGcmDecrypt(principal_key->keyData, + return AesGcmDecrypt(principal_key_data->data, signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), NULL, 0, diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 516ab7ddb4c00..da4e88cc2133f 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -677,9 +677,7 @@ pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid) #endif /* FRONTEND */ /* - * Gets principal key form the keyring. - * - * Caller should hold an exclusive tde_lwlock_enc_keys lock + * Get principal key form the keyring. */ static TDEPrincipalKey * get_principal_key_from_keyring(Oid dbOid) @@ -710,6 +708,12 @@ get_principal_key_from_keyring(Oid dbOid) errmsg("failed to retrieve principal key %s from keyring with ID %d", principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); + if (!pg_tde_verify_principal_key_info(principalKeyInfo, &keyInfo->data)) + ereport(ERROR, + errcode(ERRCODE_DATA_CORRUPTED), + errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file", + principalKeyInfo->data.name)); + principalKey = palloc_object(TDEPrincipalKey); principalKey->keyInfo = principalKeyInfo->data; @@ -718,12 +722,6 @@ get_principal_key_from_keyring(Oid dbOid) Assert(dbOid == principalKey->keyInfo.databaseId); - if (!pg_tde_verify_principal_key_info(principalKeyInfo, principalKey)) - ereport(ERROR, - errcode(ERRCODE_DATA_CORRUPTED), - errmsg("Failed to verify principal key header for key %s, incorrect principal key or corrupted key file", - principalKeyInfo->data.name)); - pfree(keyInfo); pfree(keyring); pfree(principalKeyInfo); @@ -929,7 +927,7 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId) void pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) { - TDEPrincipalKey *existing_principal_key; + TDESignedPrincipalKeyInfo *existing_principal_key; HeapTuple tuple; SysScanDesc scan; Relation rel; @@ -940,11 +938,11 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); /* Check the server key that is used for WAL encryption */ - existing_principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); + existing_principal_key = pg_tde_get_principal_key_info(GLOBAL_DATA_TDE_OID); if (existing_principal_key != NULL && - existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + existing_principal_key->data.keyringId == modified_provider->keyring_id) { - char *key_name = existing_principal_key->keyInfo.name; + char *key_name = existing_principal_key->data.name; KeyringReturnCodes return_code; KeyInfo *proposed_key; @@ -956,8 +954,7 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) key_name, modified_provider->provider_name, return_code)); } - if (proposed_key->data.len != existing_principal_key->keyLength || - memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) { ereport(ERROR, errmsg("key \"%s\" from modified key provider \"%s\" does not match existing server key", @@ -965,6 +962,9 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) } } + if (existing_principal_key) + pfree(existing_principal_key); + /* Check all databases for usage of keys from this key provider. */ rel = table_open(DatabaseRelationId, AccessShareLock); scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); @@ -973,11 +973,11 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) { Form_pg_database database = (Form_pg_database) GETSTRUCT(tuple); - existing_principal_key = GetPrincipalKeyNoDefault(database->oid, LW_EXCLUSIVE); + existing_principal_key = pg_tde_get_principal_key_info(database->oid); if (existing_principal_key != NULL && - existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id) + existing_principal_key->data.keyringId == modified_provider->keyring_id) { - char *key_name = existing_principal_key->keyInfo.name; + char *key_name = existing_principal_key->data.name; KeyringReturnCodes return_code; KeyInfo *proposed_key; @@ -989,14 +989,16 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) key_name, database->datname.data, modified_provider->provider_name, return_code)); } - if (proposed_key->data.len != existing_principal_key->keyLength || - memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0) + if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) { ereport(ERROR, errmsg("key \"%s\" from modified key provider \"%s\" does not match existing key used by database \"%s\"", key_name, modified_provider->provider_name, database->datname.data)); } } + + if (existing_principal_key) + pfree(existing_principal_key); } systable_endscan(scan); table_close(rel, AccessShareLock); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index e19f787af2b1e..471840554093e 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -103,7 +103,7 @@ extern int pg_tde_count_relations(Oid dbOid); extern void pg_tde_delete_tde_files(Oid dbOid); extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); -extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); +extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data); extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); diff --git a/contrib/pg_tde/t/change_key_provider.pl b/contrib/pg_tde/t/change_key_provider.pl index edf482c2ba760..4dbc262cf2d95 100644 --- a/contrib/pg_tde/t/change_key_provider.pl +++ b/contrib/pg_tde/t/change_key_provider.pl @@ -12,8 +12,6 @@ unlink('/tmp/change_key_provider_1.per'); unlink('/tmp/change_key_provider_2.per'); -unlink('/tmp/change_key_provider_3.per'); -unlink('/tmp/change_key_provider_4.per'); my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; @@ -26,7 +24,7 @@ "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_list_all_database_key_providers();"); + "SELECT * FROM pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" ); @@ -48,7 +46,7 @@ "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_2.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_list_all_database_key_providers();"); + "SELECT * FROM pg_tde_list_all_database_key_providers();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); @@ -57,7 +55,28 @@ PGTDE::append_to_result_file("-- server restart"); $node->restart; -# Verify +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +# Move file, restart and then change provider +PGTDE::append_to_result_file( + "-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_1.per"); +move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_1.per'); + +PGTDE::append_to_result_file("-- server restart"); +$node->restart; + +PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); +PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); +PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT * FROM pg_tde_list_all_database_key_providers();"); + PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');"); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index 3ebd708be0850..4d912c77936d6 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -5,10 +5,10 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov (1 row) -SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------ - (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_1.per""}") +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); @@ -45,10 +45,10 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) -SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ------------------------------------------------------------------------ - (1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_2.per""}") +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_2.per"} (1 row) SELECT pg_tde_verify_key(); @@ -90,5 +90,48 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) +-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_1.per +-- server restart +SELECT pg_tde_verify_key(); +psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT * FROM test_enc ORDER BY id; +psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); + pg_tde_change_database_key_provider_file +------------------------------------------ + +(1 row) + +SELECT * FROM pg_tde_list_all_database_key_providers(); + id | provider_name | provider_type | options +----+---------------+---------------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} +(1 row) + +SELECT pg_tde_verify_key(); + pg_tde_verify_key +------------------- + +(1 row) + +SELECT pg_tde_is_encrypted('test_enc'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +SELECT * FROM test_enc ORDER BY id; + id | k +----+--- + 1 | 5 + 2 | 6 +(2 rows) + DROP EXTENSION pg_tde CASCADE; psql::1: NOTICE: drop cascades to table test_enc From ba4dfe7e07c411e1db8f57b17bdfd30467d75798 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 4 Jun 2025 17:43:48 +0200 Subject: [PATCH 431/796] PG-1607 Add tests for that Vault CA path is optional for pg_tde_change_key_provider Also do not rely on the Vault server setup since any file on disk will do. --- .../pg_tde/t/pg_tde_change_key_provider.pl | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index 284611d178498..f00e394840b74 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -22,7 +22,8 @@ q{SELECT oid FROM pg_catalog.pg_database WHERE datname = 'postgres'}); my $options; -my $token_file = $ENV{ROOT_TOKEN_FILE}; +my $token_file = "${PostgreSQL::Test::Utils::tmp_check}/vault_token"; +append_to_file($token_file, 'DUMMY'); $node->stop; @@ -65,13 +66,13 @@ $db_oid, 'database-provider', 'vault-v2', - 'http://vault-server.example:8200/', + 'https://vault-server.example:8200/', $token_file, 'mount-path', '/tmp/ca_path', ], qr/Key provider updated successfully!/, - 'updates key provider to vault-v2 type'); + 'updates key provider to vault-v2 type with https'); $node->start; @@ -90,7 +91,7 @@ is($options->{tokenPath}, $token_file, 'tokenPath is set correctly for vault-v2 provider'); is( $options->{url}, - 'http://vault-server.example:8200/', + 'https://vault-server.example:8200/', 'url is set correctly for vault-v2 provider'); is($options->{mountPath}, 'mount-path', 'mount path is set correctly for vault-v2 provider'); @@ -99,6 +100,45 @@ $node->stop; +command_like( + [ + 'pg_tde_change_key_provider', + '-D' => $node->data_dir, + $db_oid, + 'database-provider', + 'vault-v2', + 'http://vault-server.example:8200/', + $token_file, + 'mount-path-2', + ], + qr/Key provider updated successfully!/, + 'updates key provider to vault-v2 type with http'); + +$node->start; + +is( $node->safe_psql( + 'postgres', + q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + ), + 'vault-v2', + 'provider type is set to vault-v2'); + +$options = decode_json( + $node->safe_psql( + 'postgres', + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + )); +is($options->{tokenPath}, $token_file, + 'tokenPath is set correctly for vault-v2 provider'); +is( $options->{url}, + 'http://vault-server.example:8200/', + 'url is set correctly for vault-v2 provider'); +is($options->{mountPath}, 'mount-path-2', + 'mount path is set correctly for vault-v2 provider'); +is($options->{caPath}, '', 'CA path is set correctly for vault-v2 provider'); + +$node->stop; + command_like( [ 'pg_tde_change_key_provider', From fb83aa1ab9aca7e89f77e805f73ba3876c8d9469 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 17:37:36 +0200 Subject: [PATCH 432/796] PG-1607 Add a very basic test for change Vault key provider This is a very basic test but before this there were no tests at all. --- contrib/pg_tde/expected/vault_v2_test.out | 3 +++ contrib/pg_tde/sql/vault_v2_test.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 7042a6fa43642..0dc4a637b53ad 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -54,4 +54,7 @@ DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); ERROR: HTTP(S) request to keyring provider "will-not-work" failed +-- Changing provider fails if we can't connect to vault +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +ERROR: HTTP(S) request to keyring provider "vault-v2" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index d0cce4719fccd..78c8c6e434cf8 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -34,4 +34,7 @@ DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +-- Changing provider fails if we can't connect to vault +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); + DROP EXTENSION pg_tde; From 2272e9df41cf3ae15bd66b2c6cbb1434c6fbf085 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 30 May 2025 18:38:54 +0200 Subject: [PATCH 433/796] PG-1607 Run our test suite for Vault with HTTPS Since the dev mode of Vault supports generating HTTPs ceritficates we should just use that since in production everyone will use HTTPS we should run our tests with HTTPS too. --- ci_scripts/setup-keyring-servers.sh | 14 ++++++++------ contrib/pg_tde/expected/vault_v2_test.out | 17 ++++++++++++----- contrib/pg_tde/sql/vault_v2_test.sql | 17 ++++++++++++----- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index 356d98b586ce9..962f859244339 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -17,12 +17,14 @@ cd .. echo $SCRIPT_DIR pykmip-server -f "$SCRIPT_DIR/../contrib/pg_tde/pykmip-server.conf" -l /tmp/kmip-server.log & -TV=$(mktemp) -{ exec >$TV; vault server -dev; } & +CLUSTER_INFO=$(mktemp) +vault server -dev -dev-tls -dev-cluster-json="$CLUSTER_INFO" > /dev/null & sleep 10 -export ROOT_TOKEN_FILE=$(mktemp) -cat $TV | grep "Root Token" | cut -d ":" -f 2 | xargs echo -n > $ROOT_TOKEN_FILE -echo "export ROOT_TOKEN_FILE=$ROOT_TOKEN_FILE" +export VAULT_ROOT_TOKEN_FILE=$(mktemp) +jq -r .root_token "$CLUSTER_INFO" > "$VAULT_ROOT_TOKEN_FILE" +export VAULT_CACERT_FILE=$(jq -r .ca_cert_path "$CLUSTER_INFO") +rm "$CLUSTER_INFO" if [ -v GITHUB_ACTIONS ]; then - echo "ROOT_TOKEN_FILE=$ROOT_TOKEN_FILE" >> $GITHUB_ENV + echo "VAULT_ROOT_TOKEN_FILE=$VAULT_ROOT_TOKEN_FILE" >> $GITHUB_ENV + echo "VAULT_CACERT_FILE=$VAULT_CACERT_FILE" >> $GITHUB_ENV fi diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 0dc4a637b53ad..291d230dd1df9 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,6 +1,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -\getenv root_token_file ROOT_TOKEN_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +\getenv root_token_file VAULT_ROOT_TOKEN_FILE +\getenv cacert_file VAULT_CACERT_FILE +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','https://127.0.0.1:8200','DUMMY-TOKEN',:'cacert_file'); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -16,7 +17,7 @@ CREATE TABLE test_enc( ) USING tde_heap; ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','https://127.0.0.1:8200','secret',:'cacert_file'); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -52,9 +53,15 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); ERROR: HTTP(S) request to keyring provider "will-not-work" failed -- Changing provider fails if we can't connect to vault -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); ERROR: HTTP(S) request to keyring provider "vault-v2" failed +-- HTTPS without cert fails +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:8200', 'secret', NULL); +ERROR: HTTP(S) request to keyring provider "vault-v2" failed +-- HTTP against HTTPS server fails +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:8200', 'secret', NULL); +ERROR: Listing secrets of "http://127.0.0.1:8200" at mountpoint "secret" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 78c8c6e434cf8..a1f5a92233db2 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -1,8 +1,9 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -\getenv root_token_file ROOT_TOKEN_FILE +\getenv root_token_file VAULT_ROOT_TOKEN_FILE +\getenv cacert_file VAULT_CACERT_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','http://127.0.0.1:8200','DUMMY-TOKEN',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','https://127.0.0.1:8200','DUMMY-TOKEN',:'cacert_file'); -- FAILS SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); @@ -12,7 +13,7 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','http://127.0.0.1:8200','secret',NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','https://127.0.0.1:8200','secret',:'cacert_file'); SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); CREATE TABLE test_enc( @@ -32,9 +33,15 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); -- Changing provider fails if we can't connect to vault -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:61', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); + +-- HTTPS without cert fails +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:8200', 'secret', NULL); + +-- HTTP against HTTPS server fails +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:8200', 'secret', NULL); DROP EXTENSION pg_tde; From 4575c031f08964895743ad9b55dc1fdeabde1a07 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 4 Jun 2025 16:39:05 +0200 Subject: [PATCH 434/796] PG-1607 Encourage using HTTPS for Vault in our documentation We should encourage HTTPS over HTTP in out examples. --- .../docs/global-key-provider-configuration/vault.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index 71aaaf075a56d..9db21f325ec0f 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -31,9 +31,9 @@ The following example is for testing purposes only. Use secure tokens and proper SELECT pg_tde_add_global_key_provider_vault_v2( 'my-vault', '/path/to/token_file', - 'http://vault.vault.svc.cluster.local:8200', + 'https://vault.vault.svc.cluster.local:8200', 'secret/data', - NULL + '/path/to/ca_cert.pem' ); ``` From 7f940c52816467657a0d3dc37b707751590a17a1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 6 Jun 2025 14:49:34 +0200 Subject: [PATCH 435/796] Remove useless comments in extension SQL file These comments are repeated many times but add little to no value in practice since any such issues should be releaved by making sure we test all these functions. --- contrib/pg_tde/pg_tde--1.0-rc.sql | 33 ------------------------------- 1 file changed, 33 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index f4e91e1313fb0..6bc7c1d959036 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -14,8 +14,6 @@ CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_p RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -24,8 +22,6 @@ CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_p RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -38,8 +34,6 @@ CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, 'tokenPath' VALUE vault_token_path, @@ -56,8 +50,6 @@ CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_database_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -97,8 +89,6 @@ CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_pat RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -107,8 +97,6 @@ CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_pat RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -121,8 +109,6 @@ CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, 'tokenPath' VALUE vault_token_path, @@ -139,8 +125,6 @@ CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. SELECT pg_tde_add_global_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -160,8 +144,6 @@ CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, fil RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -170,8 +152,6 @@ CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, fil RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -184,8 +164,6 @@ CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, 'tokenPath' VALUE vault_token_path, @@ -202,8 +180,6 @@ CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_database_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -223,8 +199,6 @@ CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_ RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -233,8 +207,6 @@ CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_ RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_file_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('file', provider_name, json_object('path' VALUE file_path)); END; @@ -247,8 +219,6 @@ CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_vaultV2_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, 'tokenPath' VALUE vault_token_path, @@ -265,8 +235,6 @@ CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, RETURNS VOID LANGUAGE SQL BEGIN ATOMIC - -- JSON keys in the options must be matched to the keys in - -- load_kmip_keyring_provider_options function. SELECT pg_tde_change_global_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, @@ -299,7 +267,6 @@ LANGUAGE C AS 'MODULE_PATHNAME'; REVOKE ALL ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; - CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) RETURNS VOID AS 'MODULE_PATHNAME' From 2feea53fd6774a0c61093c1dcf39c5efb22efa7d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 6 Jun 2025 15:12:10 +0200 Subject: [PATCH 436/796] Clean up KeyringGenerateNewKey() Instead of returning NULL when we failed the generate a key we throw an error like all other places where random fails. Additionally we stop hardcoding 32 in the assertion. --- contrib/pg_tde/src/keyring/keyring_api.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 3d073ccd59721..618ff2da7a20e 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -10,6 +10,7 @@ #include #include +#include typedef struct RegisteredKeyProviderType { @@ -127,15 +128,15 @@ KeyringGenerateNewKey(const char *key_name, unsigned key_len) { KeyInfo *key; - Assert(key_len <= 32); + Assert(key_len <= sizeof(key->data)); /* Struct will be saved to disk so keep clean */ key = palloc0_object(KeyInfo); key->data.len = key_len; if (!RAND_bytes(key->data.data, key_len)) - { - pfree(key); - return NULL; /* openssl error */ - } + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate new principal key: %s", + ERR_error_string(ERR_get_error(), NULL))); strlcpy(key->name, key_name, sizeof(key->name)); return key; } From e636b10ea5b1f9201588a98e73eae85b0e711104 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 6 Jun 2025 16:05:00 +0200 Subject: [PATCH 437/796] Use OpenTransientFile() instead of BasicOpenFile() By using OpenTransientFile() we do not have to close file descriptors on error plus PostgreSQL will check if we have forgot to close any files on commit. This change made us find one instance where we had forgot to close a file which is also fixed in this commit. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 30 +++++++++++------------ contrib/pg_tde/src/catalog/tde_keyring.c | 20 ++++++--------- contrib/pg_tde/src/include/pg_tde_fe.h | 3 ++- contrib/pg_tde/src/keyring/keyring_file.c | 20 +++++++-------- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index d54784ce02959..0137b9e95ddc7 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -177,7 +177,7 @@ pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); map_fd = pg_tde_open_file_write(db_map_path, signed_key_info, false, &curr_pos); - close(map_fd); + CloseTransientFile(map_fd); LWLockRelease(tde_lwlock_enc_keys()); } @@ -216,7 +216,7 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) } map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, true, &curr_pos); - close(map_fd); + CloseTransientFile(map_fd); } /* @@ -365,7 +365,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re /* Write the given entry at curr_pos; i.e. the free entry. */ pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); - close(map_fd); + CloseTransientFile(map_fd); } /* @@ -410,7 +410,7 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) } } - close(map_fd); + CloseTransientFile(map_fd); LWLockRelease(tde_lwlock_enc_keys()); } @@ -490,8 +490,8 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p pfree(rel_key_data); } - close(old_fd); - close(new_fd); + CloseTransientFile(old_fd); + CloseTransientFile(new_fd); /* * Do the final steps - replace the current _map with the file with new @@ -589,7 +589,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) } LWLockRelease(lock_pk); - close(fd); + CloseTransientFile(fd); } /* @@ -649,7 +649,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, } } - close(map_fd); + CloseTransientFile(map_fd); return found; } @@ -688,7 +688,7 @@ pg_tde_count_relations(Oid dbOid) count++; } - close(map_fd); + CloseTransientFile(map_fd); LWLockRelease(lock_pk); @@ -764,7 +764,7 @@ pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_miss { int fd; - fd = BasicOpenFile(tde_filename, fileFlags); + fd = OpenTransientFile(tde_filename, fileFlags); if (fd < 0 && !(errno == ENOENT && ignore_missing == true)) { ereport(ERROR, @@ -792,7 +792,6 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader if (*bytes_read != TDE_FILE_HEADER_SIZE || fheader->file_version != PG_TDE_FILEMAGIC) { - close(fd); ereport(FATAL, errcode_for_file_access(), errmsg("TDE map file \"%s\" is corrupted: %m", tde_filename)); @@ -870,7 +869,7 @@ pg_tde_get_principal_key_info(Oid dbOid) pg_tde_file_header_read(db_map_path, fd, &fheader, &bytes_read); - close(fd); + CloseTransientFile(fd); /* * It's not a new file. So we can copy the principal key info from the @@ -1008,6 +1007,7 @@ pg_tde_read_last_wal_key(void) if (fsize == TDE_FILE_HEADER_SIZE) { LWLockRelease(lock_pk); + CloseTransientFile(fd); return NULL; } @@ -1016,7 +1016,7 @@ pg_tde_read_last_wal_key(void) rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); LWLockRelease(lock_pk); - close(fd); + CloseTransientFile(fd); return rel_key_data; } @@ -1064,7 +1064,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); LWLockRelease(lock_pk); - close(fd); + CloseTransientFile(fd); return wal_rec; } @@ -1094,7 +1094,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) } } LWLockRelease(lock_pk); - close(fd); + CloseTransientFile(fd); return return_wal_rec; } diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 6ddde83343fb1..742bb5d2f8ebe 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -476,13 +476,12 @@ save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId) if (strcmp(existing_provider.provider_name, provider->provider_name) == 0) { - close(fd); ereport(ERROR, errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("Key provider \"%s\" already exists.", provider->provider_name)); } } - close(fd); + CloseTransientFile(fd); if (max_provider_id == PG_INT32_MAX) { @@ -610,7 +609,7 @@ write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) Assert(LWLockHeldByMeInMode(tde_provider_info_lock(), LW_EXCLUSIVE)); get_keyring_infofile_path(kp_info_path, record->database_id); - fd = BasicOpenFile(kp_info_path, O_CREAT | O_RDWR | PG_BINARY); + fd = OpenTransientFile(kp_info_path, O_CREAT | O_RDWR | PG_BINARY); if (fd < 0) { ereport(ERROR, @@ -638,7 +637,6 @@ write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) record->offset_in_file); if (bytes_written != sizeof(KeyringProviderRecord)) { - close(fd); ereport(ERROR, errcode_for_file_access(), errmsg("key provider info file \"%s\" can't be written: %m", @@ -646,12 +644,11 @@ write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) } if (pg_fsync(fd) != 0) { - close(fd); ereport(ERROR, errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", kp_info_path)); } - close(fd); + CloseTransientFile(fd); } /* Returns true if the record is found, false otherwise. */ @@ -678,7 +675,7 @@ get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, record->database_id = database_id; record->offset_in_file = current_file_offset; record->provider = existing_provider; - close(fd); + CloseTransientFile(fd); return true; } @@ -686,7 +683,7 @@ get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, } /* No matching key provider found */ - close(fd); + CloseTransientFile(fd); return false; } @@ -750,7 +747,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) LWLockAcquire(tde_provider_info_lock(), LW_SHARED); - fd = BasicOpenFile(kp_info_path, PG_BINARY); + fd = OpenTransientFile(kp_info_path, PG_BINARY); if (fd < 0) { LWLockRelease(tde_provider_info_lock()); @@ -801,7 +798,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid) } } } - close(fd); + CloseTransientFile(fd); LWLockRelease(tde_provider_info_lock()); return providers_list; } @@ -994,7 +991,7 @@ open_keyring_infofile(Oid database_id, int flags) char kp_info_path[MAXPGPATH]; get_keyring_infofile_path(kp_info_path, database_id); - fd = BasicOpenFile(kp_info_path, flags | PG_BINARY); + fd = OpenTransientFile(kp_info_path, flags | PG_BINARY); if (fd < 0) { ereport(ERROR, @@ -1022,7 +1019,6 @@ fetch_next_key_provider(int fd, off_t *curr_pos, KeyringProviderRecord *provider return false; if (bytes_read != sizeof(KeyringProviderRecord)) { - close(fd); /* Corrupt file */ ereport(ERROR, errcode_for_file_access(), diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index 53269ea9123be..196457a052288 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -85,7 +85,8 @@ static int tde_fe_error_level = 0; #define LW_EXCLUSIVE NULL #define tde_lwlock_enc_keys() NULL -#define BasicOpenFile(fileName, fileFlags) open(fileName, fileFlags, PG_FILE_MODE_OWNER) +#define OpenTransientFile(fileName, fileFlags) open(fileName, fileFlags, PG_FILE_MODE_OWNER) +#define CloseTransientFile(fd) close(fd) #define AllocateFile(name, mode) fopen(name, mode) #define FreeFile(file) fclose(file) diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 5d1f83c1349ef..2166b8ef95442 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -53,7 +53,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode *return_code = KEYRING_CODE_SUCCESS; - fd = BasicOpenFile(file_keyring->file_name, PG_BINARY); + fd = OpenTransientFile(file_keyring->file_name, PG_BINARY); if (fd < 0) return NULL; @@ -69,13 +69,13 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode * Empty keyring file is considered as a valid keyring file that * has no keys */ - close(fd); + CloseTransientFile(fd); pfree(key); return NULL; } if (bytes_read != sizeof(KeyInfo)) { - close(fd); + CloseTransientFile(fd); pfree(key); /* Corrupt file */ *return_code = KEYRING_CODE_DATA_CORRUPTED; @@ -88,11 +88,11 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode } if (strncasecmp(key->name, key_name, sizeof(key->name)) == 0) { - close(fd); + CloseTransientFile(fd); return key; } } - close(fd); + CloseTransientFile(fd); pfree(key); return NULL; } @@ -116,7 +116,7 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) errmsg("Key with name %s already exists in keyring", key->name)); } - fd = BasicOpenFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); + fd = OpenTransientFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); if (fd < 0) { ereport(ERROR, @@ -128,7 +128,6 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) bytes_written = pg_pwrite(fd, key, sizeof(KeyInfo), curr_pos); if (bytes_written != sizeof(KeyInfo)) { - close(fd); ereport(ERROR, errcode_for_file_access(), errmsg("keyring file \"%s\" can't be written: %m", @@ -137,20 +136,19 @@ set_key_by_name(GenericKeyring *keyring, KeyInfo *key) if (pg_fsync(fd) != 0) { - close(fd); ereport(ERROR, errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", file_keyring->file_name)); } - close(fd); + CloseTransientFile(fd); } static void validate(GenericKeyring *keyring) { FileKeyring *file_keyring = (FileKeyring *) keyring; - int fd = BasicOpenFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); + int fd = OpenTransientFile(file_keyring->file_name, O_CREAT | O_RDWR | PG_BINARY); if (fd < 0) { @@ -159,5 +157,5 @@ validate(GenericKeyring *keyring) errmsg("Failed to open keyring file %s: %m", file_keyring->file_name)); } - close(fd); + CloseTransientFile(fd); } From 23b15bdee6a841ab3a670ad1fcb178b092e8e60e Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 9 Jun 2025 12:29:14 +0300 Subject: [PATCH 438/796] Created new Thales KMIP topic for TDE (#370) Added Thales integration, initial documentation, to TDE. --- .../docs/global-key-provider-configuration/kmip-thales.md | 5 +++++ contrib/pg_tde/documentation/mkdocs.yml | 1 + 2 files changed, 6 insertions(+) create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md new file mode 100644 index 0000000000000..daf6bd5e16039 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md @@ -0,0 +1,5 @@ +# Thales KMIP Server Configuration + +`pg_tde` is compatible with the Thales CipherTrust Manager via the KMIP protocol. For a full setup guide, see [the Thales documentation](https://thalesdocs.com/ctp/cm/2.19/reference/kmip-ref/index.html?). + +For more information on adding or modifying the provider, see the [Adding or modifying KMIP providers](https://docs.percona.com/pg-tde/functions.html?h=pg_tde_add_global_key_provider_kmip#adding-or-modifying-kmip-providers) topic. diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index b087c856aca9d..cd171b378980a 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -171,6 +171,7 @@ nav: - "2.1 Configure Key Management (KMS)": global-key-provider-configuration/index.md - "KMIP Configuration": global-key-provider-configuration/kmip-server.md - "Vault Configuration": global-key-provider-configuration/vault.md + - "Thales Configuration": global-key-provider-configuration/kmip-thales.md - "Keyring File Configuration": global-key-provider-configuration/keyring.md - "2.2 Global Principal Key Configuration": global-key-provider-configuration/set-principal-key.md - "3. Validate Encryption with pg_tde": test.md From 05294247c09671dc5c87135c2053cc08e5720c10 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 9 Jun 2025 13:07:50 +0300 Subject: [PATCH 439/796] Created new Fortanix KMIP topic for TDE (#368) Since we can use Fortanix, I added it to the config KMIP section, it tells the user where to look for more details on setting it up and provides a bit of examples with pg_tde_add_global_key_provider_kmip --- .../docs/global-key-provider-configuration/kmip-fortanix.md | 5 +++++ contrib/pg_tde/documentation/mkdocs.yml | 1 + 2 files changed, 6 insertions(+) create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md new file mode 100644 index 0000000000000..edc09daf51af8 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md @@ -0,0 +1,5 @@ +# Fortanix KMIP Server Configuration + +`pg_tde` is compatible with Fortanix Data Security Manager (DSM) via the KMIP protocol. For a full setup guide, see [the Fortanix KMIP documentation here](https://support.fortanix.com/docs/users-guide-account-client-configurations?highlight=KMIP#23-kmip-clients). + +For more information on adding or modifying the provider, see the [Adding or modifying KMIP providers](https://docs.percona.com/pg-tde/functions.html?h=pg_tde_add_global_key_provider_kmip#adding-or-modifying-kmip-providers) topic. diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index cd171b378980a..ab8c6e25236ff 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -170,6 +170,7 @@ nav: - "2.1 Configure Key Management (KMS)": - "2.1 Configure Key Management (KMS)": global-key-provider-configuration/index.md - "KMIP Configuration": global-key-provider-configuration/kmip-server.md + - "Fortanix Configuration": global-key-provider-configuration/kmip-fortanix.md - "Vault Configuration": global-key-provider-configuration/vault.md - "Thales Configuration": global-key-provider-configuration/kmip-thales.md - "Keyring File Configuration": global-key-provider-configuration/keyring.md From 964c78d6527db11fcb5d8a5fe2fe41bb788fb8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 5 Jun 2025 11:05:46 +0200 Subject: [PATCH 440/796] Make tde_mdcreate more strict in it's behavior Only create keys when MAIN fork is created, and trust tde_smgr_should_encrypt() to know when to encrypt. Also trust that the key has already been created if we're in recovery or replication. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 49 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index cf3de6cd5bcef..bed0d8e5fb61f 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -322,6 +322,7 @@ static void tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool isRedo) { TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; + InternalKey *key; /* Copied from mdcreate() in md.c */ if (isRedo && tdereln->md_num_open_segs[forknum] > 0) @@ -334,36 +335,46 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool mdcreate(relold, reln, forknum, isRedo); - if (forknum == MAIN_FORKNUM || forknum == INIT_FORKNUM) + if (forknum != MAIN_FORKNUM) { /* - * Only create keys when creating the main/init fork. Other forks can - * be created later, even during tde creation events. We definitely do + * Only create keys when creating the main fork. Other forks can be + * created later, even during tde creation events. We definitely do * not want to create keys then, even later, when we encrypt all * forks! * * Later calls then decide to encrypt or not based on the existence of * the key. - * - * Since event triggers do not fire on the standby or in recovery we - * do not try to generate any new keys and instead trust the xlog. */ - InternalKey *key = tde_smgr_get_key(&reln->smgr_rlocator); + return; + } - if (!isRedo && !key && tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) - key = tde_smgr_create_key(&reln->smgr_rlocator); + if (!tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) + { + tdereln->encryption_status = RELATION_NOT_ENCRYPTED; + return; + } - if (key) - { - tdereln->encryption_status = RELATION_KEY_AVAILABLE; - tdereln->relKey = *key; - pfree(key); - } - else - { - tdereln->encryption_status = RELATION_NOT_ENCRYPTED; - } + if (isRedo) + { + /* + * If we're in redo, the WAL record for creating the key has already + * happened and we can just fetch it. + */ + key = tde_smgr_get_key(&reln->smgr_rlocator); + + Assert(key); + if (!key) + elog(ERROR, "could not get key when creating encrypted relation"); } + else + { + key = tde_smgr_create_key(&reln->smgr_rlocator); + } + + tdereln->encryption_status = RELATION_KEY_AVAILABLE; + tdereln->relKey = *key; + pfree(key); } /* From eab9bd0ec53465b32307b2dba601ea3e4ad943d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 9 Jun 2025 11:58:38 +0200 Subject: [PATCH 441/796] Move WAL logging of relation key creation to smgr Instead of it being done down in tdemap code controlled with a boolean, we just do it where the decision to do it is made instead. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 16 +--------------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 0137b9e95ddc7..eb145194288a5 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -76,13 +76,10 @@ static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); void -pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data, bool write_xlog) +pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) { TDEPrincipalKey *principal_key; LWLock *lock_pk = tde_lwlock_enc_keys(); - XLogRelKey xlrec = { - .rlocator = rel, - }; LWLockAcquire(lock_pk, LW_EXCLUSIVE); principal_key = GetPrincipalKey(rel.dbOid, LW_EXCLUSIVE); @@ -95,17 +92,6 @@ pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data, bool w pg_tde_write_key_map_entry(&rel, rel_key_data, principal_key); LWLockRelease(lock_pk); - - if (write_xlog) - { - /* - * It is fine to write the to WAL after writing to the file since we - * have not WAL logged the SMGR CREATE event either. - */ - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); - } } const char * diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 471840554093e..dcf834c995fb3 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -93,7 +93,7 @@ pg_tde_set_db_file_path(Oid dbOid, char *path) join_path_components(path, pg_tde_get_data_dir(), psprintf(PG_TDE_MAP_FILENAME, dbOid)); } -extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key, bool write_xlog); +extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key); extern bool pg_tde_has_smgr_key(RelFileLocator rel); extern InternalKey *pg_tde_get_smgr_key(RelFileLocator rel); extern void pg_tde_free_key_map_entry(RelFileLocator rel); diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index bed0d8e5fb61f..67e44e9d36786 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -3,7 +3,9 @@ #include "smgr/pg_tde_smgr.h" #include "storage/smgr.h" #include "storage/md.h" +#include "access/xloginsert.h" #include "catalog/catalog.h" +#include "access/pg_tde_xlog.h" #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" #include "access/pg_tde_tdemap.h" @@ -77,11 +79,23 @@ tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) tde_smgr_save_temp_key(&smgr_rlocator->locator, key); else - pg_tde_save_smgr_key(smgr_rlocator->locator, key, true); + pg_tde_save_smgr_key(smgr_rlocator->locator, key); return key; } +static void +tde_smgr_log_create_key(const RelFileLocatorBackend *smgr_rlocator) +{ + XLogRelKey xlrec = { + .rlocator = smgr_rlocator->locator, + }; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); +} + void tde_smgr_create_key_redo(const RelFileLocator *rlocator) { @@ -92,7 +106,7 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) pg_tde_generate_internal_key(&key, TDE_KEY_TYPE_SMGR); - pg_tde_save_smgr_key(*rlocator, &key, false); + pg_tde_save_smgr_key(*rlocator, &key); } static bool @@ -370,6 +384,7 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool else { key = tde_smgr_create_key(&reln->smgr_rlocator); + tde_smgr_log_create_key(&reln->smgr_rlocator); } tdereln->encryption_status = RELATION_KEY_AVAILABLE; From 27d05ddb6e8e08df3a2dd31d065d89e18887e6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 9 Jun 2025 12:20:02 +0200 Subject: [PATCH 442/796] Delete relation keys before create There are crash scenarios where keys are left behind in the key file even though the OID for the table goes unused. This meant that we could have keys laying around for newly created plaintext relations after OID wraparound. Simply removing any existing keys when relations are created seems appriopriate. Also move creation of pg_tde data dir to library init. This directory is used by the SMgr which is loaded regardless of whether any database are yet to create extension or not. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 6 ++++ .../pg_tde/src/include/access/pg_tde_xlog.h | 1 + contrib/pg_tde/src/include/smgr/pg_tde_smgr.h | 1 + contrib/pg_tde/src/pg_tde.c | 3 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 33 +++++++++++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 0d0581d0604a3..43407ee3698f6 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -61,6 +61,12 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_save_principal_key_redo(mkey); } + else if (info == XLOG_TDE_REMOVE_RELATION_KEY) + { + XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); + + tde_smgr_delete_key_redo(&xlrec->rlocator); + } else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { XLogPrincipalKeyRotate *xlrec = (XLogPrincipalKeyRotate *) XLogRecGetData(record); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 3938ae7763792..2f08fecb37162 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -17,6 +17,7 @@ #define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x20 #define XLOG_TDE_WRITE_KEY_PROVIDER 0x30 #define XLOG_TDE_INSTALL_EXTENSION 0x40 +#define XLOG_TDE_REMOVE_RELATION_KEY 0x50 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 diff --git a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h index e0f09efc4c39b..31a0d525f4fe6 100644 --- a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h +++ b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h @@ -14,6 +14,7 @@ extern void RegisterStorageMgr(void); extern void tde_smgr_create_key_redo(const RelFileLocator *rlocator); +extern void tde_smgr_delete_key_redo(const RelFileLocator *rlocator); extern bool tde_smgr_rel_is_encrypted(SMgrRelation reln); #endif /* PG_TDE_SMGR_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 3127c2c4cff09..410c7ce7cb679 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -93,6 +93,7 @@ _PG_init(void) check_percona_api_version(); + pg_tde_init_data_dir(); AesInit(); TdeGucInit(); TdeEventCaptureInit(); @@ -113,8 +114,6 @@ _PG_init(void) static void extension_install(Oid databaseId) { - /* Initialize the TDE dir */ - pg_tde_init_data_dir(); key_provider_startup_cleanup(databaseId); principal_key_startup_cleanup(databaseId); } diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 67e44e9d36786..476a30369e16d 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -109,6 +109,26 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) pg_tde_save_smgr_key(*rlocator, &key); } +static void +tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) +{ + XLogRelKey xlrec = { + .rlocator = smgr_rlocator->locator, + }; + + pg_tde_free_key_map_entry(smgr_rlocator->locator); + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_REMOVE_RELATION_KEY); +} + +void +tde_smgr_delete_key_redo(const RelFileLocator *rlocator) +{ + pg_tde_free_key_map_entry(*rlocator); +} + static bool tde_smgr_is_encrypted(const RelFileLocatorBackend *smgr_rlocator) { @@ -363,6 +383,19 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool return; } + if (!isRedo) + { + /* + * If we have a key for this relation already, we need to remove it. + * This can happen if OID is re-used after a crash left a key for a + * non-existing relation in the key file. + * + * If we're in redo, a separate WAL record will make sure the key is + * removed. + */ + tde_smgr_delete_key(&reln->smgr_rlocator); + } + if (!tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) { tdereln->encryption_status = RELATION_NOT_ENCRYPTED; From 9a78833f108f87034701fa9e28dde584e8f7dd5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 9 Jun 2025 16:58:31 +0200 Subject: [PATCH 443/796] Remove add/change file provider with json fragment These seem to have been overlooked in 75aad06e5678d1c3bf905a680e5539f1537b45d7 when similar functions for kmip and vault were removed. --- contrib/pg_tde/expected/key_provider.out | 4 ++-- contrib/pg_tde/pg_tde--1.0-rc.sql | 29 ------------------------ contrib/pg_tde/sql/key_provider.sql | 4 ++-- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 89d115e51f59f..e3ef0dd2e4aa1 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -5,7 +5,7 @@ SELECT * FROM pg_tde_key_info(); | | | (1 row) -SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_add_database_key_provider('file', 'incorrect-file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); ERROR: key provider value cannot be an object SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file @@ -51,7 +51,7 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) -SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); ERROR: key provider value cannot be an object SELECT * FROM pg_tde_list_all_database_key_providers(); id | provider_name | provider_type | options diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 6bc7c1d959036..35f009877fa01 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -18,13 +18,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_add_database_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - SELECT pg_tde_add_database_key_provider('file', provider_name, - json_object('path' VALUE file_path)); -END; CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, @@ -93,13 +86,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_add_global_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - SELECT pg_tde_add_global_key_provider('file', provider_name, - json_object('path' VALUE file_path)); -END; CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, @@ -148,14 +134,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_change_database_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - SELECT pg_tde_change_database_key_provider('file', provider_name, - json_object('path' VALUE file_path)); -END; - CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, vault_url TEXT, @@ -203,13 +181,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; -CREATE FUNCTION pg_tde_change_global_key_provider_file(provider_name TEXT, file_path JSON) -RETURNS VOID -LANGUAGE SQL -BEGIN ATOMIC - SELECT pg_tde_change_global_key_provider('file', provider_name, - json_object('path' VALUE file_path)); -END; CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 819da4d61cb6b..008858d8944a0 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -2,7 +2,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); -SELECT pg_tde_add_database_key_provider_file('incorrect-file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_add_database_key_provider('file', 'incorrect-file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_keyring2.per'); SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_dup.per'); @@ -15,7 +15,7 @@ SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); -SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per')); +SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); SELECT * FROM pg_tde_list_all_database_key_providers(); SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); From f3500d388b266d4dd99668d02daa45ee9cbd3a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 10 Jun 2025 10:25:56 +0200 Subject: [PATCH 444/796] Remove tripple newlines in sql file These were inadvertently left behind by 9a78833f108f87034701fa9e28dde584e8f7dd5d --- contrib/pg_tde/pg_tde--1.0-rc.sql | 3 --- 1 file changed, 3 deletions(-) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 35f009877fa01..cc92bbe217b92 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -18,7 +18,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; - CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, vault_url TEXT, @@ -86,7 +85,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; - CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, vault_url TEXT, @@ -181,7 +179,6 @@ BEGIN ATOMIC json_object('path' VALUE file_path)); END; - CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, vault_token_path TEXT, vault_url TEXT, From 385207549003328dba06990606db2d41306a3a34 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:39:14 +0200 Subject: [PATCH 445/796] Make inlined hash table lookup actually make sense There was no point in having the function for attaching the the shared memory hash table be inline if the non-inlined code had to be called every time anyway. We still maybe should just stop using an explicit inline since this is unlikely to be that performace critical code but at least if we use it we should use it correctly. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index da4e88cc2133f..744b4dd611e84 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -200,9 +200,6 @@ principal_key_info_attach_shmem(void) MemoryContext oldcontext; dsa_area *dsa; - if (principalKeyLocalState.sharedHash) - return; - /* * We want the dsa to remain valid throughout the lifecycle of this * process. so switch to TopMemoryContext before attaching @@ -374,7 +371,8 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) static inline dshash_table * get_principal_key_Hash(void) { - principal_key_info_attach_shmem(); + if (!principalKeyLocalState.sharedHash) + principal_key_info_attach_shmem(); return principalKeyLocalState.sharedHash; } From 8084e6a35a114ddadb91d7a6a03736af5538b93f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 18:39:21 +0200 Subject: [PATCH 446/796] Use consistent casing of get_principal_key_hash() There was no good reason to have an upper case H. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 744b4dd611e84..e115bfb7b4097 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -87,7 +87,7 @@ static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area); static Size required_shared_mem_size(void); static void shared_memory_shutdown(int code, Datum arg); static void clear_principal_key_cache(Oid databaseId); -static inline dshash_table *get_principal_key_Hash(void); +static inline dshash_table *get_principal_key_hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); static bool pg_tde_is_same_principal_key(TDEPrincipalKey *a, TDEPrincipalKey *b); static void pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey); @@ -369,7 +369,7 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) */ static inline dshash_table * -get_principal_key_Hash(void) +get_principal_key_hash(void) { if (!principalKeyLocalState.sharedHash) principal_key_info_attach_shmem(); @@ -384,10 +384,10 @@ get_principal_key_from_cache(Oid dbOid) { TDEPrincipalKey *cacheEntry = NULL; - cacheEntry = (TDEPrincipalKey *) dshash_find(get_principal_key_Hash(), + cacheEntry = (TDEPrincipalKey *) dshash_find(get_principal_key_hash(), &dbOid, false); if (cacheEntry) - dshash_release_lock(get_principal_key_Hash(), cacheEntry); + dshash_release_lock(get_principal_key_hash(), cacheEntry); return cacheEntry; } @@ -409,12 +409,12 @@ push_principal_key_to_cache(TDEPrincipalKey *principalKey) Oid databaseId = principalKey->keyInfo.databaseId; bool found = false; - cacheEntry = dshash_find_or_insert(get_principal_key_Hash(), + cacheEntry = dshash_find_or_insert(get_principal_key_hash(), &databaseId, &found); if (!found) *cacheEntry = *principalKey; - dshash_release_lock(get_principal_key_Hash(), cacheEntry); + dshash_release_lock(get_principal_key_hash(), cacheEntry); /* we don't want principal keys to end up paged to the swap */ if (mlock(cacheEntry, sizeof(TDEPrincipalKey)) == -1) @@ -445,11 +445,11 @@ clear_principal_key_cache(Oid databaseId) TDEPrincipalKey *cache_entry; /* Start with deleting the cache entry for the database */ - cache_entry = (TDEPrincipalKey *) dshash_find(get_principal_key_Hash(), + cache_entry = (TDEPrincipalKey *) dshash_find(get_principal_key_hash(), &databaseId, true); if (cache_entry) { - dshash_delete_entry(get_principal_key_Hash(), cache_entry); + dshash_delete_entry(get_principal_key_hash(), cache_entry); } } From 1749d8ccd8134c590d69c5bc7f1937cc3815604b Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:53:08 +0200 Subject: [PATCH 447/796] Sort header includes in a constent order Inspired by how PostgreSQL does it we support our includes in the following order: 1. postgres.h 2. System headers 3. PostgreSQL headers 4. pg_tde headers 5. Frontend/backend specific headers Within every section we sort includes in alphabetical order where possible, which right now was everywhere except in keyring_kmip_impl.c. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 22 ++++++------ contrib/pg_tde/src/access/pg_tde_xlog.c | 6 ++-- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 6 ++-- contrib/pg_tde/src/catalog/tde_keyring.c | 28 ++++++++------- .../src/catalog/tde_keyring_parse_opts.c | 1 + .../pg_tde/src/catalog/tde_principal_key.c | 34 +++++++++--------- contrib/pg_tde/src/common/pg_tde_shmem.c | 6 ++-- contrib/pg_tde/src/common/pg_tde_utils.c | 4 +-- contrib/pg_tde/src/encryption/enc_aes.c | 23 ++++++------ contrib/pg_tde/src/encryption/enc_tde.c | 12 +++---- contrib/pg_tde/src/include/pg_tde_fe.h | 2 +- contrib/pg_tde/src/keyring/keyring_api.c | 14 ++++---- contrib/pg_tde/src/keyring/keyring_file.c | 13 +++---- contrib/pg_tde/src/keyring/keyring_kmip.c | 2 +- .../pg_tde/src/keyring/keyring_kmip_impl.c | 1 + contrib/pg_tde/src/keyring/keyring_vault.c | 17 +++++---- contrib/pg_tde/src/pg_tde.c | 30 ++++++++-------- .../pg_tde/src/pg_tde_change_key_provider.c | 13 +++---- contrib/pg_tde/src/pg_tde_event_capture.c | 36 ++++++++++--------- contrib/pg_tde/src/pg_tde_guc.c | 4 ++- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 11 +++--- 21 files changed, 151 insertions(+), 134 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index eb145194288a5..3ab258ea05ec3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1,14 +1,18 @@ #include "postgres.h" -#include "access/pg_tde_tdemap.h" -#include "common/file_perm.h" -#include "storage/fd.h" -#include "utils/wait_event.h" -#include "utils/memutils.h" + +#include +#include +#include + #include "access/xlog.h" #include "access/xlog_internal.h" #include "access/xloginsert.h" -#include "utils/builtins.h" +#include "common/file_perm.h" #include "miscadmin.h" +#include "storage/fd.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/wait_event.h" #include "access/pg_tde_tdemap.h" #include "access/pg_tde_xlog.h" @@ -18,12 +22,6 @@ #include "encryption/enc_tde.h" #include "keyring/keyring_api.h" -#include -#include -#include - -#include "pg_tde_defines.h" - #ifdef FRONTEND #include "pg_tde_fe.h" #endif diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 43407ee3698f6..14015f32b8d1e 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -12,19 +12,19 @@ #include "postgres.h" -#include "pg_tde.h" -#include "pg_tde_defines.h" #include "access/xlog.h" #include "access/xlog_internal.h" #include "access/xloginsert.h" -#include "catalog/tde_keyring.h" #include "storage/bufmgr.h" #include "storage/shmem.h" #include "utils/guc.h" #include "utils/memutils.h" #include "access/pg_tde_xlog.h" +#include "catalog/tde_keyring.h" #include "encryption/enc_tde.h" +#include "pg_tde.h" +#include "pg_tde_defines.h" #include "smgr/pg_tde_smgr.h" static void tdeheap_rmgr_redo(XLogReaderState *record); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 1f331e18e6484..aec73ba3612ef 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -12,9 +12,6 @@ #include "postgres.h" -#include "pg_tde.h" -#include "pg_tde_defines.h" -#include "pg_tde_guc.h" #include "access/xlog.h" #include "access/xlog_internal.h" #include "access/xlog_smgr.h" @@ -28,6 +25,9 @@ #include "access/pg_tde_xlog_smgr.h" #include "catalog/tde_global_space.h" #include "encryption/enc_tde.h" +#include "pg_tde.h" +#include "pg_tde_defines.h" +#include "pg_tde_guc.h" #ifdef FRONTEND #include "pg_tde_fe.h" diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 742bb5d2f8ebe..c5aee34e377bd 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -9,33 +9,37 @@ * *------------------------------------------------------------------------- */ + #include "postgres.h" + +#include + +#include "access/skey.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" + #include "access/pg_tde_xlog.h" #include "catalog/tde_global_space.h" #include "catalog/tde_keyring.h" #include "catalog/tde_principal_key.h" -#include "access/skey.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/snapmgr.h" -#include "utils/fmgroids.h" #include "common/pg_tde_utils.h" -#include "miscadmin.h" -#include "storage/fd.h" -#include "unistd.h" -#include "utils/builtins.h" #include "pg_tde.h" #ifndef FRONTEND #include "access/heapam.h" -#include "common/pg_tde_shmem.h" -#include "funcapi.h" -#include "access/relscan.h" #include "access/relation.h" +#include "access/relscan.h" #include "catalog/namespace.h" #include "executor/spi.h" +#include "funcapi.h" +#include "common/pg_tde_shmem.h" #else #include "fe_utils/simple_list.h" #include "pg_tde_fe.h" diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index 7414da764f27d..f9e8a1db87d85 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -12,6 +12,7 @@ */ #include "postgres.h" + #include "common/jsonapi.h" #include "mb/pg_wchar.h" #include "utils/jsonfuncs.h" diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index e115bfb7b4097..21d7156375214 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -9,43 +9,45 @@ * *------------------------------------------------------------------------- */ + #include "postgres.h" + +#include +#include + #include "access/xlog.h" #include "access/xloginsert.h" -#include "catalog/tde_principal_key.h" -#include "storage/fd.h" -#include "utils/palloc.h" -#include "utils/memutils.h" -#include "utils/wait_event.h" -#include "utils/timestamp.h" +#include "catalog/pg_database.h" #include "common/relpath.h" #include "miscadmin.h" +#include "storage/fd.h" #include "utils/builtins.h" -#include "pg_tde.h" -#include "access/pg_tde_xlog.h" -#include -#include #include "utils/fmgroids.h" #include "utils/guc.h" -#include "catalog/pg_database.h" -#include "keyring/keyring_api.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "utils/timestamp.h" +#include "utils/wait_event.h" #include "access/pg_tde_tdemap.h" +#include "access/pg_tde_xlog.h" #include "catalog/tde_global_space.h" +#include "catalog/tde_principal_key.h" +#include "keyring/keyring_api.h" +#include "pg_tde.h" +#include "pg_tde_guc.h" + #ifndef FRONTEND #include "access/genam.h" #include "access/table.h" -#include "common/pg_tde_shmem.h" #include "funcapi.h" #include "lib/dshash.h" #include "storage/lwlock.h" #include "storage/shmem.h" +#include "common/pg_tde_shmem.h" #else #include "pg_tde_fe.h" #endif -#include "pg_tde_guc.h" - -#include #ifndef FRONTEND diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c index 6bd4c9578d617..697a3e23b6843 100644 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ b/contrib/pg_tde/src/common/pg_tde_shmem.c @@ -10,13 +10,15 @@ */ #include "postgres.h" -#include "storage/ipc.h" -#include "common/pg_tde_shmem.h" + #include "lib/dshash.h" #include "nodes/pg_list.h" +#include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/shmem.h" +#include "common/pg_tde_shmem.h" + static void tde_shmem_shutdown(int code, Datum arg); List *registeredShmemRequests = NIL; diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index e092557f90c90..4a6602ce05f3e 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -15,10 +15,10 @@ #include "pg_tde.h" #ifndef FRONTEND -#include "fmgr.h" -#include "smgr/pg_tde_smgr.h" #include "access/relation.h" +#include "fmgr.h" #include "utils/rel.h" +#include "smgr/pg_tde_smgr.h" PG_FUNCTION_INFO_V1(pg_tde_is_encrypted); Datum diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index 003f6050112c2..650645168463b 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -1,22 +1,21 @@ #include "postgres.h" -#ifdef FRONTEND -#include "pg_tde_fe.h" -#endif - -#include "encryption/enc_aes.h" - +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include +#include "encryption/enc_aes.h" + +#ifdef FRONTEND +#include "pg_tde_fe.h" +#endif /* Implementation notes * ===================== diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index 2676efb92aac8..f5324741f0efa 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -1,19 +1,19 @@ -#include "pg_tde_defines.h" - #include "postgres.h" +#include +#include + +#include "storage/bufmgr.h" + #include "access/pg_tde_tdemap.h" #include "encryption/enc_tde.h" #include "encryption/enc_aes.h" -#include "storage/bufmgr.h" +#include "pg_tde_defines.h" #ifdef FRONTEND #include "pg_tde_fe.h" #endif -#include -#include - #define AES_BLOCK_SIZE 16 #define NUM_AES_BLOCKS_IN_BATCH 200 #define DATA_BYTES_PER_AES_BATCH (NUM_AES_BLOCKS_IN_BATCH * AES_BLOCK_SIZE) diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index 196457a052288..79c1c4f4fa72e 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -13,9 +13,9 @@ #ifdef FRONTEND #include "postgres_fe.h" -#include "utils/elog.h" #include "common/logging.h" #include "common/file_perm.h" +#include "utils/elog.h" #pragma GCC diagnostic ignored "-Wunused-macros" #pragma GCC diagnostic ignored "-Wunused-value" diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 618ff2da7a20e..4af3c82130db7 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -1,17 +1,19 @@ -#include "keyring/keyring_api.h" - #include "postgres.h" + +#include +#include +#include + #include "nodes/pg_list.h" #include "utils/memutils.h" + +#include "keyring/keyring_api.h" + #ifdef FRONTEND #include "fe_utils/simple_list.h" #include "pg_tde_fe.h" #endif -#include -#include -#include - typedef struct RegisteredKeyProviderType { TDEKeyringRoutine *routine; diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 2166b8ef95442..391b6dae91d01 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -12,20 +12,21 @@ #include "postgres.h" -#include "keyring/keyring_file.h" -#include "catalog/tde_keyring.h" +#include +#include + #include "common/file_perm.h" -#include "keyring/keyring_api.h" #include "storage/fd.h" #include "utils/wait_event.h" +#include "catalog/tde_keyring.h" +#include "keyring/keyring_api.h" +#include "keyring/keyring_file.h" + #ifdef FRONTEND #include "pg_tde_fe.h" #endif -#include -#include - static KeyInfo *get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCodes *return_code); static void set_key_by_name(GenericKeyring *keyring, KeyInfo *key); static void validate(GenericKeyring *keyring); diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index bcec54258187b..12dbcab8d163d 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -14,9 +14,9 @@ #include #include +#include "keyring/keyring_api.h" #include "keyring/keyring_kmip.h" #include "keyring/keyring_kmip_impl.h" -#include "keyring/keyring_api.h" #ifdef FRONTEND #include "pg_tde_fe.h" diff --git a/contrib/pg_tde/src/keyring/keyring_kmip_impl.c b/contrib/pg_tde/src/keyring/keyring_kmip_impl.c index 086373e4c826e..54a5fba89d281 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip_impl.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip_impl.c @@ -2,6 +2,7 @@ * The libkmip specific code need to be in a separate library to avoid * collissions with PostgreSQL's header files. */ + #include #include #include diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 1c01357899208..4d896e5ffb675 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -11,19 +11,18 @@ #include "postgres.h" -#include "keyring/keyring_vault.h" -#include "keyring/keyring_curl.h" -#include "keyring/keyring_api.h" -#include "pg_tde_defines.h" +#include +#include + +#include "common/base64.h" #include "common/jsonapi.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" -#include - -#include - -#include "common/base64.h" +#include "keyring/keyring_api.h" +#include "keyring/keyring_curl.h" +#include "keyring/keyring_vault.h" +#include "pg_tde_defines.h" #ifdef FRONTEND #include "pg_tde_fe.h" diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 410c7ce7cb679..f38ad795609c4 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -11,33 +11,35 @@ */ #include "postgres.h" + +#include + +#include "access/tableam.h" +#include "access/xlog.h" +#include "access/xloginsert.h" #include "funcapi.h" -#include "pg_tde.h" #include "miscadmin.h" #include "storage/ipc.h" #include "storage/lwlock.h" #include "storage/shmem.h" +#include "utils/builtins.h" +#include "utils/percona.h" + +#include "access/pg_tde_tdemap.h" #include "access/pg_tde_xlog.h" #include "access/pg_tde_xlog_smgr.h" +#include "catalog/tde_global_space.h" +#include "catalog/tde_principal_key.h" +#include "common/pg_tde_shmem.h" #include "encryption/enc_aes.h" -#include "access/pg_tde_tdemap.h" -#include "access/xlog.h" -#include "access/xloginsert.h" #include "keyring/keyring_api.h" -#include "common/pg_tde_shmem.h" -#include "catalog/tde_principal_key.h" #include "keyring/keyring_file.h" -#include "keyring/keyring_vault.h" #include "keyring/keyring_kmip.h" -#include "utils/builtins.h" -#include "smgr/pg_tde_smgr.h" -#include "catalog/tde_global_space.h" +#include "keyring/keyring_vault.h" +#include "pg_tde.h" #include "pg_tde_event_capture.h" -#include "utils/percona.h" #include "pg_tde_guc.h" -#include "access/tableam.h" - -#include +#include "smgr/pg_tde_smgr.h" PG_MODULE_MAGIC; diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 341c182e93e51..79d69ae7ccbf0 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -1,14 +1,15 @@ #include "postgres_fe.h" -#include "pg_tde.h" -#include "catalog/tde_keyring.h" -#include "catalog/tde_global_space.h" +#include +#include + #include "common/controldata_utils.h" #include "common/logging.h" -#include "common/pg_tde_utils.h" -#include -#include +#include "catalog/tde_global_space.h" +#include "catalog/tde_keyring.h" +#include "common/pg_tde_utils.h" +#include "pg_tde.h" /* version string we expect back from pg_tde_change_key_provider */ #define PROGNAME "pg_tde_change_key_provider (PostgreSQL) " PG_VERSION "\n" diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 74cb36bb88a1c..fef9cf2b31d0d 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -10,34 +10,36 @@ */ #include "postgres.h" -#include "funcapi.h" -#include "fmgr.h" -#include "utils/rel.h" -#include "utils/builtins.h" -#include "utils/lsyscache.h" + +#include "access/heapam.h" +#include "access/relation.h" +#include "access/table.h" +#include "access/tableam.h" +#include "catalog/namespace.h" #include "catalog/pg_class.h" #include "catalog/pg_database.h" +#include "catalog/pg_event_trigger.h" #include "catalog/pg_inherits.h" #include "commands/defrem.h" -#include "commands/sequence.h" -#include "access/heapam.h" -#include "access/table.h" -#include "access/relation.h" -#include "catalog/pg_event_trigger.h" -#include "catalog/namespace.h" #include "commands/event_trigger.h" -#include "common/pg_tde_utils.h" +#include "commands/sequence.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" #include "storage/lmgr.h" #include "tcop/utility.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" -#include "pg_tde_event_capture.h" -#include "pg_tde_guc.h" + #include "access/pg_tde_tdemap.h" -#include "catalog/tde_principal_key.h" -#include "miscadmin.h" -#include "access/tableam.h" #include "catalog/tde_global_space.h" +#include "catalog/tde_principal_key.h" +#include "common/pg_tde_utils.h" +#include "pg_tde_event_capture.h" +#include "pg_tde_guc.h" typedef struct { diff --git a/contrib/pg_tde/src/pg_tde_guc.c b/contrib/pg_tde/src/pg_tde_guc.c index 5e11c3d21e9a6..8b82c3338bc71 100644 --- a/contrib/pg_tde/src/pg_tde_guc.c +++ b/contrib/pg_tde/src/pg_tde_guc.c @@ -10,10 +10,12 @@ *------------------------------------------------------------------------- */ -#include "pg_tde_guc.h" #include "postgres.h" + #include "utils/guc.h" +#include "pg_tde_guc.h" + #ifndef FRONTEND bool AllowInheritGlobalProviders = true; diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 476a30369e16d..12f68bc555d3d 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -1,16 +1,17 @@ #include "postgres.h" -#include "smgr/pg_tde_smgr.h" -#include "storage/smgr.h" -#include "storage/md.h" #include "access/xloginsert.h" #include "catalog/catalog.h" +#include "storage/md.h" +#include "storage/smgr.h" +#include "utils/hsearch.h" + +#include "access/pg_tde_tdemap.h" #include "access/pg_tde_xlog.h" #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" -#include "access/pg_tde_tdemap.h" -#include "utils/hsearch.h" #include "pg_tde_event_capture.h" +#include "smgr/pg_tde_smgr.h" typedef enum TDEMgrRelationEncryptionStatus { From b93a6fe27b27ffd701b3382158ab36c1793a678a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:18:47 +0200 Subject: [PATCH 448/796] Use strcmp() instead of strncmp() Since we know that both strings are zero termianted there is no reason to use strncmp(). --- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 21d7156375214..a3a3c3721a004 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -1009,7 +1009,7 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) static bool pg_tde_is_same_principal_key(TDEPrincipalKey *a, TDEPrincipalKey *b) { - return a != NULL && b != NULL && strncmp(a->keyInfo.name, b->keyInfo.name, PRINCIPAL_KEY_NAME_LEN) == 0 && a->keyInfo.keyringId == b->keyInfo.keyringId; + return a != NULL && b != NULL && strcmp(a->keyInfo.name, b->keyInfo.name) == 0 && a->keyInfo.keyringId == b->keyInfo.keyringId; } static void From 11e49539eb48768536d9e7c1b472eef577dc98c0 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:19:01 +0200 Subject: [PATCH 449/796] Fix argument name in header file to match that of implementation I forgot to update the name of the argument in the header file. --- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index dcf834c995fb3..eed5b7bc569d0 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -77,13 +77,11 @@ typedef struct WALKeyCacheRec } WALKeyCacheRec; extern InternalKey *pg_tde_read_last_wal_key(void); - extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); - -extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType flags); +extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type); #define PG_TDE_MAP_FILENAME "%d_keys" From 2fc8ea22b534eb0e7988b22702691d51901f0f85 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:19:15 +0200 Subject: [PATCH 450/796] Remove pointless comment The comment just says the same thing as the function name. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index a3a3c3721a004..b168bb578f41a 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -1020,7 +1020,6 @@ pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey = *newKeyTemplate; newKey->keyInfo.databaseId = oldKey->keyInfo.databaseId; - /* key rotation */ pg_tde_perform_rotate_key(oldKey, newKey, true); clear_principal_key_cache(oldKey->keyInfo.databaseId); From 4c4e838d604c5eb383ef19360108bfea842c4257 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:19:24 +0200 Subject: [PATCH 451/796] Fix code formatting issue --- contrib/pg_tde/src/keyring/keyring_curl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/keyring/keyring_curl.c b/contrib/pg_tde/src/keyring/keyring_curl.c index 5c2a58e76c686..2a4abb601e532 100644 --- a/contrib/pg_tde/src/keyring/keyring_curl.c +++ b/contrib/pg_tde/src/keyring/keyring_curl.c @@ -16,8 +16,7 @@ CURL *keyringCurl = NULL; -static -size_t +static size_t write_func(void *ptr, size_t size, size_t nmemb, struct CurlString *s) { size_t new_len = s->len + size * nmemb; From 854e1a1a7769a0cc082c88a72ae7a71b0d54c9d2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 13:12:03 +0200 Subject: [PATCH 452/796] Use the standard syntax for initializing structs to zero As far as I understand you need at least one element to write a literal which initializes a struct to all zeros. In these particular cases I do not think it matters since static variables are always zero initialized but I prefer being clear. --- contrib/pg_tde/src/keyring/keyring_kmip_impl.c | 2 +- contrib/pg_tde/src/pg_tde_event_capture.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/keyring/keyring_kmip_impl.c b/contrib/pg_tde/src/keyring/keyring_kmip_impl.c index 54a5fba89d281..3a5a23b325d20 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip_impl.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip_impl.c @@ -20,7 +20,7 @@ pg_tde_kmip_set_by_name(BIO *bio, char *key_name, const unsigned char *key, unsi int32 mask = KMIP_CRYPTOMASK_ENCRYPT | KMIP_CRYPTOMASK_DECRYPT; Name ts; TextString ts2; - TemplateAttribute ta = {}; + TemplateAttribute ta = {0}; char *idp; int id_size; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index fef9cf2b31d0d..68466d5905fc4 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -49,7 +49,7 @@ typedef struct Oid rebuildSequence; } TdeDdlEvent; -static FullTransactionId ddlEventStackTid = {}; +static FullTransactionId ddlEventStackTid = {0}; static List *ddlEventStack = NIL; static Oid get_db_oid(const char *name); From acb974d96ebd9c611424bc15c14ac89c4dc9e940 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 2 May 2025 19:43:58 +0200 Subject: [PATCH 453/796] Do not use our shmem utils in the key provider code Since the only thing our key provider code does with shared memory is look up a LWLock tranche it is quite a waste of lines of codes to use our own layer on top of the PostgreSQL shared memory. --- contrib/pg_tde/src/catalog/tde_keyring.c | 40 +++---------------- .../pg_tde/src/include/catalog/tde_keyring.h | 2 +- contrib/pg_tde/src/pg_tde.c | 2 +- 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index c5aee34e377bd..bab3f19efe94d 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -87,12 +87,10 @@ PG_FUNCTION_INFO_V1(pg_tde_list_all_global_key_providers); static const char *get_keyring_provider_typename(ProviderType p_type); static List *GetAllKeyringProviders(Oid dbOid); -static Size initialize_shared_state(void *start_address); static Datum pg_tde_add_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_change_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid dbOid); static Datum pg_tde_list_all_key_providers_internal(PG_FUNCTION_ARGS, const char *fname, Oid dbOid); -static Size required_shared_mem_size(void); static List *scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid); static void save_new_key_provider_info(KeyringProviderRecord *provider, Oid databaseId); static void modify_key_provider_info(KeyringProviderRecord *provider, Oid databaseId); @@ -101,47 +99,19 @@ static void check_provider_record(KeyringProviderRecord *provider_record); #define PG_TDE_LIST_PROVIDERS_COLS 4 -typedef struct TdeKeyProviderInfoSharedState -{ - LWLockPadded *Locks; -} TdeKeyProviderInfoSharedState; - -TdeKeyProviderInfoSharedState *sharedPrincipalKeyState = NULL; /* Lives in shared state */ - -static const TDEShmemSetupRoutine key_provider_info_shmem_routine = { - .init_shared_state = initialize_shared_state, - .init_dsa_area_objects = NULL, - .required_shared_mem_size = required_shared_mem_size, - .shmem_kill = NULL -}; - -static Size -required_shared_mem_size(void) -{ - return MAXALIGN(sizeof(TdeKeyProviderInfoSharedState)); -} - -static Size -initialize_shared_state(void *start_address) -{ - sharedPrincipalKeyState = (TdeKeyProviderInfoSharedState *) start_address; - sharedPrincipalKeyState->Locks = GetNamedLWLockTranche(TDE_TRANCHE_NAME); - - return sizeof(TdeKeyProviderInfoSharedState); -} +static LWLockPadded *tdeLocks = NULL; /* Lives in shared state */ static inline LWLock * tde_provider_info_lock(void) { - Assert(sharedPrincipalKeyState); - return &sharedPrincipalKeyState->Locks[TDE_LWLOCK_PI_FILES].lock; + Assert(tdeLocks); + return &tdeLocks[TDE_LWLOCK_PI_FILES].lock; } void -InitializeKeyProviderInfo(void) +KeyProviderShmemInit(void) { - ereport(LOG, errmsg("initializing TDE key provider info")); - RegisterShmemRequest(&key_provider_info_shmem_routine); + tdeLocks = GetNamedLWLockTranche(TDE_TRANCHE_NAME); } void diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 11c24027747e6..5dd09f3e2c12a 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -33,7 +33,7 @@ typedef struct KeyringProviderRecordInFile extern GenericKeyring *GetKeyProviderByName(const char *provider_name, Oid dbOid); extern GenericKeyring *GetKeyProviderByID(int provider_id, Oid dbOid); extern ProviderType get_keyring_provider_from_typename(char *provider_type); -extern void InitializeKeyProviderInfo(void); +extern void KeyProviderShmemInit(void); extern void key_provider_startup_cleanup(Oid databaseId); extern bool get_keyring_info_file_record_by_name(char *provider_name, Oid database_id, diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index f38ad795609c4..4157b72d6f739 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -74,6 +74,7 @@ tde_shmem_startup(void) prev_shmem_startup_hook(); TdeShmemInit(); + InitializeKeyProviderInfo(); TDEXLogShmemInit(); TDEXLogSmgrInit(); } @@ -100,7 +101,6 @@ _PG_init(void) TdeGucInit(); TdeEventCaptureInit(); InitializePrincipalKeyInfo(); - InitializeKeyProviderInfo(); InstallFileKeyring(); InstallVaultV2Keyring(); InstallKmipKeyring(); From 9aff56e75b7c36d1df18b36d1e8367388869a2b1 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 6 May 2025 17:40:57 +0200 Subject: [PATCH 454/796] Remove our own shared memory tooling Since it just has one user now let's remove our own shared memory code and remove a lot of generic code that we do not need. --- .../pg_tde/src/catalog/tde_principal_key.c | 127 +++++++++--------- contrib/pg_tde/src/common/pg_tde_shmem.c | 112 --------------- .../src/include/catalog/tde_principal_key.h | 3 +- .../pg_tde/src/include/common/pg_tde_shmem.h | 36 ----- contrib/pg_tde/src/pg_tde.c | 9 +- 5 files changed, 69 insertions(+), 218 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index b168bb578f41a..c35798807341c 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -60,7 +60,6 @@ typedef struct TdePrincipalKeySharedState LWLockPadded *Locks; dshash_table_handle hashHandle; void *rawDsaArea; /* DSA area pointer */ - } TdePrincipalKeySharedState; typedef struct TdePrincipalKeylocalState @@ -84,10 +83,6 @@ static dshash_parameters principal_key_dsh_params = { static TdePrincipalKeylocalState principalKeyLocalState; static void principal_key_info_attach_shmem(void); -static Size initialize_shared_state(void *start_address); -static void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area); -static Size required_shared_mem_size(void); -static void shared_memory_shutdown(int code, Datum arg); static void clear_principal_key_cache(Oid databaseId); static inline dshash_table *get_principal_key_hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); @@ -111,32 +106,6 @@ PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); static void pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *principal_key_name, const char *provider_name, bool ensure_new_key); -static const TDEShmemSetupRoutine principal_key_info_shmem_routine = { - .init_shared_state = initialize_shared_state, - .init_dsa_area_objects = initialize_objects_in_dsa_area, - .required_shared_mem_size = required_shared_mem_size, - .shmem_kill = shared_memory_shutdown -}; - -void -InitializePrincipalKeyInfo(void) -{ - ereport(LOG, errmsg("Initializing TDE principal key info")); - RegisterShmemRequest(&principal_key_info_shmem_routine); -} - -/* - * Lock to guard internal/principal key. Usually, this lock has to be held until - * the caller fetches an internal_key or rotates the principal. - */ -LWLock * -tde_lwlock_enc_keys(void) -{ - Assert(principalKeyLocalState.sharedPrincipalKeyState); - - return &principalKeyLocalState.sharedPrincipalKeyState->Locks[TDE_LWLOCK_ENC_KEY].lock; -} - /* * Request some pages so we can fit the DSA header, empty hash table plus some * extra. Additional memory to grow the hash map will be allocated as needed @@ -147,8 +116,8 @@ tde_lwlock_enc_keys(void) */ #define CACHE_DSA_INITIAL_SIZE (4096 * 64) -static Size -required_shared_mem_size(void) +Size +PrincipalKeyShmemSize(void) { Size sz = CACHE_DSA_INITIAL_SIZE; @@ -156,41 +125,75 @@ required_shared_mem_size(void) return MAXALIGN(sz); } -/* - * Initialize the shared area for Principal key info. - * This includes locks and cache area for principal key info - */ - -static Size -initialize_shared_state(void *start_address) +void +PrincipalKeyShmemInit(void) { - TdePrincipalKeySharedState *sharedState = (TdePrincipalKeySharedState *) start_address; + bool found; + char *free_start; + Size required_shmem_size = PrincipalKeyShmemSize(); - ereport(LOG, errmsg("initializing shared state for principal key")); + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); - sharedState->Locks = GetNamedLWLockTranche(TDE_TRANCHE_NAME); + /* Create or attach to the shared memory state */ + ereport(NOTICE, errmsg("PrincipalKeyShmemInit: requested %ld bytes", required_shmem_size)); + free_start = ShmemInitStruct("pg_tde", required_shmem_size, &found); - principalKeyLocalState.sharedPrincipalKeyState = sharedState; - principalKeyLocalState.sharedHash = NULL; + if (!found) + { + TdePrincipalKeySharedState *sharedState; + Size sz; + Size dsa_area_size; + dsa_area *dsa; + dshash_table *dsh; - return sizeof(TdePrincipalKeySharedState); -} + /* Now place shared state structure */ + sharedState = (TdePrincipalKeySharedState *) free_start; + sz = MAXALIGN(sizeof(TdePrincipalKeySharedState)); + free_start += sz; + Assert(sz <= required_shmem_size); -static void -initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area) -{ - dshash_table *dsh; - TdePrincipalKeySharedState *sharedState = principalKeyLocalState.sharedPrincipalKeyState; + /* Create DSA area */ + dsa_area_size = required_shmem_size - sz; + Assert(dsa_area_size > 0); + + ereport(LOG, errmsg("creating DSA area of size %lu", dsa_area_size)); + + dsa = dsa_create_in_place(free_start, + dsa_area_size, + LWLockNewTrancheId(), 0); + dsa_pin(dsa); + + /* Limit area size during population to get a nice error */ + dsa_set_size_limit(dsa, dsa_area_size); + + principal_key_dsh_params.tranche_id = LWLockNewTrancheId(); + dsh = dshash_create(dsa, &principal_key_dsh_params, NULL); + + dsa_set_size_limit(dsa, -1); - ereport(LOG, errmsg("initializing dsa area objects for principal key")); + sharedState->Locks = GetNamedLWLockTranche(TDE_TRANCHE_NAME); + sharedState->hashHandle = dshash_get_hash_table_handle(dsh); + sharedState->rawDsaArea = free_start; - Assert(sharedState != NULL); + principalKeyLocalState.sharedPrincipalKeyState = sharedState; + principalKeyLocalState.sharedHash = NULL; - sharedState->rawDsaArea = raw_dsa_area; - principal_key_dsh_params.tranche_id = LWLockNewTrancheId(); - dsh = dshash_create(dsa, &principal_key_dsh_params, NULL); - sharedState->hashHandle = dshash_get_hash_table_handle(dsh); - dshash_detach(dsh); + dshash_detach(dsh); + } + + LWLockRelease(AddinShmemInitLock); +} + +/* + * Lock to guard internal/principal key. Usually, this lock has to be held until + * the caller fetches an internal_key or rotates the principal. + */ +LWLock * +tde_lwlock_enc_keys(void) +{ + Assert(principalKeyLocalState.sharedPrincipalKeyState); + + return &principalKeyLocalState.sharedPrincipalKeyState->Locks[TDE_LWLOCK_ENC_KEY].lock; } /* @@ -217,12 +220,6 @@ principal_key_info_attach_shmem(void) MemoryContextSwitchTo(oldcontext); } -static void -shared_memory_shutdown(int code, Datum arg) -{ - principalKeyLocalState.sharedPrincipalKeyState = NULL; -} - void set_principal_key_with_keyring(const char *key_name, const char *provider_name, Oid providerOid, Oid dbOid, bool ensure_new_key) diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c index 697a3e23b6843..2d32893ff3a16 100644 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ b/contrib/pg_tde/src/common/pg_tde_shmem.c @@ -11,122 +11,10 @@ #include "postgres.h" -#include "lib/dshash.h" -#include "nodes/pg_list.h" -#include "storage/ipc.h" -#include "storage/lwlock.h" -#include "storage/shmem.h" - #include "common/pg_tde_shmem.h" -static void tde_shmem_shutdown(int code, Datum arg); - -List *registeredShmemRequests = NIL; -bool shmemInited = false; - -void -RegisterShmemRequest(const TDEShmemSetupRoutine *routine) -{ - Assert(shmemInited == false); - registeredShmemRequests = lappend(registeredShmemRequests, (void *) routine); -} - -Size -TdeRequiredSharedMemorySize(void) -{ - Size sz = 0; - ListCell *lc; - - foreach(lc, registeredShmemRequests) - { - TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc); - - if (routine->required_shared_mem_size) - sz = add_size(sz, routine->required_shared_mem_size()); - } - return MAXALIGN(sz); -} - int TdeRequiredLocksCount(void) { return TDE_LWLOCK_COUNT; } - -void -TdeShmemInit(void) -{ - bool found; - char *free_start; - Size required_shmem_size = TdeRequiredSharedMemorySize(); - - LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); - /* Create or attach to the shared memory state */ - ereport(NOTICE, errmsg("TdeShmemInit: requested %ld bytes", required_shmem_size)); - free_start = ShmemInitStruct("pg_tde", required_shmem_size, &found); - - if (!found) - { - /* First time through ... */ - dsa_area *dsa; - ListCell *lc; - Size used_size = 0; - Size dsa_area_size; - - /* Now place all shared state structures */ - foreach(lc, registeredShmemRequests) - { - Size sz = 0; - TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc); - - if (routine->init_shared_state) - { - sz = routine->init_shared_state(free_start); - used_size += MAXALIGN(sz); - free_start += MAXALIGN(sz); - Assert(used_size <= required_shmem_size); - } - } - /* Create DSA area */ - dsa_area_size = required_shmem_size - used_size; - Assert(dsa_area_size > 0); - - ereport(LOG, errmsg("creating DSA area of size %lu", dsa_area_size)); - dsa = dsa_create_in_place(free_start, - dsa_area_size, - LWLockNewTrancheId(), 0); - dsa_pin(dsa); - dsa_set_size_limit(dsa, dsa_area_size); - - /* Initialize all DSA area objects */ - foreach(lc, registeredShmemRequests) - { - TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc); - - if (routine->init_dsa_area_objects) - routine->init_dsa_area_objects(dsa, free_start); - } - ereport(LOG, errmsg("setting no limit to DSA area of size %lu", dsa_area_size)); - - dsa_set_size_limit(dsa, -1); /* Let it grow outside the shared - * memory */ - - shmemInited = true; - } - LWLockRelease(AddinShmemInitLock); - on_shmem_exit(tde_shmem_shutdown, (Datum) 0); -} - -static void -tde_shmem_shutdown(int code, Datum arg) -{ - ListCell *lc; - - foreach(lc, registeredShmemRequests) - { - TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc); - - if (routine->shmem_kill) - routine->shmem_kill(code, arg); - } -} diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index 26dd6f22befaa..185a92292954a 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -42,7 +42,8 @@ typedef struct XLogPrincipalKeyRotate #define SizeoOfXLogPrincipalKeyRotate offsetof(XLogPrincipalKeyRotate, buff) -extern void InitializePrincipalKeyInfo(void); +extern void PrincipalKeyShmemInit(void); +extern Size PrincipalKeyShmemSize(void); #ifndef FRONTEND extern void principal_key_startup_cleanup(Oid databaseId); diff --git a/contrib/pg_tde/src/include/common/pg_tde_shmem.h b/contrib/pg_tde/src/include/common/pg_tde_shmem.h index 31dfb3a691723..ba36d10d2461e 100644 --- a/contrib/pg_tde/src/include/common/pg_tde_shmem.h +++ b/contrib/pg_tde/src/include/common/pg_tde_shmem.h @@ -8,9 +8,6 @@ #ifndef PG_TDE_SHMEM_H #define PG_TDE_SHMEM_H -#include "postgres.h" -#include "utils/dsa.h" - #define TDE_TRANCHE_NAME "pg_tde_tranche" typedef enum @@ -22,39 +19,6 @@ typedef enum TDE_LWLOCK_COUNT } TDELockTypes; -typedef struct TDEShmemSetupRoutine -{ - /* - * init_shared_state gets called at the time of extension load you can - * initialize the data structures required to be placed in shared memory - * in this callback The callback must return the size of the shared memory - * area acquired. The argument to the function is the start of the shared - * memory address that can be used to store the shared data structures. - */ - Size (*init_shared_state) (void *raw_dsa_area); - - /* - * shmem_startup gets called at the time of postmaster shutdown - */ - void (*shmem_kill) (int code, Datum arg); - - /* - * The callback must return the size of the shared memory acquired. - */ - Size (*required_shared_mem_size) (void); - - /* - * Gets called after all shared memory structures are initialized and here - * you can create shared memory hash tables or any other shared objects - * that needs to live in DSA area. - */ - void (*init_dsa_area_objects) (dsa_area *dsa, void *raw_dsa_area); -} TDEShmemSetupRoutine; - -/* Interface to register the shared memory requests */ -extern void RegisterShmemRequest(const TDEShmemSetupRoutine *routine); -extern void TdeShmemInit(void); -extern Size TdeRequiredSharedMemorySize(void); extern int TdeRequiredLocksCount(void); #endif /* PG_TDE_SHMEM_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 4157b72d6f739..974b3358a2285 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -55,13 +55,15 @@ PG_FUNCTION_INFO_V1(pg_tdeam_handler); static void tde_shmem_request(void) { - Size sz = TdeRequiredSharedMemorySize(); + Size sz = 0; int required_locks = TdeRequiredLocksCount(); + sz = add_size(sz, PrincipalKeyShmemSize()); sz = add_size(sz, TDEXLogEncryptStateSize()); if (prev_shmem_request_hook) prev_shmem_request_hook(); + RequestAddinShmemSpace(sz); RequestNamedLWLockTranche(TDE_TRANCHE_NAME, required_locks); ereport(LOG, errmsg("tde_shmem_request: requested %ld bytes", sz)); @@ -73,8 +75,8 @@ tde_shmem_startup(void) if (prev_shmem_startup_hook) prev_shmem_startup_hook(); - TdeShmemInit(); - InitializeKeyProviderInfo(); + KeyProviderShmemInit(); + PrincipalKeyShmemInit(); TDEXLogShmemInit(); TDEXLogSmgrInit(); } @@ -100,7 +102,6 @@ _PG_init(void) AesInit(); TdeGucInit(); TdeEventCaptureInit(); - InitializePrincipalKeyInfo(); InstallFileKeyring(); InstallVaultV2Keyring(); InstallKmipKeyring(); From 589bcab28128c02bb6cf040745d709e6423612ab Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 11:05:39 +0200 Subject: [PATCH 455/796] Move code in pg_tde_shmem.h to pg_tde.h Now that we have removed our shared memory tools the remaining contents of the header can be moved elsewhere. --- contrib/pg_tde/Makefile | 1 - contrib/pg_tde/meson.build | 1 - contrib/pg_tde/src/catalog/tde_keyring.c | 3 ++- .../pg_tde/src/catalog/tde_principal_key.c | 1 - contrib/pg_tde/src/common/pg_tde_shmem.c | 20 ---------------- .../pg_tde/src/include/common/pg_tde_shmem.h | 24 ------------------- contrib/pg_tde/src/include/pg_tde.h | 11 +++++++++ contrib/pg_tde/src/pg_tde.c | 4 +--- 8 files changed, 14 insertions(+), 51 deletions(-) delete mode 100644 contrib/pg_tde/src/common/pg_tde_shmem.c delete mode 100644 contrib/pg_tde/src/include/common/pg_tde_shmem.h diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index f8500336c00c2..32171a622cb67 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -44,7 +44,6 @@ src/keyring/keyring_api.o \ src/catalog/tde_keyring.o \ src/catalog/tde_keyring_parse_opts.o \ src/catalog/tde_principal_key.o \ -src/common/pg_tde_shmem.o \ src/common/pg_tde_utils.o \ src/smgr/pg_tde_smgr.o \ src/pg_tde_event_capture.o \ diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 7abf96de07030..c2b44755a5eca 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -7,7 +7,6 @@ pg_tde_sources = files( 'src/catalog/tde_keyring.c', 'src/catalog/tde_keyring_parse_opts.c', 'src/catalog/tde_principal_key.c', - 'src/common/pg_tde_shmem.c', 'src/common/pg_tde_utils.c', 'src/encryption/enc_aes.c', 'src/encryption/enc_tde.c', diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index bab3f19efe94d..b192069052af2 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -34,12 +34,13 @@ #ifndef FRONTEND #include "access/heapam.h" +#include "funcapi.h" +#include "access/relscan.h" #include "access/relation.h" #include "access/relscan.h" #include "catalog/namespace.h" #include "executor/spi.h" #include "funcapi.h" -#include "common/pg_tde_shmem.h" #else #include "fe_utils/simple_list.h" #include "pg_tde_fe.h" diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index c35798807341c..c9e2017f269b7 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -44,7 +44,6 @@ #include "lib/dshash.h" #include "storage/lwlock.h" #include "storage/shmem.h" -#include "common/pg_tde_shmem.h" #else #include "pg_tde_fe.h" #endif diff --git a/contrib/pg_tde/src/common/pg_tde_shmem.c b/contrib/pg_tde/src/common/pg_tde_shmem.c deleted file mode 100644 index 2d32893ff3a16..0000000000000 --- a/contrib/pg_tde/src/common/pg_tde_shmem.c +++ /dev/null @@ -1,20 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_shmem.c - * Shared memory area to manage cache and locks. - * - * IDENTIFICATION - * contrib/pg_tde/src/pg_tde_shmem.c - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "common/pg_tde_shmem.h" - -int -TdeRequiredLocksCount(void) -{ - return TDE_LWLOCK_COUNT; -} diff --git a/contrib/pg_tde/src/include/common/pg_tde_shmem.h b/contrib/pg_tde/src/include/common/pg_tde_shmem.h deleted file mode 100644 index ba36d10d2461e..0000000000000 --- a/contrib/pg_tde/src/include/common/pg_tde_shmem.h +++ /dev/null @@ -1,24 +0,0 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_shmem.h - * src/include/common/pg_tde_shmem.h - * - *------------------------------------------------------------------------- - */ -#ifndef PG_TDE_SHMEM_H -#define PG_TDE_SHMEM_H - -#define TDE_TRANCHE_NAME "pg_tde_tranche" - -typedef enum -{ - TDE_LWLOCK_ENC_KEY, - TDE_LWLOCK_PI_FILES, - - /* Must be the last entry in the enum */ - TDE_LWLOCK_COUNT -} TDELockTypes; - -extern int TdeRequiredLocksCount(void); - -#endif /* PG_TDE_SHMEM_H */ diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index f20af2892e896..cdeff802ed7b1 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -14,6 +14,17 @@ #define PG_TDE_DATA_DIR "pg_tde" +#define TDE_TRANCHE_NAME "pg_tde_tranche" + +typedef enum +{ + TDE_LWLOCK_ENC_KEY, + TDE_LWLOCK_PI_FILES, + + /* Must be the last entry in the enum */ + TDE_LWLOCK_COUNT +} TDELockTypes; + typedef struct XLogExtensionInstall { Oid database_id; diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 974b3358a2285..216d6a1f3c6cb 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -30,7 +30,6 @@ #include "access/pg_tde_xlog_smgr.h" #include "catalog/tde_global_space.h" #include "catalog/tde_principal_key.h" -#include "common/pg_tde_shmem.h" #include "encryption/enc_aes.h" #include "keyring/keyring_api.h" #include "keyring/keyring_file.h" @@ -56,7 +55,6 @@ static void tde_shmem_request(void) { Size sz = 0; - int required_locks = TdeRequiredLocksCount(); sz = add_size(sz, PrincipalKeyShmemSize()); sz = add_size(sz, TDEXLogEncryptStateSize()); @@ -65,7 +63,7 @@ tde_shmem_request(void) prev_shmem_request_hook(); RequestAddinShmemSpace(sz); - RequestNamedLWLockTranche(TDE_TRANCHE_NAME, required_locks); + RequestNamedLWLockTranche(TDE_TRANCHE_NAME, TDE_LWLOCK_COUNT); ereport(LOG, errmsg("tde_shmem_request: requested %ld bytes", sz)); } From 600718046774cb00dff7abadfc59885f2b4a18a9 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 28 May 2025 17:12:37 +0200 Subject: [PATCH 456/796] PG-1257 Add functions for principal key removal Add SQL functions that allow user to remove principal key. * Database level principal key can be removed if there are no encrypted tables or if there is default key. For the first case we just drop key map file completely, for the second we perform key rotation. * Default principal key can be removed if there are no databases that use it. Readded the DELETE key function to docs based on 1257 in Architecture chapter where we also update from DROP to DELETE. --- contrib/pg_tde/Makefile | 9 +- .../documentation/docs/architecture/index.md | 20 ++- contrib/pg_tde/expected/access_control.out | 5 + contrib/pg_tde/expected/alter_index.out | 12 ++ contrib/pg_tde/expected/create_database.out | 12 ++ .../pg_tde/expected/default_principal_key.out | 33 ++-- .../pg_tde/expected/delete_principal_key.out | 130 ++++++++++++++ contrib/pg_tde/expected/key_provider.out | 14 +- contrib/pg_tde/meson.build | 9 +- contrib/pg_tde/pg_tde--1.0-rc.sql | 12 ++ contrib/pg_tde/sql/access_control.sql | 3 + contrib/pg_tde/sql/alter_index.sql | 2 + contrib/pg_tde/sql/create_database.sql | 2 + contrib/pg_tde/sql/default_principal_key.sql | 3 +- contrib/pg_tde/sql/delete_principal_key.sql | 53 ++++++ contrib/pg_tde/src/access/pg_tde_tdemap.c | 41 ++++- contrib/pg_tde/src/access/pg_tde_xlog.c | 14 ++ .../pg_tde/src/catalog/tde_principal_key.c | 168 +++++++++++++++++- .../pg_tde/src/include/access/pg_tde_tdemap.h | 2 + .../pg_tde/src/include/access/pg_tde_xlog.h | 1 + contrib/pg_tde/src/pg_tde_event_capture.c | 6 +- 21 files changed, 506 insertions(+), 45 deletions(-) create mode 100644 contrib/pg_tde/expected/delete_principal_key.out create mode 100644 contrib/pg_tde/sql/delete_principal_key.sql diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 32171a622cb67..f10ad764ecf9c 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -8,12 +8,13 @@ DATA = pg_tde--1.0-rc.sql # Since meson supports skipping test suites this is a make only feature ifndef TDE_MODE REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_tde/pg_tde.conf -# create_database must run after default_principal_key which must run after -# key_provider. REGRESS = access_control \ alter_index \ cache_alloc \ change_access_method \ +create_database \ +default_principal_key \ +delete_principal_key \ insert_update_delete \ key_provider \ kmip_test \ @@ -24,9 +25,7 @@ relocate \ tablespace \ toast_decrypt \ vault_v2_test \ -version \ -default_principal_key \ -create_database +version TAP_TESTS = 1 endif diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index d2061a8f7058c..0eff7081d4b07 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -292,15 +292,23 @@ With `pg_tde.inherit_global_key_providers`, it is also possible to set up a defa With this feature, it is possible for the entire database server to easily use the same principal key for all databases, completely disabling multi-tenency. -A default key can be managed with the following functions: +#### Manage a default key -```sql -pg_tde_set_default_key_using_global_key_provider('key-name', 'provider-name', 'true/false') -``` +You can manage a default key with the following functions: + +* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name','true/false')` +* `pg_tde_delete_default_key()` + +!!! note + `pg_tde_delete_default_key()` is only possible if there's no table currently using the default principal key. + Changing the default principal key will rotate the encryption of internal keys for all databases using the current default principal key. + +#### Delete a key -`DROP` is only possible if there's no table currently using the default principal key. +The `pg_tde_delete_key()` function removes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a global default principal key, internal keys will be encrypted with the default key. -Changing the default principal key will rotate the encryption of internal keys for all databases using the current default principal key. +!!! note + WAL keys **cannot** be deleted, as server keys are managed separately. ### Current key details diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 8996168f54fc6..045e3ca8964c3 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -10,6 +10,8 @@ SET ROLE regress_pg_tde_access_control; -- should throw access denied SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); ERROR: permission denied for function pg_tde_set_key_using_database_key_provider +SELECT pg_tde_delete_key(); +ERROR: permission denied for function pg_tde_delete_key SELECT pg_tde_list_all_database_key_providers(); ERROR: permission denied for function pg_tde_list_all_database_key_providers SELECT pg_tde_list_all_global_key_providers(); @@ -37,6 +39,7 @@ GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_ GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_default_key() TO regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); ERROR: must be superuser to modify key providers @@ -56,5 +59,7 @@ SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-pro ERROR: must be superuser to access global key providers SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers +SELECT pg_tde_delete_default_key(); +ERROR: must be superuser to access global key providers RESET ROLE; DROP EXTENSION pg_tde CASCADE; diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index 72575e8a548e6..dc3c181acdd49 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -67,5 +67,17 @@ SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_ind (5 rows) DROP TABLE concur_reindex_part; +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +SELECT pg_tde_delete_database_key_provider('file-vault'); + pg_tde_delete_database_key_provider +------------------------------------- + +(1 row) + DROP EXTENSION pg_tde; RESET default_table_access_method; diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index 16acc9d8d4c2f..83944edd3e3e5 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -102,4 +102,16 @@ CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY; DROP DATABASE new_db_file_copy; DROP DATABASE new_db; DROP DATABASE template_db; +SELECT pg_tde_delete_default_key(); + pg_tde_delete_default_key +--------------------------- + +(1 row) + +SELECT pg_tde_delete_global_key_provider('global-file-vault'); + pg_tde_delete_global_key_provider +----------------------------------- + +(1 row) + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index b36393ff0aa51..ad5870cc198e0 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -33,20 +33,17 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_default_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -5 | file-provider | default-key + -2 | file-provider | default-key (1 row) -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+------------------ - -1 | file-keyring - -3 | global-provider - -4 | global-provider2 - -5 | file-provider -(4 rows) + id | provider_name +----+--------------- + -2 | file-provider +(1 row) -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name @@ -68,7 +65,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -5 | file-provider | default-key + -2 | file-provider | default-key (1 row) SELECT current_database() AS regress_database @@ -97,7 +94,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+------------- - -5 | file-provider | default-key + -2 | file-provider | default-key (1 row) \c :regress_database @@ -112,7 +109,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+----------------- - -5 | file-provider | new-default-key + -2 | file-provider | new-default-key (1 row) \c regress_pg_tde_other @@ -120,7 +117,7 @@ SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); key_provider_id | key_provider_name | key_name -----------------+-------------------+----------------- - -5 | file-provider | new-default-key + -2 | file-provider | new-default-key (1 row) SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); @@ -155,6 +152,18 @@ SELECT * FROM test_enc; (3 rows) DROP TABLE test_enc; +SELECT pg_tde_delete_default_key(); + pg_tde_delete_default_key +--------------------------- + +(1 row) + +SELECT pg_tde_delete_global_key_provider('file-provider'); + pg_tde_delete_global_key_provider +----------------------------------- + +(1 row) + DROP EXTENSION pg_tde CASCADE; DROP EXTENSION pg_buffercache; DROP DATABASE regress_pg_tde_other; diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out new file mode 100644 index 0000000000000..480297556dd07 --- /dev/null +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -0,0 +1,130 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; +SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + pg_tde_add_global_key_provider_file +------------------------------------- + +(1 row) + +-- Set the local key and delete it without any encrypted tables +-- Should succeed: nothing used the key +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -3 | file-provider | test-db-key +(1 row) + +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +-- Set local key, encrypt a table, and delete the key +-- Should fail: the is no default key to fallback +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +CREATE TABLE test_table (id int, data text) USING tde_heap; +SELECT pg_tde_delete_key(); +ERROR: cannot delete principal key +DETAIL: There are encrypted tables in the database. +HINT: Set default principal key as fallback option or decrypt all tables before deleting principal key. +-- Decrypt the table and delete the key +-- Should succeed: there is no more encrypted tables +ALTER TABLE test_table SET ACCESS METHOD heap; +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +-- Set local key, encrypt the table then delete teable and key +-- Should succeed: the table is deleted and there are no more encrypted tables +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +ALTER TABLE test_table SET ACCESS METHOD tde_heap; +DROP TABLE test_table; +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +-- Set default key, set regular key, create table, delete regular key +-- Should succeed: regular key will be rotated to default key +SELECT pg_tde_set_default_key_using_global_key_provider('defalut-key','file-provider'); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +CREATE TABLE test_table (id int, data text) USING tde_heap; +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); + key_provider_id | key_provider_name | key_name +-----------------+-------------------+------------- + -3 | file-provider | defalut-key +(1 row) + +-- Try to delete key when default key is used +-- Should fail: table already uses the default key, so there is no key to fallback to +SELECT pg_tde_delete_key(); +ERROR: cannot delete principal key +DETAIL: There are encrypted tables in the database. +-- Try to delete default key +-- Should fail: default key is used by the table +SELECT pg_tde_delete_default_key(); +ERROR: cannot delete default principal key +HINT: There are encrypted tables in the database with id: 16384. +-- Set regular principal key, delete default key +-- Should succeed: the table will use the regular key +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +SELECT pg_tde_delete_default_key(); + pg_tde_delete_default_key +--------------------------- + +(1 row) + +DROP TABLE test_table; +SELECT pg_tde_delete_key(); + pg_tde_delete_key +------------------- + +(1 row) + +SELECT pg_tde_delete_global_key_provider('file-provider'); + pg_tde_delete_global_key_provider +----------------------------------- + +(1 row) + +DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index e3ef0dd2e4aa1..c77c2653657d1 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -75,8 +75,8 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_key SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- - -1 | file-keyring - -2 | file-keyring2 + -4 | file-keyring + -5 | file-keyring2 (2 rows) -- fails @@ -105,8 +105,8 @@ SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- - -1 | file-keyring - -2 | file-keyring2 + -4 | file-keyring + -5 | file-keyring2 (2 rows) SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); @@ -121,8 +121,8 @@ ERROR: Can't delete a provider which is currently in use SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- - -1 | file-keyring - -2 | file-keyring2 + -4 | file-keyring + -5 | file-keyring2 (2 rows) -- works @@ -135,7 +135,7 @@ SELECT pg_tde_delete_global_key_provider('file-keyring2'); SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); id | provider_name ----+--------------- - -1 | file-keyring + -4 | file-keyring (1 row) -- Creating a file key provider fails if we can't open or create the file diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index c2b44755a5eca..21f5b13c445a8 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -79,26 +79,25 @@ install_data( kwargs: contrib_data_args, ) -# create_database must run after default_principal_key which must run after -# key_provider. sql_tests = [ 'access_control', 'alter_index', 'cache_alloc', 'change_access_method', + 'create_database', + 'default_principal_key', + 'delete_principal_key', 'insert_update_delete', 'key_provider', 'kmip_test', 'partition_table', 'pg_tde_is_encrypted', - 'relocate', 'recreate_storage', + 'relocate', 'tablespace', 'toast_decrypt', 'vault_v2_test', 'version', - 'default_principal_key', - 'create_database', ] tap_tests = [ diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index cc92bbe217b92..c9092e81ee66e 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -259,6 +259,18 @@ LANGUAGE C AS 'MODULE_PATHNAME'; REVOKE ALL ON FUNCTION pg_tde_verify_default_key() FROM PUBLIC; +CREATE FUNCTION pg_tde_delete_key() +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_delete_key() FROM PUBLIC; + +CREATE FUNCTION pg_tde_delete_default_key() +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_delete_default_key() FROM PUBLIC; + CREATE FUNCTION pg_tde_key_info() RETURNS TABLE ( key_name TEXT, key_provider_name TEXT, diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 90ca5e9c60bc7..b8ac7aff0ec79 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -8,6 +8,7 @@ SET ROLE regress_pg_tde_access_control; -- should throw access denied SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); +SELECT pg_tde_delete_key(); SELECT pg_tde_list_all_database_key_providers(); SELECT pg_tde_list_all_global_key_providers(); SELECT pg_tde_key_info(); @@ -29,6 +30,7 @@ GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_ GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_delete_default_key() TO regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; @@ -41,6 +43,7 @@ SELECT pg_tde_delete_global_key_provider('global-file-provider'); SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); +SELECT pg_tde_delete_default_key(); RESET ROLE; diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 9dac7bea58338..794161bbd0eae 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -33,5 +33,7 @@ SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_ind SELECT relid, parentrelid, level FROM pg_partition_tree('concur_reindex_part_index') ORDER BY relid, level; DROP TABLE concur_reindex_part; +SELECT pg_tde_delete_key(); +SELECT pg_tde_delete_database_key_provider('file-vault'); DROP EXTENSION pg_tde; RESET default_table_access_method; diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql index d62cdc12f5092..77c7aaf84a83a 100644 --- a/contrib/pg_tde/sql/create_database.sql +++ b/contrib/pg_tde/sql/create_database.sql @@ -59,4 +59,6 @@ DROP DATABASE new_db_file_copy; DROP DATABASE new_db; DROP DATABASE template_db; +SELECT pg_tde_delete_default_key(); +SELECT pg_tde_delete_global_key_provider('global-file-vault'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index be4183566c7fa..b91744390daa5 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -93,7 +93,8 @@ SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (S SELECT * FROM test_enc; DROP TABLE test_enc; - +SELECT pg_tde_delete_default_key(); +SELECT pg_tde_delete_global_key_provider('file-provider'); DROP EXTENSION pg_tde CASCADE; DROP EXTENSION pg_buffercache; diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql new file mode 100644 index 0000000000000..6f313277ab297 --- /dev/null +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -0,0 +1,53 @@ +CREATE EXTENSION IF NOT EXISTS pg_tde; + +SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); + +-- Set the local key and delete it without any encrypted tables +-- Should succeed: nothing used the key +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +SELECT pg_tde_delete_key(); + +-- Set local key, encrypt a table, and delete the key +-- Should fail: the is no default key to fallback +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +CREATE TABLE test_table (id int, data text) USING tde_heap; +SELECT pg_tde_delete_key(); + +-- Decrypt the table and delete the key +-- Should succeed: there is no more encrypted tables +ALTER TABLE test_table SET ACCESS METHOD heap; +SELECT pg_tde_delete_key(); + +-- Set local key, encrypt the table then delete teable and key +-- Should succeed: the table is deleted and there are no more encrypted tables +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +ALTER TABLE test_table SET ACCESS METHOD tde_heap; +DROP TABLE test_table; +SELECT pg_tde_delete_key(); + +-- Set default key, set regular key, create table, delete regular key +-- Should succeed: regular key will be rotated to default key +SELECT pg_tde_set_default_key_using_global_key_provider('defalut-key','file-provider'); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +CREATE TABLE test_table (id int, data text) USING tde_heap; +SELECT pg_tde_delete_key(); +SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); + +-- Try to delete key when default key is used +-- Should fail: table already uses the default key, so there is no key to fallback to +SELECT pg_tde_delete_key(); + +-- Try to delete default key +-- Should fail: default key is used by the table +SELECT pg_tde_delete_default_key(); + +-- Set regular principal key, delete default key +-- Should succeed: the table will use the regular key +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +SELECT pg_tde_delete_default_key(); + +DROP TABLE test_table; +SELECT pg_tde_delete_key(); +SELECT pg_tde_delete_global_key_provider('file-provider'); +DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 3ab258ea05ec3..d4e377559eefd 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -508,6 +508,40 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p } } +void +pg_tde_delete_principal_key_redo(Oid dbOid) +{ + char path[MAXPGPATH]; + + pg_tde_set_db_file_path(dbOid, path); + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + durable_unlink(path, WARNING); + LWLockRelease(tde_lwlock_enc_keys()); +} + +/* + * Deletes the principal key for the database. This fucntion checks if key map + * file has any entries, and if not, it removes the file. Otherwise raises an error. + */ +void +pg_tde_delete_principal_key(Oid dbOid) +{ + char path[MAXPGPATH]; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + Assert(pg_tde_count_relations(dbOid) == 0); + + pg_tde_set_db_file_path(dbOid, path); + + XLogBeginInsert(); + XLogRegisterData((char *) &dbOid, sizeof(Oid)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_PRINCIPAL_KEY); + + /* Remove whole key map file */ + durable_unlink(path, ERROR); +} + /* * It's called by seg_write inside crit section so no pallocs, hence * needs keyfile_path @@ -652,15 +686,14 @@ int pg_tde_count_relations(Oid dbOid) { char db_map_path[MAXPGPATH]; - LWLock *lock_pk = tde_lwlock_enc_keys(); File map_fd; off_t curr_pos = 0; TDEMapEntry map_entry; int count = 0; - pg_tde_set_db_file_path(dbOid, db_map_path); + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - LWLockAcquire(lock_pk, LW_SHARED); + pg_tde_set_db_file_path(dbOid, db_map_path); map_fd = pg_tde_open_file_read(db_map_path, true, &curr_pos); if (map_fd < 0) @@ -674,8 +707,6 @@ pg_tde_count_relations(Oid dbOid) CloseTransientFile(map_fd); - LWLockRelease(lock_pk); - return count; } diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 14015f32b8d1e..c0e1ba07510d4 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -73,6 +73,12 @@ tdeheap_rmgr_redo(XLogReaderState *record) xl_tde_perform_rotate_key(xlrec); } + else if (info == XLOG_TDE_DELETE_PRINCIPAL_KEY) + { + Oid dbOid = *((Oid *) XLogRecGetData(record)); + + pg_tde_delete_principal_key_redo(dbOid); + } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { KeyringProviderRecordInFile *xlrec = (KeyringProviderRecordInFile *) XLogRecGetData(record); @@ -114,6 +120,12 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "db: %u", xlrec->databaseId); } + else if (info == XLOG_TDE_DELETE_PRINCIPAL_KEY) + { + Oid dbOid = *((Oid *) XLogRecGetData(record)); + + appendStringInfo(buf, "db: %u", dbOid); + } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { KeyringProviderRecordInFile *xlrec = (KeyringProviderRecordInFile *) XLogRecGetData(record); @@ -139,6 +151,8 @@ tdeheap_rmgr_identify(uint8 info) return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_ROTATE_PRINCIPAL_KEY: return "ROTATE_PRINCIPAL_KEY"; + case XLOG_TDE_DELETE_PRINCIPAL_KEY: + return "DELETE_PRINCIPAL_KEY"; case XLOG_TDE_WRITE_KEY_PROVIDER: return "WRITE_KEY_PROVIDER"; case XLOG_TDE_INSTALL_EXTENSION: diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index c9e2017f269b7..1216cb9becc79 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -39,7 +39,10 @@ #ifndef FRONTEND #include "access/genam.h" +#include "access/heapam.h" #include "access/table.h" +#include "access/tableam.h" +#include "common/pg_tde_shmem.h" #include "funcapi.h" #include "lib/dshash.h" #include "storage/lwlock.h" @@ -86,7 +89,7 @@ static void clear_principal_key_cache(Oid databaseId); static inline dshash_table *get_principal_key_hash(void); static TDEPrincipalKey *get_principal_key_from_cache(Oid dbOid); static bool pg_tde_is_same_principal_key(TDEPrincipalKey *a, TDEPrincipalKey *b); -static void pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey); +static void pg_tde_update_default_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey); static void push_principal_key_to_cache(TDEPrincipalKey *principalKey); static Datum pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid); static TDEPrincipalKey *get_principal_key_from_keyring(Oid dbOid); @@ -97,11 +100,14 @@ static void set_principal_key_with_keyring(const char *key_name, Oid dbOid, bool ensure_new_key); static bool pg_tde_verify_principal_key_internal(Oid databaseOid); +static void pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKeyTemplate); PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); +PG_FUNCTION_INFO_V1(pg_tde_delete_key); +PG_FUNCTION_INFO_V1(pg_tde_delete_default_key); static void pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *principal_key_name, const char *provider_name, bool ensure_new_key); @@ -569,12 +575,159 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); newDefaultKey = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); - pg_tde_update_global_principal_key_everywhere(&existingKeyCopy, newDefaultKey); + pg_tde_update_default_principal_key_everywhere(&existingKeyCopy, newDefaultKey); LWLockRelease(tde_lwlock_enc_keys()); } } + +/* + * SQL interface to delete principal key. + * + * This operation allowed if there is no any encrypted tables in the database or + * if the default principal key is set for the database. In second case, + * key for database rotated to the default key. + */ +Datum +pg_tde_delete_key(PG_FUNCTION_ARGS) +{ + TDEPrincipalKey *principal_key; + TDEPrincipalKey *default_principal_key; + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + principal_key = GetPrincipalKeyNoDefault(MyDatabaseId, LW_EXCLUSIVE); + if (principal_key == NULL) + ereport(ERROR, errmsg("principal key does not exists for the database")); + + ereport(LOG, errmsg("Deleting principal key [%s] for the database", principal_key->keyInfo.name)); + + /* + * If database has something encryted, we can try to fallback to the + * default principal key + */ + if (pg_tde_count_relations(MyDatabaseId) != 0) + { + default_principal_key = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); + if (default_principal_key == NULL) + { + ereport(ERROR, + errmsg("cannot delete principal key"), + errdetail("There are encrypted tables in the database."), + errhint("Set default principal key as fallback option or decrypt all tables before deleting principal key.")); + } + + /* + * If database already encrypted with default principal key, there is + * nothing to do + */ + if (pg_tde_is_same_principal_key(principal_key, default_principal_key)) + { + ereport(ERROR, + errmsg("cannot delete principal key"), + errdetail("There are encrypted tables in the database.")); + } + + pg_tde_rotate_default_key_for_database(principal_key, default_principal_key); + + LWLockRelease(tde_lwlock_enc_keys()); + PG_RETURN_VOID(); + } + + pg_tde_delete_principal_key(MyDatabaseId); + clear_principal_key_cache(MyDatabaseId); + + LWLockRelease(tde_lwlock_enc_keys()); + PG_RETURN_VOID(); +} + +/* + * SQL interface to delete default principal key. + * + * This operation allowed if there is no databases using the default principal key. + */ +Datum +pg_tde_delete_default_key(PG_FUNCTION_ARGS) +{ + HeapTuple tuple; + SysScanDesc scan; + Relation rel; + TDEPrincipalKey *principal_key; + TDEPrincipalKey *default_principal_key; + List *dbs = NIL; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + default_principal_key = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); + if (default_principal_key == NULL) + ereport(ERROR, errmsg("default principal key is not set")); + + ereport(LOG, errmsg("Deleting default principal key [%s]", default_principal_key->keyInfo.name)); + + /* + * Take row exclusive lock, as we do not want anybody to create/drop a + * database in parallel. If it happens, its not the end of the world, but + * not ideal. + */ + rel = table_open(DatabaseRelationId, RowExclusiveLock); + scan = systable_beginscan(rel, 0, false, NULL, 0, NULL); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Oid dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid; + + principal_key = GetPrincipalKeyNoDefault(dbOid, LW_EXCLUSIVE); + + /* Check if database uses default principalkey */ + if (pg_tde_is_same_principal_key(default_principal_key, principal_key)) + { + /* + * If database key map is non-empty raise an error, as we cannot + * delete default principal key if there are encrypted tables in + * the database. + */ + if (pg_tde_count_relations(dbOid) != 0) + { + ereport(ERROR, + errmsg("cannot delete default principal key"), + errhint("There are encrypted tables in the database with id: %u.", dbOid)); + } + + /* Remember databases that has no encrypted tables */ + dbs = lappend_oid(dbs, dbOid); + } + } + + /* + * Remove empty key map files for databases that has no encrypted tables + * as we cannot leave reference to the default principal key. + */ + foreach_oid(dbOid, dbs) + { + pg_tde_delete_principal_key(dbOid); + clear_principal_key_cache(dbOid); + } + + systable_endscan(scan); + table_close(rel, RowExclusiveLock); + + /* No databases use default principal key, so we can delete it */ + pg_tde_delete_principal_key(DEFAULT_DATA_TDE_OID); + clear_principal_key_cache(DEFAULT_DATA_TDE_OID); + + LWLockRelease(tde_lwlock_enc_keys()); + + list_free(dbs); + + PG_RETURN_VOID(); +} + PG_FUNCTION_INFO_V1(pg_tde_key_info); Datum pg_tde_key_info(PG_FUNCTION_ARGS) @@ -1024,8 +1177,17 @@ pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey pfree(newKey); } +/* + * Update the default principal key for all databases that use it. + * + * This function is called when the default principal key is rotated. It + * updates all databases that use the old default principal key to use the new + * one. + * + * Caller should hold an exclusive tde_lwlock_enc_keys lock. + */ static void -pg_tde_update_global_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey) +pg_tde_update_default_principal_key_everywhere(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey) { HeapTuple tuple; SysScanDesc scan; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index eed5b7bc569d0..0ca77332a96f6 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -105,6 +105,8 @@ extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_k extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); +extern void pg_tde_delete_principal_key(Oid dbOid); +extern void pg_tde_delete_principal_key_redo(Oid dbOid); const char *tde_sprint_key(InternalKey *k); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 2f08fecb37162..5cf5e9c78b7d9 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -18,6 +18,7 @@ #define XLOG_TDE_WRITE_KEY_PROVIDER 0x30 #define XLOG_TDE_INSTALL_EXTENSION 0x40 #define XLOG_TDE_REMOVE_RELATION_KEY 0x50 +#define XLOG_TDE_DELETE_PRINCIPAL_KEY 0x60 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 68466d5905fc4..5124f209781a2 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -635,7 +635,11 @@ pg_tde_proccess_utility(PlannedStmt *pstmt, if (dbOid != InvalidOid) { - int count = pg_tde_count_relations(dbOid); + int count; + + LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); + count = pg_tde_count_relations(dbOid); + LWLockRelease(tde_lwlock_enc_keys()); if (count > 0) ereport(ERROR, From 20891d54f5df42496c3065a0ae284f82a5dc5f16 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 14:48:21 +0200 Subject: [PATCH 457/796] Remove unused system header includes None of these headers were necessary to include, some because they are already included by postgres.h and others because we have removed the functions which we used to use from them. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 1 - contrib/pg_tde/src/catalog/tde_keyring.c | 2 -- contrib/pg_tde/src/encryption/enc_aes.c | 8 -------- contrib/pg_tde/src/keyring/keyring_api.c | 1 - contrib/pg_tde/src/keyring/keyring_file.c | 3 --- contrib/pg_tde/src/keyring/keyring_vault.c | 1 - 6 files changed, 16 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index d4e377559eefd..29ee3267ab6ab 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -2,7 +2,6 @@ #include #include -#include #include "access/xlog.h" #include "access/xlog_internal.h" diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index b192069052af2..918546e1f8cdb 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -12,8 +12,6 @@ #include "postgres.h" -#include - #include "access/skey.h" #include "access/xlog.h" #include "access/xloginsert.h" diff --git a/contrib/pg_tde/src/encryption/enc_aes.c b/contrib/pg_tde/src/encryption/enc_aes.c index 650645168463b..8c98a3750741a 100644 --- a/contrib/pg_tde/src/encryption/enc_aes.c +++ b/contrib/pg_tde/src/encryption/enc_aes.c @@ -1,15 +1,7 @@ #include "postgres.h" -#include -#include -#include #include #include -#include -#include -#include -#include -#include #include "encryption/enc_aes.h" diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 4af3c82130db7..7e69f4a15575c 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -1,6 +1,5 @@ #include "postgres.h" -#include #include #include diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 391b6dae91d01..291fd38f2fb76 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -12,9 +12,6 @@ #include "postgres.h" -#include -#include - #include "common/file_perm.h" #include "storage/fd.h" #include "utils/wait_event.h" diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 4d896e5ffb675..55357e8827396 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -12,7 +12,6 @@ #include "postgres.h" #include -#include #include "common/base64.h" #include "common/jsonapi.h" From 3fc81cca35d0fb4b262fc165deee3c722f567ddd Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 16:26:37 +0200 Subject: [PATCH 458/796] Fix shared memory clenaup merge issue --- contrib/pg_tde/src/catalog/tde_principal_key.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 1216cb9becc79..2b53d9f561c44 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -42,7 +42,6 @@ #include "access/heapam.h" #include "access/table.h" #include "access/tableam.h" -#include "common/pg_tde_shmem.h" #include "funcapi.h" #include "lib/dshash.h" #include "storage/lwlock.h" From a0285c0f2c2ea2fc863edd540236543d3bd5ac61 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 9 Jun 2025 15:13:25 +0200 Subject: [PATCH 459/796] PG-1607 PG-1652 Unify argument order for KMIP and Vault providers The argument order differed between the CLI tool and the SQL functions which could cause confusion so we pick one order and standardize on it. --- .../pg-tde-change-key-provider.md | 2 +- .../pg_tde/documentation/docs/functions.md | 50 ++++++++++--------- .../vault.md | 4 +- .../docs/how-to/multi-tenant-setup.md | 12 ++--- .../documentation/docs/wal-encryption.md | 10 ++-- contrib/pg_tde/expected/kmip_test.out | 4 +- contrib/pg_tde/expected/vault_v2_test.out | 16 +++--- contrib/pg_tde/pg_tde--1.0-rc.sql | 44 ++++++++-------- contrib/pg_tde/sql/kmip_test.sql | 4 +- contrib/pg_tde/sql/vault_v2_test.sql | 16 +++--- .../pg_tde/src/pg_tde_change_key_provider.c | 10 ++-- .../pg_tde/t/pg_tde_change_key_provider.pl | 14 +++--- 12 files changed, 94 insertions(+), 92 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md index f0dc00e582e47..d372d4a5d166d 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md @@ -31,6 +31,6 @@ Depending on the provider type, the additional parameters are: ```bash pg_tde_change_key_provider [-D ] file -pg_tde_change_key_provider [-D ] vault [] +pg_tde_change_key_provider [-D ] vault-v2 [] pg_tde_change_key_provider [-D ] kmip [] ``` diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 5dae0deaee72c..9ddf5fba3b80c 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -63,15 +63,17 @@ Use the following functions to add the Vault provider: ```sql SELECT pg_tde_add_database_key_provider_vault_v2( 'provider-name', + 'url', + 'mount', 'secret_token_path', - 'url','mount', 'ca_path' ); SELECT pg_tde_add_global_key_provider_vault_v2( 'provider-name', + 'url', + 'mount', 'secret_token_path', - 'url','mount', 'ca_path' ); ``` @@ -81,17 +83,17 @@ These functions change the Vault provider: ```sql SELECT pg_tde_change_database_key_provider_vault_v2( 'provider-name', - 'secret_token_path', 'url', 'mount', + 'secret_token_path', 'ca_path' ); SELECT pg_tde_change_global_key_provider_vault_v2( 'provider-name', - 'secret_token_path', 'url', 'mount', + 'secret_token_path', 'ca_path' ); ``` @@ -115,19 +117,19 @@ Use these functions to add a KMIP provider: ```sql SELECT pg_tde_add_database_key_provider_kmip( 'provider-name', - 'kmip-addr', - `port`, - '/path_to/server_certificate.pem', - '/path_to/client_cert.pem', - '/path_to/client_key.pem' + 'kmip-addr', + port, + '/path_to/client_cert.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' ); SELECT pg_tde_add_global_key_provider_kmip( 'provider-name', - 'kmip-addr', - `port`, - '/path_to/server_certificate.pem', - '/path_to/client_certificate.pem', - '/path_to/client_key.pem' + 'kmip-addr', + port, + '/path_to/client_certificate.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' ); ``` @@ -136,19 +138,19 @@ These functions change the KMIP provider: ```sql SELECT pg_tde_change_database_key_provider_kmip( 'provider-name', - 'kmip-addr', - `port`, - '/path_to/server_certificate.pem', - '/path_to/client_cert.pem', - '/path_to/client_key.pem' + 'kmip-addr', + port, + '/path_to/client_cert.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' ); SELECT pg_tde_change_global_key_provider_kmip( 'provider-name', - 'kmip-addr', - `port`, - '/path_to/server_certificate.pem', - '/path_to/client_certificate.pem', - '/path_to/client_key.pem' + 'kmip-addr', + port, + '/path_to/client_certificate.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' ); ``` diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index 9db21f325ec0f..93bbab47cb9d8 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -10,9 +10,9 @@ You can configure `pg_tde` to use HashiCorp Vault as a global key provider for m ```sql SELECT pg_tde_add_global_key_provider_vault_v2( 'provider-name', - 'secret_token_path', 'url', 'mount', + 'secret_token_path', 'ca_path' ); ``` @@ -30,9 +30,9 @@ The following example is for testing purposes only. Use secure tokens and proper ```sql SELECT pg_tde_add_global_key_provider_vault_v2( 'my-vault', - '/path/to/token_file', 'https://vault.vault.svc.cluster.local:8200', 'secret/data', + '/path/to/token_file', '/path/to/ca_cert.pem' ); ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index b128faef55871..b59c1e7fc585a 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -61,7 +61,7 @@ You must do these steps for every database where you have created the extension. For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql - SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/server_certificate.pem', '/path_to/client_cert.pem', '/path_to/client_key.pem'); + SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/client_cert.pem', '/path_to/client_key.pem', '/path_to/server_certificate.pem'); ``` where: @@ -75,8 +75,8 @@ You must do these steps for every database where you have created the extension. :material-information: Warning: This example is for testing purposes only: - ``` - SELECT pg_tde_add_database_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); + ```sql + SELECT pg_tde_add_database_key_provider_kmip('kmip', '127.0.0.1', 5696, '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); ``` === "With HashiCorp Vault" @@ -84,7 +84,7 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_database_key_provider_vault_v2('provider-name','secret_token_path','url','mount','ca_path'); + SELECT pg_tde_add_database_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); ``` where: @@ -105,13 +105,13 @@ You must do these steps for every database where you have created the extension. This setup is intended for development and stores the keys unencrypted in the specified data file. ```sql - SELECT pg_tde_add_database_key_provider_file('provider-name','/path/to/the/keyring/data.file'); + SELECT pg_tde_add_database_key_provider_file('provider-name', '/path/to/the/keyring/data.file'); ``` :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_add_database_key_provider_file('file-keyring','/tmp/pg_tde_test_local_keyring.per'); + SELECT pg_tde_add_database_key_provider_file('file-keyring', '/tmp/pg_tde_test_local_keyring.per'); ``` 2. Add a principal key diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 21f37ed73ae85..d539a8c6a5e1d 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -19,7 +19,7 @@ Before turning WAL encryption on, you must follow the steps below to create your For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql - SELECT pg_tde_add_global_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/server_certificate.pem', '/path_to/client_cert.pem', '/path_to/client_key.pem'); + SELECT pg_tde_add_global_key_provider_kmip('provider-name', 'kmip-addr', 5696, '/path_to/client_cert.pem', '/path_to/client_key.pem', '/path_to/server_certificate.pem'); ``` where: @@ -33,14 +33,14 @@ Before turning WAL encryption on, you must follow the steps below to create your :material-information: Warning: This example is for testing purposes only: - ``` - SELECT pg_tde_add_key_using_global_key_provider_kmip('kmip','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); + ```sql + SELECT pg_tde_add_key_using_global_key_provider_kmip('kmip', '127.0.0.1', 5696, '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); ``` === "With HashiCorp Vault" ```sql - SELECT pg_tde_add_global_key_provider_vault_v2('provider-name', 'secret_token_path', 'url', 'mount', 'ca_path'); + SELECT pg_tde_add_global_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); ``` where: @@ -56,7 +56,7 @@ Before turning WAL encryption on, you must follow the steps below to create your This setup is **not recommended**, as it is intended for development. The keys are stored **unencrypted** in the specified data file. ```sql - SELECT pg_tde_add_global_key_provider_file('provider-name','/path/to/the/keyring/data.file'); + SELECT pg_tde_add_global_key_provider_file('provider-name', '/path/to/the/keyring/data.file'); ``` 3. Create principal key diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index 66cf91d658680..b363a6db94b2f 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -1,5 +1,5 @@ CREATE EXTENSION pg_tde; -SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); +SELECT pg_tde_add_database_key_provider_kmip('kmip-prov', '127.0.0.1', 5696, '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); pg_tde_add_database_key_provider_kmip --------------------------------------- @@ -35,6 +35,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to kmip server -SELECT pg_tde_add_database_key_provider_kmip('will-not-work','127.0.0.1', 61, '/tmp/server_certificate.pem', '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); +SELECT pg_tde_add_database_key_provider_kmip('will-not-work', '127.0.0.1', 61, '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); ERROR: SSL error: BIO_do_connect failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 291d230dd1df9..3a092b86dadfe 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,14 +1,14 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','https://127.0.0.1:8200','DUMMY-TOKEN',:'cacert_file'); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- (1 row) -- FAILS -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); ERROR: Invalid HTTP response from keyring provider "vault-incorrect": 404 CREATE TABLE test_enc( id SERIAL, @@ -17,13 +17,13 @@ CREATE TABLE test_enc( ) USING tde_heap; ERROR: principal key not configured HINT: create one using pg_tde_set_key before using encrypted tables -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','https://127.0.0.1:8200','secret',:'cacert_file'); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', :'cacert_file'); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- (1 row) -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-v2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -53,15 +53,15 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', 'https://127.0.0.1:61', 'secret', :'root_token_file', :'cacert_file'); ERROR: HTTP(S) request to keyring provider "will-not-work" failed -- Changing provider fails if we can't connect to vault -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:61', 'secret', :'root_token_file', :'cacert_file'); ERROR: HTTP(S) request to keyring provider "vault-v2" failed -- HTTPS without cert fails -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:8200', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', NULL); ERROR: HTTP(S) request to keyring provider "vault-v2" failed -- HTTP against HTTPS server fails -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:8200', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'http://127.0.0.1:8200', 'secret', :'root_token_file', NULL); ERROR: Listing secrets of "http://127.0.0.1:8200" at mountpoint "secret" failed DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index c9092e81ee66e..89afa1d2f0a01 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -19,35 +19,35 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_database_key_provider_vault_v2(provider_name TEXT, - vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, + vault_token_path TEXT, vault_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_add_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, + 'tokenPath' VALUE vault_token_path, 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_add_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, - kmip_ca_path TEXT, kmip_cert_path TEXT, - kmip_key_path TEXT) + kmip_key_path TEXT, + kmip_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_add_database_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); + 'keyPath' VALUE kmip_key_path, + 'caPath' VALUE kmip_ca_path)); END; CREATE FUNCTION pg_tde_list_all_database_key_providers @@ -86,35 +86,35 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_add_global_key_provider_vault_v2(provider_name TEXT, - vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, + vault_token_path TEXT, vault_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_add_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, + 'tokenPath' VALUE vault_token_path, 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_add_global_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, - kmip_ca_path TEXT, kmip_cert_path TEXT, - kmip_key_path TEXT) + kmip_key_path TEXT, + kmip_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_add_global_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); + 'keyPath' VALUE kmip_key_path, + 'caPath' VALUE kmip_ca_path)); END; -- Key Provider Management @@ -133,26 +133,26 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_database_key_provider_vault_v2(provider_name TEXT, - vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, + vault_token_path TEXT, vault_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_change_database_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, + 'tokenPath' VALUE vault_token_path, 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_change_database_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, - kmip_ca_path TEXT, kmip_cert_path TEXT, - kmip_key_path TEXT) + kmip_key_path TEXT, + kmip_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC @@ -180,35 +180,35 @@ BEGIN ATOMIC END; CREATE FUNCTION pg_tde_change_global_key_provider_vault_v2(provider_name TEXT, - vault_token_path TEXT, vault_url TEXT, vault_mount_path TEXT, + vault_token_path TEXT, vault_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_change_global_key_provider('vault-v2', provider_name, json_object('url' VALUE vault_url, - 'tokenPath' VALUE vault_token_path, 'mountPath' VALUE vault_mount_path, + 'tokenPath' VALUE vault_token_path, 'caPath' VALUE vault_ca_path)); END; CREATE FUNCTION pg_tde_change_global_key_provider_kmip(provider_name TEXT, kmip_host TEXT, kmip_port INT, - kmip_ca_path TEXT, kmip_cert_path TEXT, - kmip_key_path TEXT) + kmip_key_path TEXT, + kmip_ca_path TEXT) RETURNS VOID LANGUAGE SQL BEGIN ATOMIC SELECT pg_tde_change_global_key_provider('kmip', provider_name, json_object('host' VALUE kmip_host, 'port' VALUE kmip_port, - 'caPath' VALUE kmip_ca_path, 'certPath' VALUE kmip_cert_path, - 'keyPath' VALUE kmip_key_path)); + 'keyPath' VALUE kmip_key_path, + 'caPath' VALUE kmip_ca_path)); END; CREATE FUNCTION pg_tde_is_encrypted(relation REGCLASS) diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index ec8f6102e75f4..eedc14c6e7f5e 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -1,6 +1,6 @@ CREATE EXTENSION pg_tde; -SELECT pg_tde_add_database_key_provider_kmip('kmip-prov','127.0.0.1', 5696, '/tmp/server_certificate.pem', '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); +SELECT pg_tde_add_database_key_provider_kmip('kmip-prov', '127.0.0.1', 5696, '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); CREATE TABLE test_enc( @@ -20,6 +20,6 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to kmip server -SELECT pg_tde_add_database_key_provider_kmip('will-not-work','127.0.0.1', 61, '/tmp/server_certificate.pem', '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem'); +SELECT pg_tde_add_database_key_provider_kmip('will-not-work', '127.0.0.1', 61, '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index a1f5a92233db2..e43dc3798d7fe 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -3,9 +3,9 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect',:'root_token_file','https://127.0.0.1:8200','DUMMY-TOKEN',:'cacert_file'); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); -- FAILS -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-incorrect'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); CREATE TABLE test_enc( id SERIAL, @@ -13,8 +13,8 @@ CREATE TABLE test_enc( PRIMARY KEY (id) ) USING tde_heap; -SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2',:'root_token_file','https://127.0.0.1:8200','secret',:'cacert_file'); -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key','vault-v2'); +SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', :'cacert_file'); +SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-v2'); CREATE TABLE test_enc( id SERIAL, @@ -33,15 +33,15 @@ SELECT pg_tde_verify_key(); DROP TABLE test_enc; -- Creating provider fails if we can't connect to vault -SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); +SELECT pg_tde_add_database_key_provider_vault_v2('will-not-work', 'https://127.0.0.1:61', 'secret', :'root_token_file', :'cacert_file'); -- Changing provider fails if we can't connect to vault -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:61', 'secret', :'cacert_file'); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:61', 'secret', :'root_token_file', :'cacert_file'); -- HTTPS without cert fails -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'https://127.0.0.1:8200', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', NULL); -- HTTP against HTTPS server fails -SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', :'root_token_file', 'http://127.0.0.1:8200', 'secret', NULL); +SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'http://127.0.0.1:8200', 'secret', :'root_token_file', NULL); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index 79d69ae7ccbf0..c66eb874de1ed 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -28,7 +28,7 @@ help(void) puts("Depending on the provider type, the complete parameter list is:"); puts(""); puts("pg_tde_change_key_provider [-D ] file "); - puts("pg_tde_change_key_provider [-D ] vault-v2 []"); + puts("pg_tde_change_key_provider [-D ] vault-v2 []"); puts("pg_tde_change_key_provider [-D ] kmip []"); puts(""); printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); @@ -197,8 +197,8 @@ main(int argc, char *argv[]) if (!build_json(json, 4, "url", argv[4 + argstart], - "tokenPath", argv[5 + argstart], - "mountPath", argv[6 + argstart], + "mountPath", argv[5 + argstart], + "tokenPath", argv[6 + argstart], "caPath", (argc - argstart > 7 ? argv[7 + argstart] : ""))) { exit(1); @@ -220,9 +220,9 @@ main(int argc, char *argv[]) if (!build_json(json, 5, "host", argv[4 + argstart], "port", argv[5 + argstart], - "caPath", (argc - argstart > 8 ? argv[8 + argstart] : ""), "certPath", argv[6 + argstart], - "keyPath", argv[7 + argstart])) + "keyPath", argv[7 + argstart], + "caPath", argc - argstart > 8 ? argv[8 + argstart] : "")) { exit(1); } diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index f00e394840b74..47380a940bebd 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -67,8 +67,8 @@ 'database-provider', 'vault-v2', 'https://vault-server.example:8200/', - $token_file, 'mount-path', + $token_file, '/tmp/ca_path', ], qr/Key provider updated successfully!/, @@ -88,13 +88,13 @@ 'postgres', q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} )); -is($options->{tokenPath}, $token_file, - 'tokenPath is set correctly for vault-v2 provider'); is( $options->{url}, 'https://vault-server.example:8200/', 'url is set correctly for vault-v2 provider'); is($options->{mountPath}, 'mount-path', 'mount path is set correctly for vault-v2 provider'); +is($options->{tokenPath}, $token_file, + 'tokenPath is set correctly for vault-v2 provider'); is($options->{caPath}, '/tmp/ca_path', 'CA path is set correctly for vault-v2 provider'); @@ -108,8 +108,8 @@ 'database-provider', 'vault-v2', 'http://vault-server.example:8200/', - $token_file, 'mount-path-2', + $token_file, ], qr/Key provider updated successfully!/, 'updates key provider to vault-v2 type with http'); @@ -128,13 +128,13 @@ 'postgres', q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} )); -is($options->{tokenPath}, $token_file, - 'tokenPath is set correctly for vault-v2 provider'); is( $options->{url}, 'http://vault-server.example:8200/', 'url is set correctly for vault-v2 provider'); is($options->{mountPath}, 'mount-path-2', 'mount path is set correctly for vault-v2 provider'); +is($options->{tokenPath}, $token_file, + 'tokenPath is set correctly for vault-v2 provider'); is($options->{caPath}, '', 'CA path is set correctly for vault-v2 provider'); $node->stop; @@ -189,8 +189,8 @@ 'global-provider', 'vault-v2', 'http://vault-server.example:8200/', - $token_file, 'mount-path', + $token_file, '/tmp/ca_path', ], qr/Key provider updated successfully!/, From 9f44f1be490e836dd52d6d34b9ccb5345a08bd31 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 10 Jun 2025 16:31:00 +0200 Subject: [PATCH 460/796] Be consistent about that finding no key in the provider is a success All different provider types except Vault treated finding no key as SUCCESS but with NULL as the key. Let's do this for Vault too which slightly simplifies the callers which used to have to understand both ways to handle a key not existing. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 4 ++-- contrib/pg_tde/src/keyring/keyring_vault.c | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 2b53d9f561c44..d9990319979ce 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -256,7 +256,7 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, keyInfo = KeyringGetKey(new_keyring, key_name, &kr_ret); - if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE) + if (kr_ret != KEYRING_CODE_SUCCESS) { ereport(ERROR, errmsg("failed to retrieve principal key from keyring provider :\"%s\"", new_keyring->provider_name), @@ -333,7 +333,7 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) new_keyring = GetKeyProviderByID(xlrec->keyringId, xlrec->databaseId); keyInfo = KeyringGetKey(new_keyring, xlrec->keyName, &kr_ret); - if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE) + if (kr_ret != KEYRING_CODE_SUCCESS) { ereport(ERROR, errmsg("failed to retrieve principal key from keyring provider: \"%s\"", new_keyring->provider_name), diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 55357e8827396..bd7d66c1b293a 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -236,7 +236,6 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (httpCode == 404) { - *return_code = KEYRING_CODE_RESOURCE_NOT_AVAILABLE; goto cleanup; } From ca790cd2b486606305ac431a1e820a65ec5c5342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 10 Jun 2025 12:53:33 +0200 Subject: [PATCH 461/796] Remove some useless comments at top of files We're long past the years where someone is gonna read a print-out of these files and thus need a reminder of what the file name is. And even if someone wanted to do that it's easy to have your printer program automatically add the file name to the header or footer of each page now-a-days. --- contrib/pg_tde/Makefile | 2 -- contrib/pg_tde/pg_tde--1.0-rc.sql | 2 -- contrib/pg_tde/pg_tde.control | 1 - contrib/pg_tde/src/access/pg_tde_xlog.c | 12 ++---------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 12 ++---------- contrib/pg_tde/src/catalog/tde_keyring.c | 12 ++---------- contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 11 ++--------- contrib/pg_tde/src/catalog/tde_principal_key.c | 12 ++---------- contrib/pg_tde/src/common/pg_tde_utils.c | 11 ----------- contrib/pg_tde/src/include/access/pg_tde_fe_init.h | 8 ++------ contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 7 ------- contrib/pg_tde/src/include/access/pg_tde_xlog.h | 8 ++------ .../pg_tde/src/include/access/pg_tde_xlog_smgr.h | 8 ++------ .../pg_tde/src/include/catalog/tde_global_space.h | 10 ++-------- contrib/pg_tde/src/include/catalog/tde_keyring.h | 11 +++-------- .../pg_tde/src/include/catalog/tde_principal_key.h | 11 +++-------- contrib/pg_tde/src/include/common/pg_tde_utils.h | 7 ------- contrib/pg_tde/src/include/encryption/enc_aes.h | 11 +++-------- contrib/pg_tde/src/include/encryption/enc_tde.h | 11 +++-------- contrib/pg_tde/src/include/keyring/keyring_api.h | 8 -------- contrib/pg_tde/src/include/keyring/keyring_curl.h | 11 ++--------- contrib/pg_tde/src/include/keyring/keyring_file.h | 10 ++-------- contrib/pg_tde/src/include/keyring/keyring_kmip.h | 11 ++--------- .../pg_tde/src/include/keyring/keyring_kmip_impl.h | 11 ++--------- contrib/pg_tde/src/include/keyring/keyring_vault.h | 11 ++--------- contrib/pg_tde/src/include/pg_tde.h | 7 ------- contrib/pg_tde/src/include/pg_tde_defines.h | 11 +++-------- contrib/pg_tde/src/include/pg_tde_event_capture.h | 6 ------ contrib/pg_tde/src/include/pg_tde_fe.h | 11 +++-------- contrib/pg_tde/src/include/pg_tde_guc.h | 10 ++-------- contrib/pg_tde/src/include/smgr/pg_tde_smgr.h | 8 -------- contrib/pg_tde/src/keyring/keyring_curl.c | 11 ++--------- contrib/pg_tde/src/keyring/keyring_file.c | 12 ++---------- contrib/pg_tde/src/keyring/keyring_kmip.c | 11 ++--------- contrib/pg_tde/src/keyring/keyring_vault.c | 11 ++--------- contrib/pg_tde/src/pg_tde.c | 13 +++---------- contrib/pg_tde/src/pg_tde_event_capture.c | 11 ++--------- contrib/pg_tde/src/pg_tde_guc.c | 12 ++---------- 38 files changed, 63 insertions(+), 300 deletions(-) diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index f10ad764ecf9c..a8b7458b411f8 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -1,5 +1,3 @@ -# contrib/pg_tde/Makefile - PGFILEDESC = "pg_tde access method" MODULE_big = pg_tde EXTENSION = pg_tde diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 89afa1d2f0a01..897e1664bbbbb 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -1,5 +1,3 @@ -/* contrib/pg_tde/pg_tde--1.0-rc.sql */ - -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_tde" to load this file. \quit diff --git a/contrib/pg_tde/pg_tde.control b/contrib/pg_tde/pg_tde.control index b72231dd1aa1e..73d135807fbe4 100644 --- a/contrib/pg_tde/pg_tde.control +++ b/contrib/pg_tde/pg_tde.control @@ -1,4 +1,3 @@ -# pg_tde extension comment = 'pg_tde access method' default_version = '1.0-rc' module_pathname = '$libdir/pg_tde' diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index c0e1ba07510d4..131ee69914f8e 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * tdeheap_xlog.c - * TDE XLog resource manager - * - * - * IDENTIFICATION - * src/access/pg_tde_xlog.c - * - *------------------------------------------------------------------------- +/* + * TDE XLog resource manager */ #include "postgres.h" diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index aec73ba3612ef..bd08072aefdcc 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_xlog_smgr.c - * Encrypted XLog storage manager - * - * - * IDENTIFICATION - * src/access/pg_tde_xlog_smgr.c - * - *------------------------------------------------------------------------- +/* + * Encrypted XLog storage manager */ #include "postgres.h" diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 918546e1f8cdb..80f5337066f1f 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * tde_keyring.c - * Deals with the tde keyring configuration - * routines. - * - * IDENTIFICATION - * contrib/pg_tde/src/catalog/tde_keyring.c - * - *------------------------------------------------------------------------- +/* + * Deals with the tde keyring configuration routines. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index f9e8a1db87d85..e8714cb02692b 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -1,14 +1,7 @@ -/*------------------------------------------------------------------------- - * - * tde_keyring_parse_opts.c - * Parser routines for the keyring JSON options +/* + * Parser routines for the keyring JSON options * * We expect one-dimentional JSON object with scalar fields - * - * IDENTIFICATION - * contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c - * - *------------------------------------------------------------------------- */ #include "postgres.h" diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index d9990319979ce..2ad5b1f4c779f 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * tde_principal_key.c - * Deals with the tde principal key configuration catalog - * routines. - * - * IDENTIFICATION - * contrib/pg_tde/src/catalog/tde_principal_key.c - * - *------------------------------------------------------------------------- +/* + * Deals with the tde principal key configuration catalog routines. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/common/pg_tde_utils.c b/contrib/pg_tde/src/common/pg_tde_utils.c index 4a6602ce05f3e..ce2beaf3a9ad8 100644 --- a/contrib/pg_tde/src/common/pg_tde_utils.c +++ b/contrib/pg_tde/src/common/pg_tde_utils.c @@ -1,14 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_utils.c - * Utility functions. - * - * IDENTIFICATION - * contrib/pg_tde/src/pg_tde_utils.c - * - *------------------------------------------------------------------------- - */ - #include "postgres.h" #include "common/pg_tde_utils.h" diff --git a/contrib/pg_tde/src/include/access/pg_tde_fe_init.h b/contrib/pg_tde/src/include/access/pg_tde_fe_init.h index 3b2a6f2324b25..784c8952dee84 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_fe_init.h +++ b/contrib/pg_tde/src/include/access/pg_tde_fe_init.h @@ -1,9 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_fe.h - * Frontened definitions for encrypted XLog storage manager - * - *------------------------------------------------------------------------- +/* + * Frontened definitions for encrypted XLog storage manager */ #ifndef PG_TDE_FE_INIT_H diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 0ca77332a96f6..f7d99f735c890 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -1,10 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_tdemap.h - * TDE relation fork manapulation. - * - *------------------------------------------------------------------------- - */ #ifndef PG_TDE_MAP_H #define PG_TDE_MAP_H diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 5cf5e9c78b7d9..1dcd385b16acc 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -1,9 +1,5 @@ -/*------------------------------------------------------------------------- - * - * tdeheap_xlog.h - * TDE XLog resource manager - * - *------------------------------------------------------------------------- +/* + * TDE XLog resource manager */ #ifndef PG_TDE_XLOG_H diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index ce8df4b8769e8..cb714ed34438a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -1,9 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_xlog_smgr.h - * Encrypted XLog storage manager - * - *------------------------------------------------------------------------- +/* + * Encrypted XLog storage manager */ #ifndef PG_TDE_XLOGSMGR_H diff --git a/contrib/pg_tde/src/include/catalog/tde_global_space.h b/contrib/pg_tde/src/include/catalog/tde_global_space.h index cd867aba3f1e5..faa2098a27f0c 100644 --- a/contrib/pg_tde/src/include/catalog/tde_global_space.h +++ b/contrib/pg_tde/src/include/catalog/tde_global_space.h @@ -1,11 +1,5 @@ -/*------------------------------------------------------------------------- - * - * tde_global_space.h - * Global catalog key management - * - * src/include/catalog/tde_global_space.h - * - *------------------------------------------------------------------------- +/* + * Global catalog key management */ #ifndef TDE_GLOBAL_CATALOG_H diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index 5dd09f3e2c12a..db4a83ca02194 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * tde_keyring.h - * TDE catalog handling - * - * src/include/catalog/tde_keyring.h - * - *------------------------------------------------------------------------- +/* + * TDE catalog handling */ + #ifndef TDE_KEYRING_H #define TDE_KEYRING_H diff --git a/contrib/pg_tde/src/include/catalog/tde_principal_key.h b/contrib/pg_tde/src/include/catalog/tde_principal_key.h index 185a92292954a..a3294c73b02cb 100644 --- a/contrib/pg_tde/src/include/catalog/tde_principal_key.h +++ b/contrib/pg_tde/src/include/catalog/tde_principal_key.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * tde_principal_key.h - * TDE principal key handling - * - * src/include/catalog/tde_principal_key.h - * - *------------------------------------------------------------------------- +/* + * TDE principal key handling */ + #ifndef PG_TDE_PRINCIPAL_KEY_H #define PG_TDE_PRINCIPAL_KEY_H diff --git a/contrib/pg_tde/src/include/common/pg_tde_utils.h b/contrib/pg_tde/src/include/common/pg_tde_utils.h index fc2062a0692ad..f91b53f1caa37 100644 --- a/contrib/pg_tde/src/include/common/pg_tde_utils.h +++ b/contrib/pg_tde/src/include/common/pg_tde_utils.h @@ -1,10 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_utils.h - * src/include/common/pg_tde_utils.h - * - *------------------------------------------------------------------------- - */ #ifndef PG_TDE_UTILS_H #define PG_TDE_UTILS_H diff --git a/contrib/pg_tde/src/include/encryption/enc_aes.h b/contrib/pg_tde/src/include/encryption/enc_aes.h index 0b5269a5456ec..68b9416972fd6 100644 --- a/contrib/pg_tde/src/include/encryption/enc_aes.h +++ b/contrib/pg_tde/src/include/encryption/enc_aes.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * end_aes.h - * AES Encryption / Decryption routines using OpenSSL - * - * src/include/encryption/enc_aes.h - * - *------------------------------------------------------------------------- +/* + * AES Encryption / Decryption routines using OpenSSL */ + #ifndef ENC_AES_H #define ENC_AES_H diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index 879733cc0a1af..93148dba244a6 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * enc_tde.h - * Encryption / Decryption of functions for TDE - * - * src/include/encryption/enc_tde.h - * - *------------------------------------------------------------------------- +/* + * Encryption / Decryption of functions for TDE */ + #ifndef ENC_TDE_H #define ENC_TDE_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index a137f67f94041..e282323c28377 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -1,11 +1,3 @@ -/*------------------------------------------------------------------------- - * - * keyring_api.h - * src/include/keyring/keyring_api.h - * - *------------------------------------------------------------------------- - */ - #ifndef KEYRING_API_H #define KEYRING_API_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_curl.h b/contrib/pg_tde/src/include/keyring/keyring_curl.h index 6960ad0036d1b..a164a39b51d05 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_curl.h +++ b/contrib/pg_tde/src/include/keyring/keyring_curl.h @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_curl.h - * Contains common curl related methods. - * - * IDENTIFICATION - * src/include/keyring/keyring_curl.h - * - *------------------------------------------------------------------------- +/* + * Contains common curl related methods. */ #ifndef KEYRING_CURL_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_file.h b/contrib/pg_tde/src/include/keyring/keyring_file.h index a72e643464116..a671070dc28d5 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_file.h +++ b/contrib/pg_tde/src/include/keyring/keyring_file.h @@ -1,11 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_file.h - * File vault implementation - * - * src/include/keyring/keyring_file.h - * - *------------------------------------------------------------------------- +/* + * File vault implementation */ #ifndef KEYRING_FILE_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_kmip.h b/contrib/pg_tde/src/include/keyring/keyring_kmip.h index fc55a5981351b..98b7e7863c815 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_kmip.h +++ b/contrib/pg_tde/src/include/keyring/keyring_kmip.h @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_kmip.h - * KMIP based keyring provider - * - * IDENTIFICATION - * src/include/keyring/keyring_kmip.h - * - *------------------------------------------------------------------------- +/* + * KMIP based keyring provider */ #ifndef KEYRING_KMIP_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_kmip_impl.h b/contrib/pg_tde/src/include/keyring/keyring_kmip_impl.h index df6f46105054c..357ff6c703e81 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_kmip_impl.h +++ b/contrib/pg_tde/src/include/keyring/keyring_kmip_impl.h @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_kmip_impl.h - * Intenrals for the KMIP based keyring provider - * - * IDENTIFICATION - * src/include/keyring/keyring_kmip_impl.h - * - *------------------------------------------------------------------------- +/* + * Internals for the KMIP based keyring provider */ #ifndef KEYRING_KMIP_IMPL_H diff --git a/contrib/pg_tde/src/include/keyring/keyring_vault.h b/contrib/pg_tde/src/include/keyring/keyring_vault.h index 13f7b957e7d78..918027882466e 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_vault.h +++ b/contrib/pg_tde/src/include/keyring/keyring_vault.h @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_vault.h - * HashiCorp Vault 2 based keyring provider - * - * IDENTIFICATION - * src/include/keyring/keyring_vault.h - * - *------------------------------------------------------------------------- +/* + * HashiCorp Vault 2 based keyring provider */ #ifndef KEYRING_VAULT_H diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index cdeff802ed7b1..652dc8eea18c6 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -1,10 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde.h - * src/include/pg_tde.h - * - *------------------------------------------------------------------------- - */ #ifndef PG_TDE_H #define PG_TDE_H diff --git a/contrib/pg_tde/src/include/pg_tde_defines.h b/contrib/pg_tde/src/include/pg_tde_defines.h index c18faff36052f..b8daf01e0b7b8 100644 --- a/contrib/pg_tde/src/include/pg_tde_defines.h +++ b/contrib/pg_tde/src/include/pg_tde_defines.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_defines.h - * Debug definitions for pg_tde - * - * src/include/pg_tde_defines.h - * - *------------------------------------------------------------------------- +/* + * Debug definitions for pg_tde */ + #ifndef PG_TDE_DEFINES_H #define PG_TDE_DEFINES_H diff --git a/contrib/pg_tde/src/include/pg_tde_event_capture.h b/contrib/pg_tde/src/include/pg_tde_event_capture.h index cd9ddddf45b41..8a5e1d64bdb29 100644 --- a/contrib/pg_tde/src/include/pg_tde_event_capture.h +++ b/contrib/pg_tde/src/include/pg_tde_event_capture.h @@ -1,9 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_event_capture.h - * - *------------------------------------------------------------------------- - */ #ifndef PG_TDE_EVENT_CAPTURE_H #define PG_TDE_EVENT_CAPTURE_H diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index 79c1c4f4fa72e..6ef811b2b98ec 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -1,12 +1,7 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_fe.h - * TDE redefinitions for frontend included code - * - * src/include/pg_tde_fe.h - * - *------------------------------------------------------------------------- +/* + * TDE redefinitions for frontend included code */ + #ifndef PG_TDE_EREPORT_H #define PG_TDE_EREPORT_H diff --git a/contrib/pg_tde/src/include/pg_tde_guc.h b/contrib/pg_tde/src/include/pg_tde_guc.h index b576e7900b356..a48d0fd6f0294 100644 --- a/contrib/pg_tde/src/include/pg_tde_guc.h +++ b/contrib/pg_tde/src/include/pg_tde_guc.h @@ -1,11 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_guc.h - * GUC variables for pg_tde - * - * src/include/pg_tde_guc.h - * - *------------------------------------------------------------------------- +/* + * GUC variables for pg_tde */ #ifndef TDE_GUC_H diff --git a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h index 31a0d525f4fe6..a97421cfef914 100644 --- a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h +++ b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h @@ -1,11 +1,3 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_smgr.h - * src/include/smgr/pg_tde_smgr.h - * - *------------------------------------------------------------------------- - */ - #ifndef PG_TDE_SMGR_H #define PG_TDE_SMGR_H diff --git a/contrib/pg_tde/src/keyring/keyring_curl.c b/contrib/pg_tde/src/keyring/keyring_curl.c index 2a4abb601e532..1f2966a8833ba 100644 --- a/contrib/pg_tde/src/keyring/keyring_curl.c +++ b/contrib/pg_tde/src/keyring/keyring_curl.c @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_curl.c - * Contains common curl related methods. - * - * IDENTIFICATION - * contrib/pg_tde/src/keyring/keyring_curl.c - * - *------------------------------------------------------------------------- +/* + * Contains common curl related methods. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/keyring/keyring_file.c b/contrib/pg_tde/src/keyring/keyring_file.c index 291fd38f2fb76..a394260cac7fd 100644 --- a/contrib/pg_tde/src/keyring/keyring_file.c +++ b/contrib/pg_tde/src/keyring/keyring_file.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_file.c - * Implements the file provider keyring - * routines. - * - * IDENTIFICATION - * contrib/pg_tde/src/keyring/keyring_file.c - * - *------------------------------------------------------------------------- +/* + * Implements the file provider keyring routines. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index 12dbcab8d163d..15dc35692dc14 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_kmip.c - * KMIP based keyring provider - * - * IDENTIFICATION - * contrib/pg_tde/src/keyring/keyring_kmip.c - * - *------------------------------------------------------------------------- +/* + * KMIP based keyring provider */ #include "postgres.h" diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index bd7d66c1b293a..e03aca818d2c2 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * keyring_vault.c - * HashiCorp Vault 2 based keyring provider - * - * IDENTIFICATION - * contrib/pg_tde/src/keyring/keyring_vault.c - * - *------------------------------------------------------------------------- +/* + * HashiCorp Vault 2 based keyring provider */ #include "postgres.h" diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 216d6a1f3c6cb..d7738ed869dac 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -1,13 +1,6 @@ -/*------------------------------------------------------------------------- - * - * pg_tde.c - * Main file: setup GUCs, shared memory, hooks and other general-purpose - * routines. - * - * IDENTIFICATION - * contrib/pg_tde/src/pg_tde.c - * - *------------------------------------------------------------------------- +/* + * Main file: setup GUCs, shared memory, hooks and other general-purpose + * routines. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 5124f209781a2..43ee36cbecfd9 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -1,12 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_event_capture.c - * event trigger logic to identify if we are creating the encrypted table or not. - * - * IDENTIFICATION - * contrib/pg_tde/src/pg_tde_event_trigger.c - * - *------------------------------------------------------------------------- +/* + * event trigger logic to identify if we are creating the encrypted table or not. */ #include "postgres.h" diff --git a/contrib/pg_tde/src/pg_tde_guc.c b/contrib/pg_tde/src/pg_tde_guc.c index 8b82c3338bc71..fe724d1c91595 100644 --- a/contrib/pg_tde/src/pg_tde_guc.c +++ b/contrib/pg_tde/src/pg_tde_guc.c @@ -1,13 +1,5 @@ -/*------------------------------------------------------------------------- - * - * pg_tde_guc.c - * GUC variables for pg_tde - * - * - * IDENTIFICATION - * src/pg_tde_guc.c - * - *------------------------------------------------------------------------- +/* + * GUC variables for pg_tde */ #include "postgres.h" From ad80ac2c53513090046c8ff515e2e601a9e6e665 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 11 Jun 2025 11:10:03 +0300 Subject: [PATCH 462/796] Documentation: standardized headers + pg_waldump cleanup (#406) Headers now do not have `pg_tde` mentioned (removed ``) and improved the text flow of pg_waldump. Other minor text fixes and linting done. --- .../pg_tde/documentation/docs/architecture/index.md | 2 +- .../docs/command-line-tools/pg-waldump.md | 9 ++++----- contrib/pg_tde/documentation/docs/faq.md | 8 ++++---- contrib/pg_tde/documentation/docs/functions.md | 10 +++------- contrib/pg_tde/documentation/docs/index.md | 2 +- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 0eff7081d4b07..b643a393fac95 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -175,7 +175,7 @@ For such situations, `pg_tde` also provides [command line tools](../command-line ## User interface -### Setting up `pg_tde` +### Setting up pg_tde To use `pg_tde`, users are required to: diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md index b38441d58e640..a3ac50df4df02 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md @@ -4,10 +4,9 @@ To read encrypted WAL records, `pg_waldump` supports the following additional arguments: -* `keyring_path`: the directory where keyring configuration files for WAL are stored. These files include: - * `1664_keys` - * `1664_providers` +* `keyring_path` is the directory where the keyring configuration files for WAL are stored. The following files are included: + * `1664_keys` + * `1664_providers` !!! note - - `pg_waldump` will not decrypt WAL unless the `keyring_path` is set. + `pg_waldump` does not decrypt WAL unless the `keyring_path` is set. diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index 902e0a18b49fc..df80bea17f555 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -61,7 +61,7 @@ Thus, to protect your sensitive data, consider using TDE to encrypt it at the ta * Regular monitoring and auditing * Additional data protection for sensitive fields (e.g., application-layer encryption) -## How does `pg_tde` make my data safe? +## How does pg_tde make my data safe? `pg_tde` uses two keys to encrypt data: @@ -106,7 +106,7 @@ Consider encrypting only tables that store sensitive data. You can decide what t We advise encrypting the whole database only if all your data is sensitive, like PII, or if there is no other way to comply with data safety requirements. -## What cipher mechanisms are used by `pg_tde`? +## What cipher mechanisms are used by pg_tde? `pg_tde` currently uses a AES-CBC-128 algorithm. First the internal keys in the datafile are encrypted using the principal key with AES-CBC-128, then the file data itself is again encrypted using AES-CBC-128 with the internal key. @@ -141,7 +141,7 @@ After that, no database restart is required. When you create or alter the table If you lose encryption keys, especially, the principal key, the data is lost. That's why it's critical to back up your encryption keys securely and use the Key Management service for key management. -## Can I use `pg_tde` in a multi-tenant setup? +## Can I use pg_tde in a multi-tenant setup? Multi-tenancy is the type of architecture where multiple users, or tenants, share the same resource. It can be a database, a schema or an entire cluster. @@ -159,6 +159,6 @@ Since the encryption happens on the database level, it makes no difference for y To restore from an encrypted backup, you must have the same principal encryption key, which was used to encrypt files in your backup. -## I'm using OpenSSL in FIPS mode and need to use `pg_tde`. Does `pg_tde` comply with FIPS requirements? Can I use my own FIPS-mode OpenSSL library with `pg_tde`? +## I'm using OpenSSL in FIPS mode and need to use pg_tde. Does pg_tde comply with FIPS requirements? Can I use my own FIPS-mode OpenSSL library with pg_tde? Yes. `pg_tde` works with the FIPS-compliant version of OpenSSL, whether it is provided by your operating system or if you use your own OpenSSL libraries. If you use your own libraries, make sure they are FIPS certified. diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 9ddf5fba3b80c..0a6a7afde256e 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -15,7 +15,7 @@ However, database owners can run the “view keys” and “set principal key” A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: -* local file (not for production use) +* local file (not recommended for production use) * Hashicorp Vault / OpenBao * KMIP compatible providers @@ -26,7 +26,7 @@ Key provider management includes the following operations: * deleting a key provider, * listing key providers. -### Add a provider +### Add a key provider You can add a new key provider using the provided functions, which are implemented for each provider type. @@ -35,7 +35,7 @@ There are two functions to add a key provider: one function adds it for the curr * `pg_tde_add_database_key_provider_('provider-name', )` * `pg_tde_add_global_key_provider_('provider-name', )` -When you add a new provider, the provider name must be unqiue in the scope. But a local database provider and a global provider can have the same name. +When you add a new provider, the provider name must be unique in the scope. But a local database provider and a global provider can have the same name. ### Change an existing provider @@ -106,8 +106,6 @@ where: * `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * **[optional]** `ca_path` is the path of the CA file used for SSL verification - - #### Adding or modifying KMIP providers The KMIP provider uses a remote KMIP server. @@ -167,7 +165,6 @@ where: !!! note The specified access parameters require permission to read and write keys at the server. - ### Adding or modifying local keyfile providers This provider manages database keys using a local keyfile. @@ -210,7 +207,6 @@ where: * `provider-name` is the name of the provider. You can specify any name, it's for you to identify the provider. * `/path/to/the/key/provider/data.file` is the path to the key provider file. - ### Delete a provider These functions delete an existing provider in the current database or in the global scope: diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 13512dd28cb75..30bf17a431bd2 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -1,4 +1,4 @@ -# `pg_tde` Documentation +# pg_tde Documentation `pg_tde` is the open source, community driven and futureproof PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. `pg_tde` ensures that the data stored on disk is encrypted, and that no one can read it without the proper encryption keys, even if they gain access to the physical storage media. From 73797914c7f800c848e1565e3b3cd22309be6064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 11 Jun 2025 11:46:39 +0200 Subject: [PATCH 463/796] Remove provider_ prefix from parameter names There was no reason for name and type to be prefixed in the add/change key provider functions while options was not. The output from the list_providers functions had them for name and type, but not id nor options. So remove the prefixes there aswell. In other functions the prefixes serve to show what parameters is about the provider and which are about something else. --- .../pg_tde/expected/default_principal_key.out | 4 +- contrib/pg_tde/expected/key_provider.out | 50 +++++++++---------- contrib/pg_tde/pg_tde--1.0-rc.sql | 16 +++--- contrib/pg_tde/sql/default_principal_key.sql | 2 +- contrib/pg_tde/sql/key_provider.sql | 12 ++--- .../pg_tde/t/expected/change_key_provider.out | 18 +++---- .../pg_tde/t/pg_tde_change_key_provider.pl | 20 ++++---- 7 files changed, 61 insertions(+), 61 deletions(-) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index ad5870cc198e0..1de794060aa8c 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -39,8 +39,8 @@ SELECT key_provider_id, key_provider_name, key_name -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_global_key_providers(); + id | name ----+--------------- -2 | file-provider (1 row) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index c77c2653657d1..b7a3105fa2823 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -22,10 +22,10 @@ SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_ SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_dup.per'); ERROR: Key provider "file-provider" already exists. SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+-------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} + id | name | type | options +----+----------------+------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_verify_key(); @@ -45,19 +45,19 @@ SELECT pg_tde_verify_key(); SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per'); ERROR: key provider "not-existent-provider" does not exists SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+-------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} + id | name | type | options +----+----------------+------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); ERROR: key provider value cannot be an object SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+----------------+---------------+-------------------------------------------- - 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} - 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} + id | name | type | options +----+----------------+------+-------------------------------------------- + 1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"} + 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyring.per'); @@ -72,8 +72,8 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_key (1 row) -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_global_key_providers(); + id | name ----+--------------- -4 | file-keyring -5 | file-keyring2 @@ -82,8 +82,8 @@ SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_database_key_providers(); + id | name ----+---------------- 1 | file-provider 2 | file-provider2 @@ -96,14 +96,14 @@ SELECT pg_tde_delete_database_key_provider('file-provider2'); (1 row) -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_database_key_providers(); + id | name ----+--------------- 1 | file-provider (1 row) -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_global_key_providers(); + id | name ----+--------------- -4 | file-keyring -5 | file-keyring2 @@ -118,8 +118,8 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', f -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); ERROR: Can't delete a provider which is currently in use -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name +SELECT id, name FROM pg_tde_list_all_global_key_providers(); + id | name ----+--------------- -4 | file-keyring -5 | file-keyring2 @@ -132,9 +132,9 @@ SELECT pg_tde_delete_global_key_provider('file-keyring2'); (1 row) -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); - id | provider_name -----+--------------- +SELECT id, name FROM pg_tde_list_all_global_key_providers(); + id | name +----+-------------- -4 | file-keyring (1 row) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 897e1664bbbbb..716fa003e1158 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -2,7 +2,7 @@ \echo Use "CREATE EXTENSION pg_tde" to load this file. \quit -- Key Provider Management -CREATE FUNCTION pg_tde_add_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_add_database_key_provider(type TEXT, name TEXT, options JSON) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; @@ -50,8 +50,8 @@ END; CREATE FUNCTION pg_tde_list_all_database_key_providers (OUT id INT, - OUT provider_name TEXT, - OUT provider_type TEXT, + OUT name TEXT, + OUT type TEXT, OUT options JSON) RETURNS SETOF RECORD LANGUAGE C @@ -60,8 +60,8 @@ REVOKE ALL ON FUNCTION pg_tde_list_all_database_key_providers() FROM PUBLIC; CREATE FUNCTION pg_tde_list_all_global_key_providers (OUT id INT, - OUT provider_name TEXT, - OUT provider_type TEXT, + OUT name TEXT, + OUT type TEXT, OUT options JSON) RETURNS SETOF RECORD LANGUAGE C @@ -69,7 +69,7 @@ AS 'MODULE_PATHNAME'; REVOKE ALL ON FUNCTION pg_tde_list_all_global_key_providers() FROM PUBLIC; -- Global Tablespace Key Provider Management -CREATE FUNCTION pg_tde_add_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_add_global_key_provider(type TEXT, name TEXT, options JSON) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; @@ -116,7 +116,7 @@ BEGIN ATOMIC END; -- Key Provider Management -CREATE FUNCTION pg_tde_change_database_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_change_database_key_provider(type TEXT, name TEXT, options JSON) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; @@ -163,7 +163,7 @@ BEGIN ATOMIC END; -- Global Tablespace Key Provider Management -CREATE FUNCTION pg_tde_change_global_key_provider(provider_type TEXT, provider_name TEXT, options JSON) +CREATE FUNCTION pg_tde_change_global_key_provider(type TEXT, name TEXT, options JSON) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index b91744390daa5..ec24ccd4d4c4e 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -18,7 +18,7 @@ SELECT key_provider_id, key_provider_name, key_name -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- Should fail: no principal key for the database yet SELECT key_provider_id, key_provider_name, key_name diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 008858d8944a0..9cfb21ee2f5e6 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -22,27 +22,27 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring','/tmp/pg_tde_test_keyr SELECT pg_tde_add_global_key_provider_file('file-keyring2','/tmp/pg_tde_test_keyring2.per'); -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); +SELECT id, name FROM pg_tde_list_all_database_key_providers(); -- works SELECT pg_tde_delete_database_key_provider('file-provider2'); -SELECT id, provider_name FROM pg_tde_list_all_database_key_providers(); +SELECT id, name FROM pg_tde_list_all_database_key_providers(); -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +SELECT id, name FROM pg_tde_list_all_global_key_providers(); SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- works SELECT pg_tde_delete_global_key_provider('file-keyring2'); -SELECT id, provider_name FROM pg_tde_list_all_global_key_providers(); +SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- Creating a file key provider fails if we can't open or create the file SELECT pg_tde_add_database_key_provider_file('will-not-work','/cant-create-file-in-root.per'); diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index 4d912c77936d6..fc7858c7f684e 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -6,9 +6,9 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_prov (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------------------------------------------- - 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} + id | name | type | options +----+------------+------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} (1 row) SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); @@ -46,9 +46,9 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------------------------------------------- - 1 | file-vault | file | {"path" : "/tmp/change_key_provider_2.per"} + id | name | type | options +----+------------+------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_2.per"} (1 row) SELECT pg_tde_verify_key(); @@ -109,9 +109,9 @@ SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_p (1 row) SELECT * FROM pg_tde_list_all_database_key_providers(); - id | provider_name | provider_type | options -----+---------------+---------------+--------------------------------------------- - 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} + id | name | type | options +----+------------+------+--------------------------------------------- + 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} (1 row) SELECT pg_tde_verify_key(); diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index 47380a940bebd..fbcae2e77b9c2 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -43,7 +43,7 @@ is( $node->safe_psql( 'postgres', - q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT type FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} ), 'file', 'provider type is set to file'); @@ -51,7 +51,7 @@ $options = decode_json( $node->safe_psql( 'postgres', - q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} )); is( $options->{path}, '/tmp/pg_tde_change_key_provider-database-2', @@ -78,7 +78,7 @@ is( $node->safe_psql( 'postgres', - q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT type FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} ), 'vault-v2', 'provider type is set to vault-v2'); @@ -86,7 +86,7 @@ $options = decode_json( $node->safe_psql( 'postgres', - q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} )); is( $options->{url}, 'https://vault-server.example:8200/', @@ -118,7 +118,7 @@ is( $node->safe_psql( 'postgres', - q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT type FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} ), 'vault-v2', 'provider type is set to vault-v2'); @@ -126,7 +126,7 @@ $options = decode_json( $node->safe_psql( 'postgres', - q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} )); is( $options->{url}, 'http://vault-server.example:8200/', @@ -159,7 +159,7 @@ is( $node->safe_psql( 'postgres', - q{SELECT provider_type FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT type FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} ), 'kmip', 'provider type is set to kmip'); @@ -167,7 +167,7 @@ $options = decode_json( $node->safe_psql( 'postgres', - q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE provider_name = 'database-provider'} + q{SELECT options FROM pg_tde_list_all_database_key_providers() WHERE name = 'database-provider'} )); is($options->{host}, 'kmip-server.example', 'host is set correctly for kmip provider'); @@ -200,7 +200,7 @@ is( $node->safe_psql( 'postgres', - q{SELECT provider_type FROM pg_tde_list_all_global_key_providers() WHERE provider_name = 'global-provider'} + q{SELECT type FROM pg_tde_list_all_global_key_providers() WHERE name = 'global-provider'} ), 'vault-v2', 'provider type is set to vault-v2 for global provider'); @@ -208,7 +208,7 @@ $options = decode_json( $node->safe_psql( 'postgres', - q{SELECT options FROM pg_tde_list_all_global_key_providers() WHERE provider_name = 'global-provider'} + q{SELECT options FROM pg_tde_list_all_global_key_providers() WHERE name = 'global-provider'} )); is( $options->{url}, 'http://vault-server.example:8200/', From 34e2d298bf68d09493144d934aa8b436647ba922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 11 Jun 2025 11:48:16 +0200 Subject: [PATCH 464/796] Remove double prefix from provider name and id In the output from the key info functions the key_ prefix for the provider name and id columns doesn't add any value so let's harmonize them with how we name arguments to the key creation functions. --- .../pg_tde/expected/default_principal_key.out | 64 ++++++------- .../pg_tde/expected/delete_principal_key.out | 16 ++-- contrib/pg_tde/expected/key_provider.out | 6 +- .../pg_tde/expected/pg_tde_is_encrypted.out | 8 +- contrib/pg_tde/pg_tde--1.0-rc.sql | 12 +-- contrib/pg_tde/sql/default_principal_key.sql | 16 ++-- contrib/pg_tde/sql/delete_principal_key.sql | 4 +- contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 2 +- contrib/pg_tde/t/expected/rotate_key.out | 96 +++++++++---------- contrib/pg_tde/t/expected/wal_encrypt.out | 16 ++-- contrib/pg_tde/t/rotate_key.pl | 30 +++--- contrib/pg_tde/t/wal_encrypt.pl | 4 +- 12 files changed, 134 insertions(+), 140 deletions(-) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 1de794060aa8c..6c5c92509a66e 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -10,11 +10,11 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi SELECT pg_tde_verify_default_key(); ERROR: principal key not configured for current database -- Should fail: no default principal key for the server yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_default_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); @@ -29,11 +29,11 @@ SELECT pg_tde_verify_default_key(); (1 row) -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_default_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -2 | file-provider | default-key + provider_id | provider_name | key_name +-------------+---------------+------------- + -2 | file-provider | default-key (1 row) -- fails @@ -46,11 +46,11 @@ SELECT id, name FROM pg_tde_list_all_global_key_providers(); (1 row) -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) -- Should succeed: "localizes" the default principal key for the database @@ -61,11 +61,11 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -2 | file-provider | default-key + provider_id | provider_name | key_name +-------------+---------------+------------- + -2 | file-provider | default-key (1 row) SELECT current_database() AS regress_database @@ -75,11 +75,11 @@ CREATE DATABASE regress_pg_tde_other; CREATE EXTENSION pg_tde; CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) -- Should succeed: "localizes" the default principal key for the database @@ -90,11 +90,11 @@ CREATE TABLE test_enc( ) USING tde_heap; INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -2 | file-provider | default-key + provider_id | provider_name | key_name +-------------+---------------+------------- + -2 | file-provider | default-key (1 row) \c :regress_database @@ -105,19 +105,19 @@ SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file (1 row) -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+----------------- - -2 | file-provider | new-default-key + provider_id | provider_name | key_name +-------------+---------------+----------------- + -2 | file-provider | new-default-key (1 row) \c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+----------------- - -2 | file-provider | new-default-key + provider_id | provider_name | key_name +-------------+---------------+----------------- + -2 | file-provider | new-default-key (1 row) SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 480297556dd07..3c6319e7b3ebf 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -13,10 +13,10 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -3 | file-provider | test-db-key +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+------------- + -3 | file-provider | test-db-key (1 row) SELECT pg_tde_delete_key(); @@ -84,10 +84,10 @@ SELECT pg_tde_delete_key(); (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -3 | file-provider | defalut-key +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+------------- + -3 | file-provider | defalut-key (1 row) -- Try to delete key when default key is used diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index b7a3105fa2823..2570357c4ffce 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -1,8 +1,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); - key_name | key_provider_name | key_provider_id | key_creation_time -----------+-------------------+-----------------+------------------- - | | | + key_name | provider_name | provider_id | key_creation_time +----------+---------------+-------------+------------------- + | | | (1 row) SELECT pg_tde_add_database_key_provider('file', 'incorrect-file-provider', '{"path": {"foo": "/tmp/pg_tde_test_keyring.per"}}'); diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index fc85af7de597a..f3916e4734adb 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -77,11 +77,11 @@ SELECT pg_tde_is_encrypted(NULL); (1 row) -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - 1 | file-vault | test-db-key + provider_id | provider_name | key_name +-------------+---------------+------------- + 1 | file-vault | test-db-key (1 row) DROP TABLE test_temp_norm; diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 716fa003e1158..242482dbc30c0 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -271,8 +271,8 @@ REVOKE ALL ON FUNCTION pg_tde_delete_default_key() FROM PUBLIC; CREATE FUNCTION pg_tde_key_info() RETURNS TABLE ( key_name TEXT, - key_provider_name TEXT, - key_provider_id INT, + provider_name TEXT, + provider_id INT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; @@ -280,8 +280,8 @@ REVOKE ALL ON FUNCTION pg_tde_key_info() FROM PUBLIC; CREATE FUNCTION pg_tde_server_key_info() RETURNS TABLE ( key_name TEXT, - key_provider_name TEXT, - key_provider_id INT, + provider_name TEXT, + provider_id INT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; @@ -289,8 +289,8 @@ REVOKE ALL ON FUNCTION pg_tde_server_key_info() FROM PUBLIC; CREATE FUNCTION pg_tde_default_key_info() RETURNS TABLE ( key_name TEXT, - key_provider_name TEXT, - key_provider_id INT, + provider_name TEXT, + provider_id INT, key_creation_time TIMESTAMP WITH TIME ZONE) LANGUAGE C AS 'MODULE_PATHNAME'; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index ec24ccd4d4c4e..3a39fa87fc0c2 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -7,13 +7,13 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regressi SELECT pg_tde_verify_default_key(); -- Should fail: no default principal key for the server yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_default_key_info(); SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); SELECT pg_tde_verify_default_key(); -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_default_key_info(); -- fails @@ -21,7 +21,7 @@ SELECT pg_tde_delete_global_key_provider('file-provider'); SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); -- Should succeed: "localizes" the default principal key for the database @@ -34,7 +34,7 @@ CREATE TABLE test_enc( INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); SELECT current_database() AS regress_database @@ -48,7 +48,7 @@ CREATE EXTENSION pg_tde; CREATE EXTENSION pg_buffercache; -- Should fail: no principal key for the database yet -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); -- Should succeed: "localizes" the default principal key for the database @@ -61,7 +61,7 @@ CREATE TABLE test_enc( INSERT INTO test_enc (k) VALUES (1), (2), (3); -- Should succeed: create table localized the principal key -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); \c :regress_database @@ -70,12 +70,12 @@ CHECKPOINT; SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); \c regress_pg_tde_other -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass); diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql index 6f313277ab297..f058a7f506064 100644 --- a/contrib/pg_tde/sql/delete_principal_key.sql +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -5,7 +5,7 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_key -- Set the local key and delete it without any encrypted tables -- Should succeed: nothing used the key SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); SELECT pg_tde_delete_key(); -- Set local key, encrypt a table, and delete the key @@ -32,7 +32,7 @@ SELECT pg_tde_set_default_key_using_global_key_provider('defalut-key','file-prov SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); CREATE TABLE test_table (id int, data text) USING tde_heap; SELECT pg_tde_delete_key(); -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); -- Try to delete key when default key is used -- Should fail: table already uses the default key, so there is no key to fallback to diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index f1fdede1c93bd..19e57b1689b93 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -38,7 +38,7 @@ SELECT relname, pg_tde_is_encrypted(relname) FROM (VALUES ('test_enc_pkey'), ('t SELECT pg_tde_is_encrypted(NULL); -SELECT key_provider_id, key_provider_name, key_name +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); DROP TABLE test_temp_norm; diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 288d9d7a3993e..956708ed5981e 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -59,16 +59,16 @@ SELECT * FROM test_enc ORDER BY id; (2 rows) -- server restart -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+-------------- - 1 | file-vault | rotated-key1 +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+-------------- + 1 | file-vault | rotated-key1 (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT * FROM test_enc ORDER BY id; @@ -92,16 +92,16 @@ SELECT * FROM test_enc ORDER BY id; (2 rows) -- server restart -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+-------------- - 2 | file-2 | rotated-key2 +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+-------------- + 2 | file-2 | rotated-key2 (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT * FROM test_enc ORDER BY id; @@ -125,16 +125,16 @@ SELECT * FROM test_enc ORDER BY id; (2 rows) -- server restart -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+------------- - -2 | file-3 | rotated-key +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+------------- + -2 | file-3 | rotated-key (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT * FROM test_enc ORDER BY id; @@ -158,16 +158,16 @@ SELECT * FROM test_enc ORDER BY id; (2 rows) -- server restart -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+-------------- - -1 | file-2 | rotated-keyX +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+-------------- + -1 | file-2 | rotated-keyX (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT * FROM test_enc ORDER BY id; @@ -181,16 +181,16 @@ ALTER SYSTEM SET pg_tde.inherit_global_providers = off; -- server restart SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+-------------- - -1 | file-2 | rotated-keyX +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+-------------- + -1 | file-2 | rotated-keyX (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); @@ -199,16 +199,16 @@ SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+-------------- - 2 | file-2 | rotated-key2 +SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); + provider_id | provider_name | key_name +-------------+---------------+-------------- + 2 | file-2 | rotated-key2 (1 row) -SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info(); - key_provider_id | key_provider_name | key_name ------------------+-------------------+---------- - | | +SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info(); + provider_id | provider_name | key_name +-------------+---------------+---------- + | | (1 row) DROP TABLE test_enc; diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 15f878e0d24a8..3f89acc31a745 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -7,10 +7,10 @@ SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test SELECT pg_tde_verify_server_key(); psql::1: ERROR: principal key not configured for current database -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); - key_name | key_provider_name | key_provider_id -----------+-------------------+----------------- - | | +SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info(); + key_name | provider_name | provider_id +----------+---------------+------------- + | | (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); @@ -25,10 +25,10 @@ SELECT pg_tde_verify_server_key(); (1 row) -SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info(); - key_name | key_provider_name | key_provider_id -------------+-------------------+----------------- - server-key | file-keyring-010 | -1 +SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info(); + key_name | provider_name | provider_id +------------+------------------+------------- + server-key | file-keyring-010 | -1 (1 row) ALTER SYSTEM SET pg_tde.wal_encrypt = on; diff --git a/contrib/pg_tde/t/rotate_key.pl b/contrib/pg_tde/t/rotate_key.pl index 02da31aca98b8..c203513635cd9 100644 --- a/contrib/pg_tde/t/rotate_key.pl +++ b/contrib/pg_tde/t/rotate_key.pl @@ -54,10 +54,9 @@ $node->restart; PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -71,10 +70,9 @@ $node->restart; PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -88,10 +86,9 @@ $node->restart; PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -108,10 +105,9 @@ $node->restart; PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -127,20 +123,18 @@ "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);" ); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2');" ); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_key_info();" -); + "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); PGTDE::psql($node, 'postgres', - "SELECT key_provider_id, key_provider_name, key_name FROM pg_tde_server_key_info();" + "SELECT provider_id, provider_name, key_name FROM pg_tde_server_key_info();" ); PGTDE::psql($node, 'postgres', 'DROP TABLE test_enc;'); diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index 2799b3b6d238b..61ac3c8e46129 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -26,7 +26,7 @@ PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); PGTDE::psql($node, 'postgres', - 'SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info();' + 'SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info();' ); PGTDE::psql($node, 'postgres', @@ -36,7 +36,7 @@ PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); PGTDE::psql($node, 'postgres', - 'SELECT key_name, key_provider_name, key_provider_id FROM pg_tde_server_key_info();' + 'SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info();' ); PGTDE::psql($node, 'postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); From f661cad195df21ce000b6fdc6903ba553269304b Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 11 Jun 2025 15:55:46 +0200 Subject: [PATCH 465/796] Rename relation key removal XLOG record type to match consistency Rename XLOG record type for relation key removal and add missing type description and string representations. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 10 +++++++++- contrib/pg_tde/src/include/access/pg_tde_xlog.h | 2 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 131ee69914f8e..0081acb269d57 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -53,7 +53,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_save_principal_key_redo(mkey); } - else if (info == XLOG_TDE_REMOVE_RELATION_KEY) + else if (info == XLOG_TDE_DELETE_RELATION_KEY) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); @@ -118,6 +118,12 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "db: %u", dbOid); } + else if (info == XLOG_TDE_DELETE_RELATION_KEY) + { + XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); + + appendStringInfo(buf, "rel: %u/%u/%u", xlrec->rlocator.spcOid, xlrec->rlocator.dbOid, xlrec->rlocator.relNumber); + } else if (info == XLOG_TDE_WRITE_KEY_PROVIDER) { KeyringProviderRecordInFile *xlrec = (KeyringProviderRecordInFile *) XLogRecGetData(record); @@ -143,6 +149,8 @@ tdeheap_rmgr_identify(uint8 info) return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_ROTATE_PRINCIPAL_KEY: return "ROTATE_PRINCIPAL_KEY"; + case XLOG_TDE_DELETE_RELATION_KEY: + return "DELETE_RELATION_KEY"; case XLOG_TDE_DELETE_PRINCIPAL_KEY: return "DELETE_PRINCIPAL_KEY"; case XLOG_TDE_WRITE_KEY_PROVIDER: diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 1dcd385b16acc..8c93a7a1807fc 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -13,7 +13,7 @@ #define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x20 #define XLOG_TDE_WRITE_KEY_PROVIDER 0x30 #define XLOG_TDE_INSTALL_EXTENSION 0x40 -#define XLOG_TDE_REMOVE_RELATION_KEY 0x50 +#define XLOG_TDE_DELETE_RELATION_KEY 0x50 #define XLOG_TDE_DELETE_PRINCIPAL_KEY 0x60 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 12f68bc555d3d..b6dbcbb4c915f 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -121,7 +121,7 @@ tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_REMOVE_RELATION_KEY); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_RELATION_KEY); } void From ed1efc51ec74c4a92db681af2257f6c23ea6f60e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 11 Jun 2025 16:55:58 +0200 Subject: [PATCH 466/796] Make sure we always delete key files before running TAP tests This way it is more clear what the tests actually do. And addditionally make sure the files are named consistently. --- contrib/pg_tde/t/basic.pl | 2 ++ contrib/pg_tde/t/crash_recovery.pl | 5 ++--- .../pg_tde/t/expected/key_rotate_tablespace.out | 2 +- contrib/pg_tde/t/expected/replication.out | 2 +- contrib/pg_tde/t/expected/rotate_key.out | 16 ++++++++-------- contrib/pg_tde/t/expected/tde_heap.out | 2 +- contrib/pg_tde/t/expected/wal_encrypt.out | 2 +- contrib/pg_tde/t/key_rotate_tablespace.pl | 4 +++- contrib/pg_tde/t/multiple_extensions.pl | 2 ++ contrib/pg_tde/t/pg_waldump_basic.pl | 4 +++- contrib/pg_tde/t/pg_waldump_fullpage.pl | 4 +++- contrib/pg_tde/t/replication.pl | 4 +++- contrib/pg_tde/t/rotate_key.pl | 13 +++++++++---- contrib/pg_tde/t/tde_heap.pl | 4 +++- contrib/pg_tde/t/unlogged_tables.pl | 2 ++ contrib/pg_tde/t/wal_encrypt.pl | 4 +++- 16 files changed, 47 insertions(+), 25 deletions(-) diff --git a/contrib/pg_tde/t/basic.pl b/contrib/pg_tde/t/basic.pl index 567ff508934e1..8eea2d39d3d6c 100644 --- a/contrib/pg_tde/t/basic.pl +++ b/contrib/pg_tde/t/basic.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/pg_tde_test_001_basic.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); diff --git a/contrib/pg_tde/t/crash_recovery.pl b/contrib/pg_tde/t/crash_recovery.pl index 1964e26c536bd..b4f75010ac3bd 100644 --- a/contrib/pg_tde/t/crash_recovery.pl +++ b/contrib/pg_tde/t/crash_recovery.pl @@ -7,11 +7,10 @@ use lib 't'; use pgtde; -# ensure we start with a clean key provider file -unlink('/tmp/crash_recovery.per'); - PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/crash_recovery.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf( diff --git a/contrib/pg_tde/t/expected/key_rotate_tablespace.out b/contrib/pg_tde/t/expected/key_rotate_tablespace.out index fdb057433b1df..17559d72d24b9 100644 --- a/contrib/pg_tde/t/expected/key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/key_rotate_tablespace.out @@ -1,7 +1,7 @@ SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION ''; CREATE DATABASE tbc TABLESPACE = test_tblspace; CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tablespace.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index 744953c213dc3..0540349e2ac1e 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -1,6 +1,6 @@ -- At primary CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 956708ed5981e..7020730ddbd22 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -1,33 +1,33 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/rotate_key.per'); pg_tde_add_database_key_provider_file --------------------------------------- (1 row) -SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per'); +SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/rotate_key_2.per'); pg_tde_add_database_key_provider_file --------------------------------------- (1 row) -SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2g.per'); +SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/rotate_key_2g.per'); pg_tde_add_global_key_provider_file ------------------------------------- (1 row) -SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3.per'); +SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/rotate_key_3.per'); pg_tde_add_global_key_provider_file ------------------------------------- (1 row) SELECT pg_tde_list_all_database_key_providers(); - pg_tde_list_all_database_key_providers ---------------------------------------------------------------------- - (1,file-vault,file,"{""path"" : ""/tmp/pg_tde_test_keyring.per""}") - (2,file-2,file,"{""path"" : ""/tmp/pg_tde_test_keyring_2.per""}") + pg_tde_list_all_database_key_providers +------------------------------------------------------------ + (1,file-vault,file,"{""path"" : ""/tmp/rotate_key.per""}") + (2,file-2,file,"{""path"" : ""/tmp/rotate_key_2.per""}") (2 rows) SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/t/expected/tde_heap.out b/contrib/pg_tde/t/expected/tde_heap.out index d8dca8b986a2f..f49a3586eb0aa 100644 --- a/contrib/pg_tde/t/expected/tde_heap.out +++ b/contrib/pg_tde/t/expected/tde_heap.out @@ -1,5 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 3f89acc31a745..61c8c0ff43cc1 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -1,5 +1,5 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; -SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per'); +SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/wal_encrypt.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/t/key_rotate_tablespace.pl b/contrib/pg_tde/t/key_rotate_tablespace.pl index 310ecba3dbf22..5168d22527924 100644 --- a/contrib/pg_tde/t/key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/key_rotate_tablespace.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/key_rotate_tablespace.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); @@ -22,7 +24,7 @@ PGTDE::psql($node, 'tbc', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'tbc', - "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tablespace.per');" ); PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" diff --git a/contrib/pg_tde/t/multiple_extensions.pl b/contrib/pg_tde/t/multiple_extensions.pl index 7750a9945d842..2137082ca973f 100644 --- a/contrib/pg_tde/t/multiple_extensions.pl +++ b/contrib/pg_tde/t/multiple_extensions.pl @@ -17,6 +17,8 @@ "pg_tde test case only for PPG server package install with extensions."; } +unlink('/tmp/keyring_data_file'); + open my $conf2, '>>', "/tmp/datafile-location"; print $conf2 "/tmp/keyring_data_file\n"; close $conf2; diff --git a/contrib/pg_tde/t/pg_waldump_basic.pl b/contrib/pg_tde/t/pg_waldump_basic.pl index 7b37b909dee41..7ec14eed121b6 100644 --- a/contrib/pg_tde/t/pg_waldump_basic.pl +++ b/contrib/pg_tde/t/pg_waldump_basic.pl @@ -7,6 +7,8 @@ use PostgreSQL::Test::Utils; use Test::More; +unlink('/tmp/pg_waldump_basic.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf( @@ -28,7 +30,7 @@ $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_tde_test_keyring-wal.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_basic.per');" ); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" diff --git a/contrib/pg_tde/t/pg_waldump_fullpage.pl b/contrib/pg_tde/t/pg_waldump_fullpage.pl index 782d248f1b09d..3caf2cfbcf329 100644 --- a/contrib/pg_tde/t/pg_waldump_fullpage.pl +++ b/contrib/pg_tde/t/pg_waldump_fullpage.pl @@ -29,6 +29,8 @@ sub get_block_lsn return ($lsn_hi, $lsn_lo); } +unlink('/tmp/pg_waldump_fullpage.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf( @@ -42,7 +44,7 @@ sub get_block_lsn $node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); $node->safe_psql('postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_tde_test_keyring-wal.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_fullpage.per');" ); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" diff --git a/contrib/pg_tde/t/replication.pl b/contrib/pg_tde/t/replication.pl index bd64593858c73..21fbcdfbd1929 100644 --- a/contrib/pg_tde/t/replication.pl +++ b/contrib/pg_tde/t/replication.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/replication.per'); + my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init(allows_streaming => 1); $primary->append_conf( @@ -28,7 +30,7 @@ PGTDE::psql($primary, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($primary, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per');" ); PGTDE::psql($primary, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" diff --git a/contrib/pg_tde/t/rotate_key.pl b/contrib/pg_tde/t/rotate_key.pl index c203513635cd9..b60c4b5836186 100644 --- a/contrib/pg_tde/t/rotate_key.pl +++ b/contrib/pg_tde/t/rotate_key.pl @@ -9,6 +9,11 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/rotate_key.per'); +unlink('/tmp/rotate_key_2.per'); +unlink('/tmp/rotate_key_2g.per'); +unlink('/tmp/rotate_key_3.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); @@ -17,16 +22,16 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/rotate_key.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2.per');" + "SELECT pg_tde_add_database_key_provider_file('file-2', '/tmp/rotate_key_2.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/pg_tde_test_keyring_2g.per');" + "SELECT pg_tde_add_global_key_provider_file('file-2', '/tmp/rotate_key_2g.per');" ); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/pg_tde_test_keyring_3.per');" + "SELECT pg_tde_add_global_key_provider_file('file-3', '/tmp/rotate_key_3.per');" ); PGTDE::psql($node, 'postgres', diff --git a/contrib/pg_tde/t/tde_heap.pl b/contrib/pg_tde/t/tde_heap.pl index 8f3deeb2316ff..1983c1e1b2eca 100644 --- a/contrib/pg_tde/t/tde_heap.pl +++ b/contrib/pg_tde/t/tde_heap.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/tde_heap.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); @@ -17,7 +19,7 @@ PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per');" + "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per');" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" diff --git a/contrib/pg_tde/t/unlogged_tables.pl b/contrib/pg_tde/t/unlogged_tables.pl index 7a5b1739a977e..3482d93e05ffd 100644 --- a/contrib/pg_tde/t/unlogged_tables.pl +++ b/contrib/pg_tde/t/unlogged_tables.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/unlogged_tables.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index 61ac3c8e46129..42ef87fe3682c 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -9,6 +9,8 @@ PGTDE::setup_files_dir(basename($0)); +unlink('/tmp/wal_encrypt.per'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); @@ -20,7 +22,7 @@ PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); PGTDE::psql($node, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/pg_tde_test_keyring010.per');" + "SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/wal_encrypt.per');" ); PGTDE::psql($node, 'postgres', 'SELECT pg_tde_verify_server_key();'); From f36b906e1421bb87454757ce9aeb8c105dbb1880 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 12 Jun 2025 13:57:39 +0200 Subject: [PATCH 467/796] Use access() instead of stat() Since we do not care about anything other than that the directory exists we may as well use access() rather than stat(). --- contrib/pg_tde/src/pg_tde.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index d7738ed869dac..626d5493b8fb3 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -5,8 +5,6 @@ #include "postgres.h" -#include - #include "access/tableam.h" #include "access/xlog.h" #include "access/xloginsert.h" @@ -141,9 +139,7 @@ extension_install_redo(XLogExtensionInstall *xlrec) static void pg_tde_init_data_dir(void) { - struct stat st; - - if (stat(PG_TDE_DATA_DIR, &st) < 0) + if (access(PG_TDE_DATA_DIR, F_OK) == -1) { if (MakePGDirectory(PG_TDE_DATA_DIR) < 0) ereport(ERROR, From b40ec8c04a18e37c693e9b07a62c412fe6b284f3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 12 Jun 2025 14:00:58 +0200 Subject: [PATCH 468/796] Remove handling of frontend from pg_tde_guc.{c,h} code Since we do not build the GUC code when building the frontend code the code which handles the frontend define is pointless here. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 +- contrib/pg_tde/src/include/pg_tde_guc.h | 3 --- contrib/pg_tde/src/pg_tde_guc.c | 4 ---- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 2ad5b1f4c779f..1d4b03274a692 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -27,7 +27,6 @@ #include "catalog/tde_principal_key.h" #include "keyring/keyring_api.h" #include "pg_tde.h" -#include "pg_tde_guc.h" #ifndef FRONTEND #include "access/genam.h" @@ -38,6 +37,7 @@ #include "lib/dshash.h" #include "storage/lwlock.h" #include "storage/shmem.h" +#include "pg_tde_guc.h" #else #include "pg_tde_fe.h" #endif diff --git a/contrib/pg_tde/src/include/pg_tde_guc.h b/contrib/pg_tde/src/include/pg_tde_guc.h index a48d0fd6f0294..c4ce064402d6d 100644 --- a/contrib/pg_tde/src/include/pg_tde_guc.h +++ b/contrib/pg_tde/src/include/pg_tde_guc.h @@ -7,13 +7,10 @@ #include "c.h" -#ifndef FRONTEND - extern bool AllowInheritGlobalProviders; extern bool EncryptXLog; extern bool EnforceEncryption; extern void TdeGucInit(void); -#endif #endif /* TDE_GUC_H */ diff --git a/contrib/pg_tde/src/pg_tde_guc.c b/contrib/pg_tde/src/pg_tde_guc.c index fe724d1c91595..912385cc0df75 100644 --- a/contrib/pg_tde/src/pg_tde_guc.c +++ b/contrib/pg_tde/src/pg_tde_guc.c @@ -8,8 +8,6 @@ #include "pg_tde_guc.h" -#ifndef FRONTEND - bool AllowInheritGlobalProviders = true; bool EncryptXLog = false; bool EnforceEncryption = false; @@ -54,5 +52,3 @@ TdeGucInit(void) ); } - -#endif From 086b3425e2f6e02410b255fa5de2bc1d328c6baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 12 Jun 2025 15:15:39 +0200 Subject: [PATCH 469/796] Add warning about WAL encryption being beta It makes sense to warn about this when a key for WAL encryption is created as this is a necessary step before WAL encryption can be enabled. --- contrib/pg_tde/expected/access_control.out | 1 + contrib/pg_tde/expected/key_provider.out | 5 +++++ contrib/pg_tde/src/catalog/tde_principal_key.c | 3 +++ contrib/pg_tde/t/expected/crash_recovery.out | 3 +++ contrib/pg_tde/t/expected/replication.out | 1 + contrib/pg_tde/t/expected/wal_encrypt.out | 1 + 6 files changed, 14 insertions(+) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 045e3ca8964c3..de266f17e88d0 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -58,6 +58,7 @@ ERROR: must be superuser to access global key providers SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: must be superuser to access global key providers SELECT pg_tde_delete_default_key(); ERROR: must be superuser to access global key providers diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 2570357c4ffce..d665b74cb6ed3 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -229,6 +229,7 @@ SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provi (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -290,6 +291,7 @@ ERROR: key provider name cannot be null SELECT pg_tde_set_key_using_global_key_provider('key', NULL); ERROR: key provider name cannot be null SELECT pg_tde_set_server_key_using_global_key_provider('key', NULL); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key provider name cannot be null -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); @@ -299,6 +301,7 @@ ERROR: key name cannot be null SELECT pg_tde_set_key_using_global_key_provider(NULL, 'file-keyring'); ERROR: key name cannot be null SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key name cannot be null -- Empty string is not allowed for a principal key name SELECT pg_tde_set_default_key_using_global_key_provider('', 'file-keyring'); @@ -308,6 +311,7 @@ ERROR: key name "" is too short SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); ERROR: key name "" is too short SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key name "" is too short -- Setting principal key fails if the key name is too long SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); @@ -317,5 +321,6 @@ ERROR: too long principal key name, maximum length is 255 bytes SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); ERROR: too long principal key name, maximum length is 255 bytes SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: too long principal key name, maximum length is 255 bytes DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 1d4b03274a692..26f4aad39cf1d 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -498,6 +498,9 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); bool ensure_new_key = PG_GETARG_BOOL(2); + ereport(WARNING, + errmsg("The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments!")); + /* Using a global provider for the global (wal) database */ pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, GLOBAL_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); diff --git a/contrib/pg_tde/t/expected/crash_recovery.out b/contrib/pg_tde/t/expected/crash_recovery.out index f9e39b5748931..1bbaf536931f1 100644 --- a/contrib/pg_tde/t/expected/crash_recovery.out +++ b/contrib/pg_tde/t/expected/crash_recovery.out @@ -11,6 +11,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'gl (1 row) +psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per'); pg_tde_add_database_key_provider_file --------------------------------------- @@ -37,6 +38,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', ' (1 row) +psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -54,6 +56,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', ' (1 row) +psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index 0540349e2ac1e..037e04ee662ef 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -68,6 +68,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file- (1 row) +psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap; INSERT INTO test_enc2 (x) VALUES (1), (2); ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 61c8c0ff43cc1..6a2bfa6100e8d 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -19,6 +19,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyri (1 row) +psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_verify_server_key(); pg_tde_verify_server_key -------------------------- From 3904c9916c1b94cc9c88a7ca91d9649cbf12d0f5 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Wed, 11 Jun 2025 20:13:35 +0100 Subject: [PATCH 470/796] Basic self hosted stormweaver runner This builds and runs postgres and stormweaver on smblade1 with the basic scenario. --- .github/workflows/stormweaver.yml | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/stormweaver.yml diff --git a/.github/workflows/stormweaver.yml b/.github/workflows/stormweaver.yml new file mode 100644 index 0000000000000..a56d03423a05f --- /dev/null +++ b/.github/workflows/stormweaver.yml @@ -0,0 +1,49 @@ +name: Stormweaver +on: + pull_request: + push: + branches: + - TDE_REL_17_STABLE + workflow_dispatch: + +jobs: + run: + name: Run + runs-on: self-hosted + steps: + + - name: Clone stormweaver + uses: actions/checkout@master + with: + repository: 'percona-lab/stormweaver' + path: 'stormweaver' + submodules: recursive + + - name: Update path + run: | + echo "/home/ghrunner/.local/bin" >> "$GITHUB_PATH" + + - name: Install/build dependencies + run: | + conan install . --build=missing --settings=build_type=Release + working-directory: stormweaver + + - name: Build stormweaver + run: | + conan build . --settings=build_type=Release + working-directory: stormweaver + + - name: Clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + path: 'postgres' + + - name: Build postgres + run: | + ci_scripts/meson-build.sh debugoptimized + working-directory: postgres + + - name: Run Stormweaver + run: bin/stormweaver scenarios/basic.lua -i ../../pginst + working-directory: stormweaver From 683af9f61e4863ff68596871a18a607c57edcfdd Mon Sep 17 00:00:00 2001 From: Alina Derkach Date: Fri, 13 Jun 2025 13:11:28 +0200 Subject: [PATCH 471/796] Configure PDF generation plugin (#427) The modified files allow the creation of the pdf for pg_tde modified: contrib/pg_tde/documentation/_resource/overrides/partials/banner.html new file: contrib/pg_tde/documentation/_resourcepdf/overrides/404.html new file: contrib/pg_tde/documentation/_resourcepdf/overrides/main.html new file: contrib/pg_tde/documentation/_resourcepdf/overrides/partials/banner.html new file: contrib/pg_tde/documentation/_resourcepdf/overrides/partials/copyright.html new file: contrib/pg_tde/documentation/_resourcepdf/overrides/partials/header.html new file: contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl deleted: contrib/pg_tde/documentation/mkdocs-pdf.yml modified: contrib/pg_tde/documentation/mkdocs.yml modified: contrib/pg_tde/documentation/requirements.txt Co-authored-by: Dragos Andriciuc --- .../_resource/overrides/partials/banner.html | 2 +- .../_resourcepdf/overrides/404.html | 9 ++ .../_resourcepdf/overrides/main.html | 67 +++++++++ .../overrides/partials/banner.html | 9 ++ .../overrides/partials/copyright.html | 14 ++ .../overrides/partials/header.html | 135 ++++++++++++++++++ .../docs/templates/pdf_cover_page.tpl | 11 ++ contrib/pg_tde/documentation/mkdocs-pdf.yml | 15 -- contrib/pg_tde/documentation/mkdocs.yml | 30 ++-- contrib/pg_tde/documentation/requirements.txt | 2 + 10 files changed, 267 insertions(+), 27 deletions(-) create mode 100644 contrib/pg_tde/documentation/_resourcepdf/overrides/404.html create mode 100644 contrib/pg_tde/documentation/_resourcepdf/overrides/main.html create mode 100644 contrib/pg_tde/documentation/_resourcepdf/overrides/partials/banner.html create mode 100644 contrib/pg_tde/documentation/_resourcepdf/overrides/partials/copyright.html create mode 100644 contrib/pg_tde/documentation/_resourcepdf/overrides/partials/header.html create mode 100644 contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl delete mode 100644 contrib/pg_tde/documentation/mkdocs-pdf.yml diff --git a/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html b/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html index 1553c0301cc1f..69b904fe286bf 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html +++ b/contrib/pg_tde/documentation/_resource/overrides/partials/banner.html @@ -4,6 +4,6 @@ diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/404.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/404.html new file mode 100644 index 0000000000000..36c0dcfc5f0bb --- /dev/null +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/404.html @@ -0,0 +1,9 @@ +{#- + This file was automatically generated - do not edit + -#} + {% extends "main.html" %} + {% block content %} +

404 - Not found

+

+ We can't find the page you are looking for. Try using the Search or return to homepage .

+ {% endblock %} \ No newline at end of file diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html new file mode 100644 index 0000000000000..1fca8c3d06631 --- /dev/null +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html @@ -0,0 +1,67 @@ +{#- + This file was automatically generated - do not edit +-#} +{% extends "base.html" %} + +{% block announce %} + This is the Release Candidate 2 (RC2) of Percona Transparent Data Encryption (TDE) extension. +

It is not recommended for production environments at this stage.

+

We encourage you to test it and give your feedback. + This will help us improve the product and make it production-ready faster.

+{% endblock %} + +{% block scripts %} + +{{ super() }} +{% endblock %} + + {% block extrahead %} + {{ super() }} + {% set title = config.site_name %} + {% if page and page.meta and page.meta.title %} + {% set title = title ~ " - " ~ page.meta.title %} + {% elif page and page.title and not page.is_homepage %} + {% set title = title ~ " - " ~ page.title %} + {% endif %} + + + + + {% endblock %} + +{% block site_nav %} + {% if nav %} + {% if page.meta and page.meta.hide %} + {% set hidden = "hidden" if "navigation" in page.meta.hide %} + {% endif %} + + {% endif %} + {% if "toc.integrate" not in features %} + {% if page.meta and page.meta.hide %} + {% set hidden = "hidden" if "toc" in page.meta.hide %} + {% endif %} + + {% endif %} + + {% endblock %} diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/banner.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/banner.html new file mode 100644 index 0000000000000..e75867643dc29 --- /dev/null +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/banner.html @@ -0,0 +1,9 @@ +
+

+

For help, click the link below to get free database assistance or contact our experts for personalized support.

+ + +
\ No newline at end of file diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/copyright.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/copyright.html new file mode 100644 index 0000000000000..d31be105f453b --- /dev/null +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/copyright.html @@ -0,0 +1,14 @@ +{#- + This file was automatically generated - do not edit + -#} + \ No newline at end of file diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/header.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/header.html new file mode 100644 index 0000000000000..1983ab1baef50 --- /dev/null +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/partials/header.html @@ -0,0 +1,135 @@ + + + +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--shadow md-header--lifted" %} +{% elif "navigation.tabs" not in features %} + {% set class = class ~ " md-header--shadow" %} +{% endif %} + + +
+ + + + + + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
diff --git a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl new file mode 100644 index 0000000000000..286838e89fc54 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl @@ -0,0 +1,11 @@ + +{{ config.extra.added_key }} +

+ +

+

Percona Transparent Data Encryption

+{% if config.site_description %} +

{{ config.site_description }}

+{% endif %} +

Release Candidate (2025-03-27)

+ \ No newline at end of file diff --git a/contrib/pg_tde/documentation/mkdocs-pdf.yml b/contrib/pg_tde/documentation/mkdocs-pdf.yml deleted file mode 100644 index 990732fb25072..0000000000000 --- a/contrib/pg_tde/documentation/mkdocs-pdf.yml +++ /dev/null @@ -1,15 +0,0 @@ -# MkDocs configuration for PDF builds -# Usage: ENABLE_PDF_EXPORT=1 mkdocs build -f mkdocs-pdf.yml - -INHERIT: mkdocs.yml - -copyright: Percona LLC, © 2025 - -extra_css: - - https://unicons.iconscout.com/release/v3.0.3/css/line.css - - https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css - - css/extra.css - -markdown_extensions: - pymdownx.tabbed: {} - admonition: {} diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index ab8c6e25236ff..9d542f2156a1c 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -17,7 +17,7 @@ theme: name: material logo: _images/postgresql-mark.svg favicon: _images/postgresql-fav.svg - custom_dir: _resource/overrides + custom_dir: _resourcepdf/overrides font: text: Roboto palette: @@ -116,16 +116,24 @@ plugins: - macros: include_yaml: - 'variables.yml' # Use in markdown as '{{ VAR }}' - - with-pdf: # https://github.com/orzih/mkdocs-with-pdf - output_path: '_pdf/PerconaTDE.pdf' - cover_title: 'Percona Transparent Data Encryption' - cover_subtitle: Release Candidate (2025-03-27) - author: 'Percona Technical Documentation Team' - cover_logo: docs/_images/Percona_Logo_Color.png - debug_html: false - two_columns_level: 3 - custom_template_path: _resource/templates - enabled_if_env: ENABLE_PDF_EXPORT + - print-site: + add_to_navigation: false + print_page_title: 'Percona Transparent Data Encryption documentation' + add_print_site_banner: false + # Table of contents + add_table_of_contents: true + toc_title: 'Table of Contents' + toc_depth: 2 + # Content-related + add_full_urls: false + enumerate_headings: false + enumerate_headings_depth: 1 + enumerate_figures: true + add_cover_page: true + cover_page_template: "docs/templates/pdf_cover_page.tpl" + path_to_pdf: "" + include_css: true + enabled: true extra: version: diff --git a/contrib/pg_tde/documentation/requirements.txt b/contrib/pg_tde/documentation/requirements.txt index 87a44fb4e5313..dfaa1ad8e0d55 100644 --- a/contrib/pg_tde/documentation/requirements.txt +++ b/contrib/pg_tde/documentation/requirements.txt @@ -16,3 +16,5 @@ mike mkdocs-glightbox Pillow > 10.1.0 # Pillow is required for mkdocs-glightbox mkdocs-open-in-new-tab # added to fix the open in new tab issue with mkdocs-material +mkdocs-open-in-new-tab +mkdocs-print-site-plugin \ No newline at end of file From 3bc2a13ce529b0728813ba746bc579c6043ad8fe Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 13 Jun 2025 14:12:08 +0300 Subject: [PATCH 472/796] Update Configure WAL topic (#422) updated this topic with: - added warning to not use it in PROD as it is in beta - Updated the SELECT parameters to be easier to read - Removed topic header as it is not necessary and updated the paragraph introducing the steps --- .../documentation/docs/wal-encryption.md | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index d539a8c6a5e1d..c61692e530e25 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -1,8 +1,9 @@ # Configure WAL Encryption (tech preview) -Before turning WAL encryption on, you must follow the steps below to create your first principal key. +!!! warning + The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. -## Create the principal key +Before enabling WAL encryption, follow the steps below to create a principal key and configure it for WAL: 1. Create the `pg_tde` extension if it does not exist: @@ -10,7 +11,7 @@ Before turning WAL encryption on, you must follow the steps below to create your CREATE EXTENSION IF NOT EXISTS pg_tde; ``` -2. Set up the key provider for WAL encryption +2. Set up the key provider for WAL encryption: === "With KMIP server" @@ -19,7 +20,14 @@ Before turning WAL encryption on, you must follow the steps below to create your For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql - SELECT pg_tde_add_global_key_provider_kmip('provider-name', 'kmip-addr', 5696, '/path_to/client_cert.pem', '/path_to/client_key.pem', '/path_to/server_certificate.pem'); + SELECT pg_tde_add_global_key_provider_kmip( + 'provider-name', + 'kmip-addr', + 5696, + '/path_to/client_cert.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' + ); ``` where: @@ -34,13 +42,26 @@ Before turning WAL encryption on, you must follow the steps below to create your :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_add_key_using_global_key_provider_kmip('kmip', '127.0.0.1', 5696, '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); + SELECT pg_tde_add_key_using_global_key_provider_kmip( + 'kmip', + '127.0.0.1', + 5696, + '/tmp/client_cert_jane_doe.pem', + '/tmp/client_key_jane_doe.pem', + '/tmp/server_certificate.pem' + ); ``` === "With HashiCorp Vault" ```sql - SELECT pg_tde_add_global_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); + SELECT pg_tde_add_global_key_provider_vault_v2( + 'provider-name', + 'url', + 'mount', + 'secret_token_path', + 'ca_path' + ); ``` where: @@ -53,16 +74,24 @@ Before turning WAL encryption on, you must follow the steps below to create your === "With keyring file" - This setup is **not recommended**, as it is intended for development. The keys are stored **unencrypted** in the specified data file. + This setup is **not recommended**, as it is intended for development. + + :material-information: Warning: The keys are stored **unencrypted** in the specified data file. ```sql - SELECT pg_tde_add_global_key_provider_file('provider-name', '/path/to/the/keyring/data.file'); + SELECT pg_tde_add_global_key_provider_file( + 'provider-name', + '/path/to/the/keyring/data.file' + ); ``` 3. Create principal key ```sql - SELECT pg_tde_set_server_key_using_global_key_provider('key', 'provider-name'); + SELECT pg_tde_set_server_key_using_global_key_provider( + 'key', + 'provider-name' + ); ``` 4. Enable WAL level encryption using the `ALTER SYSTEM` command. You need the privileges of the superuser to run this command: From d052631c558ba1e5d0aaf6779d9970383d2d055f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 12 Jun 2025 19:38:43 +0200 Subject: [PATCH 473/796] Clean up errors from set key functions Also move a couple of checks to the calling function. --- contrib/pg_tde/expected/key_provider.out | 12 ++++++--- .../pg_tde/src/catalog/tde_principal_key.c | 26 +++++++++---------- contrib/pg_tde/t/expected/rotate_key.out | 3 ++- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index d665b74cb6ed3..d517e74596f1d 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -315,12 +315,16 @@ WARNING: The WAL encryption feature is currently in beta and may be unstable. D ERROR: key name "" is too short -- Setting principal key fails if the key name is too long SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); -ERROR: too long principal key name, maximum length is 255 bytes +ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 255 bytes. SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provider'); -ERROR: too long principal key name, maximum length is 255 bytes +ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 255 bytes. SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); -ERROR: too long principal key name, maximum length is 255 bytes +ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 255 bytes. SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! -ERROR: too long principal key name, maximum length is 255 bytes +ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long +HINT: Maximum length is 255 bytes. DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 26f4aad39cf1d..98f148c233de5 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -227,12 +227,6 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, GenericKeyring *new_keyring; const KeyInfo *keyInfo = NULL; - if (AllowInheritGlobalProviders == false && providerOid != dbOid) - { - ereport(ERROR, - errmsg("Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON")); - } - /* * Try to get principal key from cache. */ @@ -251,21 +245,16 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, if (kr_ret != KEYRING_CODE_SUCCESS) { ereport(ERROR, - errmsg("failed to retrieve principal key from keyring provider :\"%s\"", new_keyring->provider_name), - errdetail("Error code: %d", kr_ret)); + errmsg("could not successfully query key provider \"%s\"", new_keyring->provider_name)); } } if (keyInfo != NULL && ensure_new_key) { - ereport(ERROR, - errmsg("failed to create principal key: already exists")); - } - - if (strlen(key_name) >= sizeof(keyInfo->name)) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("too long principal key name, maximum length is %ld bytes", sizeof(keyInfo->name) - 1)); + errmsg("cannot to create key \"%s\" because it already exists", key_name)); + } if (keyInfo == NULL) keyInfo = KeyringGenerateNewKeyAndStore(new_keyring, key_name, PRINCIPAL_KEY_LEN); @@ -517,6 +506,10 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na ereport(ERROR, errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to access global key providers")); + if (providerOid == GLOBAL_DATA_TDE_OID && !AllowInheritGlobalProviders) + ereport(ERROR, + errmsg("usage of global key providers is disabled"), + errhint("Set \"pg_tde.inherit_global_providers = on\" in postgresql.conf.")); if (key_name == NULL) ereport(ERROR, @@ -526,6 +519,11 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key name \"\" is too short")); + if (strlen(key_name) >= PRINCIPAL_KEY_NAME_LEN) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key name \"%s\" is too long", key_name), + errhint("Maximum length is %d bytes.", PRINCIPAL_KEY_NAME_LEN - 1)); if (provider_name == NULL) ereport(ERROR, errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 7020730ddbd22..015375c62f78c 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -180,7 +180,8 @@ SELECT * FROM test_enc ORDER BY id; ALTER SYSTEM SET pg_tde.inherit_global_providers = off; -- server restart SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); -psql::1: ERROR: Usage of global key providers is disabled. Enable it with pg_tde.inherit_global_providers = ON +psql::1: ERROR: usage of global key providers is disabled +HINT: Set "pg_tde.inherit_global_providers = on" in postgresql.conf. SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); provider_id | provider_name | key_name -------------+---------------+-------------- From 46cee5cecba222d8a1eaa10981832e13e8e543d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 12 Jun 2025 21:36:26 +0200 Subject: [PATCH 474/796] Remove key provider files in regress tests To ensure the tests are always run from the same state we remove any key provider files so that pg_tde_add_database/global_key_provider_file() always creates a new file. --- ci_scripts/setup-keyring-servers.sh | 1 + ci_scripts/tde_setup.sql | 1 + ci_scripts/tde_setup_global.sql | 1 + contrib/pg_tde/expected/access_control.out | 1 + contrib/pg_tde/expected/alter_index.out | 1 + contrib/pg_tde/expected/cache_alloc.out | 1 + contrib/pg_tde/expected/change_access_method.out | 1 + contrib/pg_tde/expected/create_database.out | 2 ++ contrib/pg_tde/expected/default_principal_key.out | 1 + contrib/pg_tde/expected/delete_principal_key.out | 1 + contrib/pg_tde/expected/insert_update_delete.out | 1 + contrib/pg_tde/expected/key_provider.out | 4 ++++ contrib/pg_tde/expected/partition_table.out | 1 + contrib/pg_tde/expected/pg_tde_is_encrypted.out | 1 + contrib/pg_tde/expected/recreate_storage.out | 1 + contrib/pg_tde/expected/tablespace.out | 1 + contrib/pg_tde/expected/toast_decrypt.out | 1 + contrib/pg_tde/sql/access_control.sql | 2 ++ contrib/pg_tde/sql/alter_index.sql | 2 ++ contrib/pg_tde/sql/cache_alloc.sql | 2 ++ contrib/pg_tde/sql/change_access_method.sql | 2 ++ contrib/pg_tde/sql/create_database.sql | 3 +++ contrib/pg_tde/sql/default_principal_key.sql | 2 ++ contrib/pg_tde/sql/delete_principal_key.sql | 2 ++ contrib/pg_tde/sql/insert_update_delete.sql | 2 ++ contrib/pg_tde/sql/key_provider.sql | 5 +++++ contrib/pg_tde/sql/partition_table.sql | 2 ++ contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 2 ++ contrib/pg_tde/sql/recreate_storage.sql | 2 ++ contrib/pg_tde/sql/tablespace.sql | 2 ++ contrib/pg_tde/sql/toast_decrypt.sql | 2 ++ 31 files changed, 53 insertions(+) diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index 962f859244339..4a9a5aba52ab2 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -15,6 +15,7 @@ wget https://raw.githubusercontent.com/OpenKMIP/PyKMIP/refs/heads/master/example cd .. echo $SCRIPT_DIR +rm -f /tmp/pykmip.db pykmip-server -f "$SCRIPT_DIR/../contrib/pg_tde/pykmip-server.conf" -l /tmp/kmip-server.log & CLUSTER_INFO=$(mktemp) diff --git a/ci_scripts/tde_setup.sql b/ci_scripts/tde_setup.sql index fd084c96462bd..c4d3289042792 100644 --- a/ci_scripts/tde_setup.sql +++ b/ci_scripts/tde_setup.sql @@ -1,4 +1,5 @@ CREATE SCHEMA IF NOT EXISTS tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; +\! rm -f '/tmp/pg_tde_test_keyring.per' SELECT tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); SELECT tde.pg_tde_set_key_using_database_key_provider('test-db-key', 'reg_file-vault'); diff --git a/ci_scripts/tde_setup_global.sql b/ci_scripts/tde_setup_global.sql index 364b34c5f603a..de64310989d9e 100644 --- a/ci_scripts/tde_setup_global.sql +++ b/ci_scripts/tde_setup_global.sql @@ -1,6 +1,7 @@ CREATE SCHEMA tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; +\! rm -f '/tmp/pg_tde_test_keyring.per' SELECT tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); SELECT tde.pg_tde_set_server_key_using_global_key_provider('server-key', 'reg_file-global'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index de266f17e88d0..da19b79298131 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index dc3c181acdd49..83121582d0261 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index 86e060fae585a..31801767eadfa 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' -- Just checking there are no mem debug WARNINGs during the cache population CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index fd95f35489c89..4e18699bf1cfa 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index 83944edd3e3e5..cf5827b81a1a6 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -1,3 +1,5 @@ +\! rm -f '/tmp/template_provider_global.per' +\! rm -f '/tmp/template_provider.per' CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE DATABASE template_db; SELECT current_database() AS regress_database diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 6c5c92509a66e..b372401b9f7a4 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_regression_default_key.per' CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE EXTENSION IF NOT EXISTS pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 3c6319e7b3ebf..7faf2b9e446ef 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_global_key_provider_file diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index 9db4133a07321..8710a076b5c3f 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index d517e74596f1d..91850794e1e01 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -1,3 +1,7 @@ +\! rm -f '/tmp/db-provider-file' +\! rm -f '/tmp/global-provider-file-1' +\! rm -f '/tmp/pg_tde_test_keyring.per' +\! rm -f '/tmp/pg_tde_test_keyring2.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); key_name | provider_name | provider_id | key_creation_time diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index 704bb98598c72..30f039585365e 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_keyring.per' CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index f3916e4734adb..f3f7b1e9804c8 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index 235e75b70473a..87dc760c31ea6 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index de34caa969d70..93f790d4f815a 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index e7d2d11370eda..938aa01f28f21 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -1,3 +1,4 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index b8ac7aff0ec79..7ffa455cdf7ba 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 794161bbd0eae..7af5617ea73b7 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/cache_alloc.sql b/contrib/pg_tde/sql/cache_alloc.sql index 745fdacfc18d8..acaf5146711c6 100644 --- a/contrib/pg_tde/sql/cache_alloc.sql +++ b/contrib/pg_tde/sql/cache_alloc.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + -- Just checking there are no mem debug WARNINGs during the cache population CREATE EXTENSION IF NOT EXISTS pg_tde; diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index cc3f2eb153f5d..6949ea7054aea 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql index 77c7aaf84a83a..4ef8d4ed6fe42 100644 --- a/contrib/pg_tde/sql/create_database.sql +++ b/contrib/pg_tde/sql/create_database.sql @@ -1,3 +1,6 @@ +\! rm -f '/tmp/template_provider_global.per' +\! rm -f '/tmp/template_provider.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE DATABASE template_db; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 3a39fa87fc0c2..29422cd3d575a 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_regression_default_key.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; CREATE EXTENSION IF NOT EXISTS pg_buffercache; diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql index f058a7f506064..9c2aa36271a27 100644 --- a/contrib/pg_tde/sql/delete_principal_key.sql +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index 9efd91a809dc0..fa7f330a23948 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index 9cfb21ee2f5e6..a7cd4661274b1 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -1,3 +1,8 @@ +\! rm -f '/tmp/db-provider-file' +\! rm -f '/tmp/global-provider-file-1' +\! rm -f '/tmp/pg_tde_test_keyring.per' +\! rm -f '/tmp/pg_tde_test_keyring2.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT * FROM pg_tde_key_info(); diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index b77b1f4c35436..9889c1158f686 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_keyring.per' + CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index 19e57b1689b93..fea488ed2ad08 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 7a19ad35444ea..82fa8c3c73bef 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/tablespace.sql b/contrib/pg_tde/sql/tablespace.sql index 7e2abce87ca13..d7632f6670aeb 100644 --- a/contrib/pg_tde/sql/tablespace.sql +++ b/contrib/pg_tde/sql/tablespace.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/toast_decrypt.sql b/contrib/pg_tde/sql/toast_decrypt.sql index 4cd6cf513f618..1dfc671cb5baa 100644 --- a/contrib/pg_tde/sql/toast_decrypt.sql +++ b/contrib/pg_tde/sql/toast_decrypt.sql @@ -1,3 +1,5 @@ +\! rm -f '/tmp/pg_tde_test_keyring.per' + CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); From ceff90ec5607327143a526083ead4c96fe456cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 12 Jun 2025 22:10:15 +0200 Subject: [PATCH 475/796] Separate principal key creation from usage Add new functions pg_tde_create_key_using_database/global_key_provider() to create keys instead of key creation being a side effect of setting the key. Also remove support for "create if not exists" semantics as any user should know what keys their key provider contains. --- ci_scripts/tde_setup.sql | 1 + ci_scripts/tde_setup_global.sql | 1 + contrib/pg_tde/expected/access_control.out | 11 +- contrib/pg_tde/expected/alter_index.out | 6 + contrib/pg_tde/expected/cache_alloc.out | 6 + .../pg_tde/expected/change_access_method.out | 6 + contrib/pg_tde/expected/create_database.out | 12 ++ .../pg_tde/expected/default_principal_key.out | 16 +- .../pg_tde/expected/delete_principal_key.out | 12 ++ .../pg_tde/expected/insert_update_delete.out | 6 + contrib/pg_tde/expected/key_provider.out | 70 ++++++-- contrib/pg_tde/expected/kmip_test.out | 6 + contrib/pg_tde/expected/partition_table.out | 6 + .../pg_tde/expected/pg_tde_is_encrypted.out | 6 + contrib/pg_tde/expected/recreate_storage.out | 6 + contrib/pg_tde/expected/tablespace.out | 6 + contrib/pg_tde/expected/toast_decrypt.out | 6 + contrib/pg_tde/expected/vault_v2_test.out | 8 +- contrib/pg_tde/pg_tde--1.0-rc.sql | 28 +++- contrib/pg_tde/sql/access_control.sql | 9 +- contrib/pg_tde/sql/alter_index.sql | 1 + contrib/pg_tde/sql/cache_alloc.sql | 1 + contrib/pg_tde/sql/change_access_method.sql | 1 + contrib/pg_tde/sql/create_database.sql | 3 +- contrib/pg_tde/sql/default_principal_key.sql | 6 +- contrib/pg_tde/sql/delete_principal_key.sql | 2 + contrib/pg_tde/sql/insert_update_delete.sql | 1 + contrib/pg_tde/sql/key_provider.sql | 35 ++-- contrib/pg_tde/sql/kmip_test.sql | 1 + contrib/pg_tde/sql/partition_table.sql | 1 + contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 1 + contrib/pg_tde/sql/recreate_storage.sql | 1 + contrib/pg_tde/sql/tablespace.sql | 1 + contrib/pg_tde/sql/toast_decrypt.sql | 1 + contrib/pg_tde/sql/vault_v2_test.sql | 3 +- .../pg_tde/src/catalog/tde_principal_key.c | 150 ++++++++++++++---- contrib/pg_tde/t/basic.pl | 4 + contrib/pg_tde/t/change_key_provider.pl | 3 + contrib/pg_tde/t/crash_recovery.pl | 18 +++ contrib/pg_tde/t/expected/basic.out | 6 + .../pg_tde/t/expected/change_key_provider.out | 6 + contrib/pg_tde/t/expected/crash_recovery.out | 36 +++++ .../t/expected/key_rotate_tablespace.out | 12 ++ contrib/pg_tde/t/expected/replication.out | 12 ++ contrib/pg_tde/t/expected/rotate_key.out | 39 ++++- contrib/pg_tde/t/expected/tde_heap.out | 6 + contrib/pg_tde/t/expected/unlogged_tables.out | 6 + contrib/pg_tde/t/expected/wal_encrypt.out | 6 + contrib/pg_tde/t/key_rotate_tablespace.pl | 7 +- contrib/pg_tde/t/multiple_extensions.pl | 4 + contrib/pg_tde/t/pg_waldump_basic.pl | 3 + contrib/pg_tde/t/pg_waldump_fullpage.pl | 3 + contrib/pg_tde/t/replication.pl | 6 + contrib/pg_tde/t/rotate_key.pl | 25 ++- contrib/pg_tde/t/tde_heap.pl | 3 + contrib/pg_tde/t/unlogged_tables.pl | 3 + contrib/pg_tde/t/wal_encrypt.pl | 3 + 57 files changed, 563 insertions(+), 85 deletions(-) diff --git a/ci_scripts/tde_setup.sql b/ci_scripts/tde_setup.sql index c4d3289042792..dfce0a1b08c5d 100644 --- a/ci_scripts/tde_setup.sql +++ b/ci_scripts/tde_setup.sql @@ -2,4 +2,5 @@ CREATE SCHEMA IF NOT EXISTS tde; CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; \! rm -f '/tmp/pg_tde_test_keyring.per' SELECT tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT tde.pg_tde_create_key_using_database_key_provider('test-db-key', 'reg_file-vault'); SELECT tde.pg_tde_set_key_using_database_key_provider('test-db-key', 'reg_file-vault'); diff --git a/ci_scripts/tde_setup_global.sql b/ci_scripts/tde_setup_global.sql index de64310989d9e..f096285643a83 100644 --- a/ci_scripts/tde_setup_global.sql +++ b/ci_scripts/tde_setup_global.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; \! rm -f '/tmp/pg_tde_test_keyring.per' SELECT tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); +SELECT tde.pg_tde_create_key_using_global_key_provider('server-key', 'reg_file-global'); SELECT tde.pg_tde_set_server_key_using_global_key_provider('server-key', 'reg_file-global'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; ALTER SYSTEM SET default_table_access_method = 'tde_heap'; diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index da19b79298131..2dd199677cfa1 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -9,6 +9,8 @@ SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'local-file-provider'); +ERROR: permission denied for function pg_tde_create_key_using_database_key_provider SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); ERROR: permission denied for function pg_tde_set_key_using_database_key_provider SELECT pg_tde_delete_key(); @@ -35,11 +37,12 @@ GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_create_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_default_key() TO regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); @@ -54,6 +57,8 @@ SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_t ERROR: must be superuser to modify key providers SELECT pg_tde_delete_global_key_provider('global-file-provider'); ERROR: must be superuser to modify key providers +SELECT pg_tde_create_key_using_global_key_provider('key1', 'global-file-provider'); +ERROR: must be superuser to access global key providers SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index 83121582d0261..a4a627e7b97da 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index 31801767eadfa..bbd27168097fc 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -7,6 +7,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index 4e18699bf1cfa..3d582af882507 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_key (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index cf5827b81a1a6..ca31af47b1e3e 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -12,6 +12,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/template_provid (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -31,6 +37,12 @@ SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_pr (1 row) +SELECT pg_tde_create_key_using_global_key_provider('default-key', 'global-file-vault'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index b372401b9f7a4..a95337077e718 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -18,7 +18,13 @@ SELECT provider_id, provider_name, key_name | | (1 row) -SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); +SELECT pg_tde_create_key_using_global_key_provider('default-key', 'file-provider'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider'); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- @@ -100,7 +106,13 @@ SELECT provider_id, provider_name, key_name \c :regress_database CHECKPOINT; -SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); +SELECT pg_tde_create_key_using_global_key_provider('new-default-key', 'file-provider'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + +SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider'); pg_tde_set_default_key_using_global_key_provider -------------------------------------------------- diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 7faf2b9e446ef..17dda7d8ba435 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -6,6 +6,18 @@ SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_key (1 row) +SELECT pg_tde_create_key_using_global_key_provider('defalut-key','file-provider'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + +SELECT pg_tde_create_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + -- Set the local key and delete it without any encrypted tables -- Should succeed: nothing used the key SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index 8710a076b5c3f..88cc211d774f4 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 91850794e1e01..3be6375928e62 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -32,6 +32,12 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"} (2 rows) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_verify_key(); ERROR: principal key not configured for current database SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); @@ -113,7 +119,7 @@ SELECT id, name FROM pg_tde_list_all_global_key_providers(); -5 | file-keyring2 (2 rows) -SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring'); pg_tde_set_key_using_global_key_provider ------------------------------------------ @@ -232,6 +238,12 @@ SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provi (1 row) +SELECT pg_tde_create_key_using_global_key_provider('server-key', 'global-provider'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! pg_tde_set_server_key_using_global_key_provider @@ -253,6 +265,12 @@ SELECT current_database() AS regress_database CREATE DATABASE db_using_global_provider; \c db_using_global_provider; CREATE EXTENSION pg_tde; +SELECT pg_tde_create_key_using_global_key_provider('database-key', 'global-provider2'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2'); pg_tde_set_key_using_global_key_provider ------------------------------------------ @@ -272,6 +290,12 @@ SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-fi (1 row) +SELECT pg_tde_create_key_using_database_key_provider('database-key', 'db-provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -308,27 +332,39 @@ SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key name cannot be null -- Empty string is not allowed for a principal key name -SELECT pg_tde_set_default_key_using_global_key_provider('', 'file-keyring'); -ERROR: key name "" is too short -SELECT pg_tde_set_key_using_database_key_provider('', 'file-keyring'); -ERROR: key name "" is too short -SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); +SELECT pg_tde_create_key_using_database_key_provider('', 'file-provider'); ERROR: key name "" is too short -SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! +SELECT pg_tde_create_key_using_global_key_provider('', 'file-keyring'); ERROR: key name "" is too short --- Setting principal key fails if the key name is too long -SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); -ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long -HINT: Maximum length is 255 bytes. -SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provider'); +-- Creating principal key fails if the key name is too long +SELECT pg_tde_create_key_using_database_key_provider(repeat('K', 256), 'file-provider'); ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long HINT: Maximum length is 255 bytes. -SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +SELECT pg_tde_create_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long HINT: Maximum length is 255 bytes. -SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); +-- Creating principal key fails if key already exists +SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + +SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provider'); +ERROR: cannot to create key "existing-key" because it already exists +SELECT pg_tde_create_key_using_global_key_provider('existing-key','file-keyring'); +ERROR: cannot to create key "existing-key" because it already exists +-- Setting principal key fails if key does not exist +SELECT pg_tde_set_default_key_using_global_key_provider('not-existing', 'file-keyring'); +ERROR: key "not-existing" does not exist +HINT: Use pg_tde_create_key_using_global_key_provider() to create it. +SELECT pg_tde_set_key_using_database_key_provider('not-existing', 'file-keyring'); +ERROR: key provider "file-keyring" does not exists +SELECT pg_tde_set_key_using_global_key_provider('not-existing', 'file-keyring'); +ERROR: key "not-existing" does not exist +HINT: Use pg_tde_create_key_using_global_key_provider() to create it. +SELECT pg_tde_set_server_key_using_global_key_provider('not-existing', 'file-keyring'); WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! -ERROR: key name "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK" is too long -HINT: Maximum length is 255 bytes. +ERROR: key "not-existing" does not exist +HINT: Use pg_tde_create_key_using_global_key_provider() to create it. DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/expected/kmip_test.out b/contrib/pg_tde/expected/kmip_test.out index b363a6db94b2f..630dfe69c3509 100644 --- a/contrib/pg_tde/expected/kmip_test.out +++ b/contrib/pg_tde/expected/kmip_test.out @@ -5,6 +5,12 @@ SELECT pg_tde_add_database_key_provider_kmip('kmip-prov', '127.0.0.1', 5696, '/t (1 row) +SELECT pg_tde_create_key_using_database_key_provider('kmip-key','kmip-prov'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index 30f039585365e..e58c181d5bca2 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/p (1 row) +SELECT pg_tde_create_key_using_database_key_provider('table_key','database_keyring_provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index f3f7b1e9804c8..a6e13a57d9e13 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index 87dc760c31ea6..adfca5acb8d38 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index 93f790d4f815a..4349c31ac8019 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index 938aa01f28f21..7647a4e6795db 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyr (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 3a092b86dadfe..33d9f9175444f 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -8,7 +8,7 @@ SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127 (1 row) -- FAILS -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); +SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); ERROR: Invalid HTTP response from keyring provider "vault-incorrect": 404 CREATE TABLE test_enc( id SERIAL, @@ -23,6 +23,12 @@ SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1: (1 row) +SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-v2'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-v2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0-rc.sql index 242482dbc30c0..5d23b685bb67a 100644 --- a/contrib/pg_tde/pg_tde--1.0-rc.sql +++ b/contrib/pg_tde/pg_tde--1.0-rc.sql @@ -215,29 +215,41 @@ STRICT LANGUAGE C AS 'MODULE_PATHNAME'; -CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_create_key_using_database_key_provider(key_name TEXT, provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -REVOKE ALL ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_tde_create_key_using_database_key_provider(TEXT, TEXT) FROM PUBLIC; -CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_create_key_using_global_key_provider(key_name TEXT, provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -REVOKE ALL ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_tde_create_key_using_global_key_provider(TEXT, TEXT) FROM PUBLIC; -CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_database_key_provider(key_name TEXT, provider_name TEXT) RETURNS VOID LANGUAGE C AS 'MODULE_PATHNAME'; -REVOKE ALL ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_tde_set_key_using_database_key_provider(TEXT, TEXT) FROM PUBLIC; -CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT, ensure_new_key BOOLEAN DEFAULT FALSE) +CREATE FUNCTION pg_tde_set_key_using_global_key_provider(key_name TEXT, provider_name TEXT) +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT) FROM PUBLIC; + +CREATE FUNCTION pg_tde_set_server_key_using_global_key_provider(key_name TEXT, provider_name TEXT) +RETURNS VOID +LANGUAGE C +AS 'MODULE_PATHNAME'; +REVOKE ALL ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT) FROM PUBLIC; + +CREATE FUNCTION pg_tde_set_default_key_using_global_key_provider(key_name TEXT, provider_name TEXT) RETURNS VOID AS 'MODULE_PATHNAME' LANGUAGE C; -REVOKE ALL ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT) FROM PUBLIC; CREATE FUNCTION pg_tde_verify_key() RETURNS VOID diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 7ffa455cdf7ba..78de2d3602d7d 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -9,6 +9,7 @@ CREATE USER regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; -- should throw access denied +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'local-file-provider'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'local-file-provider'); SELECT pg_tde_delete_key(); SELECT pg_tde_list_all_database_key_providers(); @@ -27,11 +28,12 @@ GRANT EXECUTE ON FUNCTION pg_tde_add_database_key_provider(TEXT, TEXT, JSON) TO GRANT EXECUTE ON FUNCTION pg_tde_add_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_database_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_change_global_key_provider(TEXT, TEXT, JSON) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_create_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_database_key_provider(TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_global_key_provider(TEXT) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; -GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT, BOOLEAN) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_default_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; +GRANT EXECUTE ON FUNCTION pg_tde_set_server_key_using_global_key_provider(TEXT, TEXT) TO regress_pg_tde_access_control; GRANT EXECUTE ON FUNCTION pg_tde_delete_default_key() TO regress_pg_tde_access_control; SET ROLE regress_pg_tde_access_control; @@ -42,6 +44,7 @@ SELECT pg_tde_delete_database_key_provider('local-file-provider'); SELECT pg_tde_add_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_change_global_key_provider_file('global-file-provider', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_delete_global_key_provider('global-file-provider'); +SELECT pg_tde_create_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 7af5617ea73b7..23283386f465d 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/cache_alloc.sql b/contrib/pg_tde/sql/cache_alloc.sql index acaf5146711c6..5098c26138681 100644 --- a/contrib/pg_tde/sql/cache_alloc.sql +++ b/contrib/pg_tde/sql/cache_alloc.sql @@ -5,6 +5,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); do $$ diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index 6949ea7054aea..cd4ff512c7e9d 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); CREATE TABLE country_table ( diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql index 4ef8d4ed6fe42..604293dd24a5a 100644 --- a/contrib/pg_tde/sql/create_database.sql +++ b/contrib/pg_tde/sql/create_database.sql @@ -13,6 +13,7 @@ SELECT current_database() AS regress_database CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/template_provider.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); CREATE TABLE test_enc (id serial PRIMARY KEY, x int) USING tde_heap; @@ -27,7 +28,7 @@ INSERT INTO test_plain (x) VALUES (30), (40); --CREATE DATABASE new_db TEMPLATE template_db; SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per'); - +SELECT pg_tde_create_key_using_global_key_provider('default-key', 'global-file-vault'); SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault'); CREATE DATABASE new_db TEMPLATE template_db; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 29422cd3d575a..20acfd3eb3e99 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -12,7 +12,8 @@ SELECT pg_tde_verify_default_key(); SELECT provider_id, provider_name, key_name FROM pg_tde_default_key_info(); -SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider', false); +SELECT pg_tde_create_key_using_global_key_provider('default-key', 'file-provider'); +SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'file-provider'); SELECT pg_tde_verify_default_key(); SELECT provider_id, provider_name, key_name @@ -70,7 +71,8 @@ SELECT provider_id, provider_name, key_name CHECKPOINT; -SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider', false); +SELECT pg_tde_create_key_using_global_key_provider('new-default-key', 'file-provider'); +SELECT pg_tde_set_default_key_using_global_key_provider('new-default-key', 'file-provider'); SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql index 9c2aa36271a27..262d456933555 100644 --- a/contrib/pg_tde/sql/delete_principal_key.sql +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -3,6 +3,8 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_global_key_provider('defalut-key','file-provider'); +SELECT pg_tde_create_key_using_global_key_provider('test-db-key','file-provider'); -- Set the local key and delete it without any encrypted tables -- Should succeed: nothing used the key diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index fa7f330a23948..3ec1a8014d4e4 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE albums ( diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index a7cd4661274b1..ad4467e787dd3 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -13,6 +13,8 @@ SELECT pg_tde_add_database_key_provider_file('file-provider2','/tmp/pg_tde_test_ SELECT pg_tde_add_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_dup.per'); SELECT * FROM pg_tde_list_all_database_key_providers(); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-provider'); + SELECT pg_tde_verify_key(); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-provider'); SELECT pg_tde_verify_key(); @@ -39,7 +41,7 @@ SELECT id, name FROM pg_tde_list_all_database_key_providers(); SELECT id, name FROM pg_tde_list_all_global_key_providers(); -SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring', false); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring'); -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); @@ -108,6 +110,7 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": tr -- Modifying key providers fails if new settings can't fetch existing server key SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1'); +SELECT pg_tde_create_key_using_global_key_provider('server-key', 'global-provider'); SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2'); @@ -118,6 +121,7 @@ SELECT current_database() AS regress_database CREATE DATABASE db_using_global_provider; \c db_using_global_provider; CREATE EXTENSION pg_tde; +SELECT pg_tde_create_key_using_global_key_provider('database-key', 'global-provider2'); SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2'); \c :regress_database SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2'); @@ -126,6 +130,7 @@ CREATE DATABASE db_using_database_provider; \c db_using_database_provider; CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file'); +SELECT pg_tde_create_key_using_database_key_provider('database-key', 'db-provider'); SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider'); SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2'); \c :regress_database @@ -148,16 +153,22 @@ SELECT pg_tde_set_key_using_global_key_provider(NULL, 'file-keyring'); SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); -- Empty string is not allowed for a principal key name -SELECT pg_tde_set_default_key_using_global_key_provider('', 'file-keyring'); -SELECT pg_tde_set_key_using_database_key_provider('', 'file-keyring'); -SELECT pg_tde_set_key_using_global_key_provider('', 'file-keyring'); -SELECT pg_tde_set_server_key_using_global_key_provider('', 'file-keyring'); - --- Setting principal key fails if the key name is too long -SELECT pg_tde_set_default_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); -SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provider'); -SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); -SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); - +SELECT pg_tde_create_key_using_database_key_provider('', 'file-provider'); +SELECT pg_tde_create_key_using_global_key_provider('', 'file-keyring'); + +-- Creating principal key fails if the key name is too long +SELECT pg_tde_create_key_using_database_key_provider(repeat('K', 256), 'file-provider'); +SELECT pg_tde_create_key_using_global_key_provider(repeat('K', 256), 'file-keyring'); + +-- Creating principal key fails if key already exists +SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provider'); +SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provider'); +SELECT pg_tde_create_key_using_global_key_provider('existing-key','file-keyring'); + +-- Setting principal key fails if key does not exist +SELECT pg_tde_set_default_key_using_global_key_provider('not-existing', 'file-keyring'); +SELECT pg_tde_set_key_using_database_key_provider('not-existing', 'file-keyring'); +SELECT pg_tde_set_key_using_global_key_provider('not-existing', 'file-keyring'); +SELECT pg_tde_set_server_key_using_global_key_provider('not-existing', 'file-keyring'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/kmip_test.sql b/contrib/pg_tde/sql/kmip_test.sql index eedc14c6e7f5e..0e148566f52bd 100644 --- a/contrib/pg_tde/sql/kmip_test.sql +++ b/contrib/pg_tde/sql/kmip_test.sql @@ -1,6 +1,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_kmip('kmip-prov', '127.0.0.1', 5696, '/tmp/client_certificate_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem'); +SELECT pg_tde_create_key_using_database_key_provider('kmip-key','kmip-prov'); SELECT pg_tde_set_key_using_database_key_provider('kmip-key','kmip-prov'); CREATE TABLE test_enc( diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index 9889c1158f686..0885e55930c10 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -2,6 +2,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('database_keyring_provider','/tmp/pg_tde_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('table_key','database_keyring_provider'); SELECT pg_tde_set_key_using_database_key_provider('table_key','database_keyring_provider'); CREATE TABLE IF NOT EXISTS partitioned_table ( id SERIAL, diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index fea488ed2ad08..5fb492c152146 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE test_enc ( diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 82fa8c3c73bef..60804f6e379db 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); SET default_table_access_method = "tde_heap"; diff --git a/contrib/pg_tde/sql/tablespace.sql b/contrib/pg_tde/sql/tablespace.sql index d7632f6670aeb..413a1159ba2de 100644 --- a/contrib/pg_tde/sql/tablespace.sql +++ b/contrib/pg_tde/sql/tablespace.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE test(num1 bigint, num2 double precision, t text) USING tde_heap; diff --git a/contrib/pg_tde/sql/toast_decrypt.sql b/contrib/pg_tde/sql/toast_decrypt.sql index 1dfc671cb5baa..34d8341c4ebd8 100644 --- a/contrib/pg_tde/sql/toast_decrypt.sql +++ b/contrib/pg_tde/sql/toast_decrypt.sql @@ -3,6 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); +SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); SELECT pg_tde_set_key_using_database_key_provider('test-db-key','file-vault'); CREATE TABLE src (f1 TEXT STORAGE EXTERNAL) USING tde_heap; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index e43dc3798d7fe..8b95e1cf27dce 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -5,7 +5,7 @@ CREATE EXTENSION IF NOT EXISTS pg_tde; SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); -- FAILS -SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); +SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); CREATE TABLE test_enc( id SERIAL, @@ -14,6 +14,7 @@ CREATE TABLE test_enc( ) USING tde_heap; SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', :'cacert_file'); +SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-v2'); SELECT pg_tde_set_key_using_database_key_provider('vault-v2-key', 'vault-v2'); CREATE TABLE test_enc( diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 98f148c233de5..3944d123825db 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -85,14 +85,14 @@ static void push_principal_key_to_cache(TDEPrincipalKey *principalKey); static Datum pg_tde_get_key_info(PG_FUNCTION_ARGS, Oid dbOid); static TDEPrincipalKey *get_principal_key_from_keyring(Oid dbOid); static TDEPrincipalKey *GetPrincipalKeyNoDefault(Oid dbOid, LWLockMode lockMode); -static void set_principal_key_with_keyring(const char *key_name, - const char *provider_name, - Oid providerOid, - Oid dbOid, - bool ensure_new_key); static bool pg_tde_verify_principal_key_internal(Oid databaseOid); +static void pg_tde_create_principal_key_internal(Oid providerOid, const char *key_name, const char *provider_name); static void pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKeyTemplate); +static void pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *principal_key_name, const char *provider_name); +static void set_principal_key_with_keyring(const char *key_name, const char *provider_name, Oid providerOid, Oid dbOid); +PG_FUNCTION_INFO_V1(pg_tde_create_key_using_database_key_provider); +PG_FUNCTION_INFO_V1(pg_tde_create_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_default_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_database_key_provider); PG_FUNCTION_INFO_V1(pg_tde_set_key_using_global_key_provider); @@ -100,8 +100,6 @@ PG_FUNCTION_INFO_V1(pg_tde_set_server_key_using_global_key_provider); PG_FUNCTION_INFO_V1(pg_tde_delete_key); PG_FUNCTION_INFO_V1(pg_tde_delete_default_key); -static void pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *principal_key_name, const char *provider_name, bool ensure_new_key); - /* * Request some pages so we can fit the DSA header, empty hash table plus some * extra. Additional memory to grow the hash map will be allocated as needed @@ -217,8 +215,10 @@ principal_key_info_attach_shmem(void) } void -set_principal_key_with_keyring(const char *key_name, const char *provider_name, - Oid providerOid, Oid dbOid, bool ensure_new_key) +set_principal_key_with_keyring(const char *key_name, + const char *provider_name, + Oid providerOid, + Oid dbOid) { TDEPrincipalKey *curr_principal_key; TDEPrincipalKey *new_principal_key; @@ -249,16 +249,20 @@ set_principal_key_with_keyring(const char *key_name, const char *provider_name, } } - if (keyInfo != NULL && ensure_new_key) + if (!keyInfo) { - ereport(ERROR, - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot to create key \"%s\" because it already exists", key_name)); + if (providerOid == GLOBAL_DATA_TDE_OID) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key \"%s\" does not exist", key_name), + errhint("Use pg_tde_create_key_using_global_key_provider() to create it.")); + else + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key \"%s\" does not exist", key_name), + errhint("Use pg_tde_create_key_using_database_key_provider() to create it.")); } - if (keyInfo == NULL) - keyInfo = KeyringGenerateNewKeyAndStore(new_keyring, key_name, PRINCIPAL_KEY_LEN); - new_principal_key = palloc_object(TDEPrincipalKey); new_principal_key->keyInfo.databaseId = dbOid; new_principal_key->keyInfo.keyringId = new_keyring->keyring_id; @@ -441,15 +445,99 @@ clear_principal_key_cache(Oid databaseId) * SQL interface to set principal key */ +Datum +pg_tde_create_key_using_database_key_provider(PG_FUNCTION_ARGS) +{ + char *key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); + + pg_tde_create_principal_key_internal(MyDatabaseId, + key_name, + provider_name); + + PG_RETURN_VOID(); +} + +Datum +pg_tde_create_key_using_global_key_provider(PG_FUNCTION_ARGS) +{ + char *key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); + + pg_tde_create_principal_key_internal(GLOBAL_DATA_TDE_OID, + key_name, + provider_name); + + PG_RETURN_VOID(); +} + +static void +pg_tde_create_principal_key_internal(Oid providerOid, + const char *key_name, + const char *provider_name) +{ + + GenericKeyring *provider; + KeyInfo *key_info; + KeyringReturnCodes return_code; + + if (providerOid == GLOBAL_DATA_TDE_OID && !superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to access global key providers")); + if (providerOid == GLOBAL_DATA_TDE_OID && !AllowInheritGlobalProviders) + ereport(ERROR, + errmsg("usage of global key providers is disabled"), + errhint("Set \"pg_tde.inherit_global_providers = on\" in postgresql.conf.")); + + if (key_name == NULL) + ereport(ERROR, + errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("key name cannot be null")); + if (strlen(key_name) == 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key name \"\" is too short")); + if (strlen(key_name) >= PRINCIPAL_KEY_NAME_LEN) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("key name \"%s\" is too long", key_name), + errhint("Maximum length is %d bytes.", PRINCIPAL_KEY_NAME_LEN - 1)); + if (provider_name == NULL) + ereport(ERROR, + errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("key provider name cannot be null")); + + provider = GetKeyProviderByName(provider_name, providerOid); + + key_info = KeyringGetKey(provider, key_name, &return_code); + + if (return_code != KEYRING_CODE_SUCCESS) + ereport(ERROR, + errmsg("could not successfully query key provider \"%s\"", provider->provider_name)); + + if (key_info != NULL) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot to create key \"%s\" because it already exists", key_name)); + + key_info = KeyringGenerateNewKeyAndStore(provider, key_name, PRINCIPAL_KEY_LEN); + + pfree(key_info); + pfree(provider); +} + Datum pg_tde_set_default_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); - bool ensure_new_key = PG_GETARG_BOOL(2); /* Using a global provider for the default encryption setting */ - pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, DEFAULT_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, + DEFAULT_DATA_TDE_OID, + principal_key_name, + provider_name); PG_RETURN_VOID(); } @@ -459,10 +547,12 @@ pg_tde_set_key_using_database_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); - bool ensure_new_key = PG_GETARG_BOOL(2); /* Using a local provider for the current database */ - pg_tde_set_principal_key_internal(MyDatabaseId, MyDatabaseId, principal_key_name, provider_name, ensure_new_key); + pg_tde_set_principal_key_internal(MyDatabaseId, + MyDatabaseId, + principal_key_name, + provider_name); PG_RETURN_VOID(); } @@ -472,10 +562,12 @@ pg_tde_set_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); - bool ensure_new_key = PG_GETARG_BOOL(2); /* Using a global provider for the current database */ - pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, MyDatabaseId, principal_key_name, provider_name, ensure_new_key); + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, + MyDatabaseId, + principal_key_name, + provider_name); PG_RETURN_VOID(); } @@ -485,19 +577,24 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) { char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); - bool ensure_new_key = PG_GETARG_BOOL(2); ereport(WARNING, errmsg("The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments!")); /* Using a global provider for the global (wal) database */ - pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, GLOBAL_DATA_TDE_OID, principal_key_name, provider_name, ensure_new_key); + pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, + GLOBAL_DATA_TDE_OID, + principal_key_name, + provider_name); PG_RETURN_VOID(); } static void -pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_name, const char *provider_name, bool ensure_new_key) +pg_tde_set_principal_key_internal(Oid providerOid, + Oid dbOid, + const char *key_name, + const char *provider_name) { TDEPrincipalKey *existingDefaultKey = NULL; TDEPrincipalKey existingKeyCopy; @@ -546,8 +643,7 @@ pg_tde_set_principal_key_internal(Oid providerOid, Oid dbOid, const char *key_na set_principal_key_with_keyring(key_name, provider_name, providerOid, - dbOid, - ensure_new_key); + dbOid); if (dbOid == DEFAULT_DATA_TDE_OID && existingDefaultKey != NULL) { diff --git a/contrib/pg_tde/t/basic.pl b/contrib/pg_tde/t/basic.pl index 8eea2d39d3d6c..7d43a3bfcadac 100644 --- a/contrib/pg_tde/t/basic.pl +++ b/contrib/pg_tde/t/basic.pl @@ -46,6 +46,10 @@ "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_001_basic.per');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault');" +); + PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); diff --git a/contrib/pg_tde/t/change_key_provider.pl b/contrib/pg_tde/t/change_key_provider.pl index 4dbc262cf2d95..78ee711596290 100644 --- a/contrib/pg_tde/t/change_key_provider.pl +++ b/contrib/pg_tde/t/change_key_provider.pl @@ -25,6 +25,9 @@ ); PGTDE::psql($node, 'postgres', "SELECT * FROM pg_tde_list_all_database_key_providers();"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" ); diff --git a/contrib/pg_tde/t/crash_recovery.pl b/contrib/pg_tde/t/crash_recovery.pl index b4f75010ac3bd..0c4a85c654683 100644 --- a/contrib/pg_tde/t/crash_recovery.pl +++ b/contrib/pg_tde/t/crash_recovery.pl @@ -24,12 +24,18 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key', 'global_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'global_keyring');" ); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('db_key', 'db_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('db_key', 'db_keyring');" ); @@ -51,9 +57,15 @@ $node->start; PGTDE::append_to_result_file("-- rotate wal key"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('db_key_1', 'db_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring');" ); @@ -67,9 +79,15 @@ $node->start; PGTDE::append_to_result_file("-- rotate wal key"); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('db_key_2', 'db_keyring');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring');" ); diff --git a/contrib/pg_tde/t/expected/basic.out b/contrib/pg_tde/t/expected/basic.out index 99020c9439217..7070fb44af8a5 100644 --- a/contrib/pg_tde/t/expected/basic.out +++ b/contrib/pg_tde/t/expected/basic.out @@ -32,6 +32,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_001 (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index fc7858c7f684e..cebcfa858ac2d 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -11,6 +11,12 @@ SELECT * FROM pg_tde_list_all_database_key_providers(); 1 | file-vault | file | {"path" : "/tmp/change_key_provider_1.per"} (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/crash_recovery.out b/contrib/pg_tde/t/expected/crash_recovery.out index 1bbaf536931f1..6b5958842a3cd 100644 --- a/contrib/pg_tde/t/expected/crash_recovery.out +++ b/contrib/pg_tde/t/expected/crash_recovery.out @@ -5,6 +5,12 @@ SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recover (1 row) +SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key', 'global_keyring'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'global_keyring'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -18,6 +24,12 @@ SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery. (1 row) +SELECT pg_tde_create_key_using_database_key_provider('db_key', 'db_keyring'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('db_key', 'db_keyring'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -32,6 +44,12 @@ ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; -- kill -9 -- server start -- rotate wal key +SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', 'global_keyring'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -39,6 +57,12 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', ' (1 row) psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! +SELECT pg_tde_create_key_using_database_key_provider('db_key_1', 'db_keyring'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('db_key_1', 'db_keyring'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -50,6 +74,12 @@ INSERT INTO test_enc (x) VALUES (3), (4); -- server start -- check that pg_tde_save_principal_key_redo hasn't destroyed a WAL key created during the server start -- rotate wal key +SELECT pg_tde_create_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', 'global_keyring'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -57,6 +87,12 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', ' (1 row) psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! +SELECT pg_tde_create_key_using_database_key_provider('db_key_2', 'db_keyring'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('db_key_2', 'db_keyring'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/key_rotate_tablespace.out b/contrib/pg_tde/t/expected/key_rotate_tablespace.out index 17559d72d24b9..c850828f9f95d 100644 --- a/contrib/pg_tde/t/expected/key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/key_rotate_tablespace.out @@ -7,6 +7,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tabl (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -30,6 +36,12 @@ SELECT * FROM country_table; 3 | USA | North America (3 rows) +SELECT pg_tde_create_key_using_database_key_provider('new-k', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index 037e04ee662ef..9b44c223f2943 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -6,6 +6,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -62,6 +68,12 @@ SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.p (1 row) +SELECT pg_tde_create_key_using_global_key_provider('test-global-key', 'file-vault'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file-vault'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 015375c62f78c..4fc776991159c 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -30,6 +30,12 @@ SELECT pg_tde_list_all_database_key_providers(); (2,file-2,file,"{""path"" : ""/tmp/rotate_key_2.per""}") (2 rows) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -45,6 +51,12 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) +SELECT pg_tde_create_key_using_database_key_provider('rotated-key1', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('rotated-key1', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -78,6 +90,12 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) +SELECT pg_tde_create_key_using_database_key_provider('rotated-key2', 'file-2'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2'); pg_tde_set_key_using_database_key_provider -------------------------------------------- @@ -111,7 +129,13 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) -SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false); +SELECT pg_tde_create_key_using_global_key_provider('rotated-key', 'file-3'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3'); pg_tde_set_key_using_global_key_provider ------------------------------------------ @@ -144,7 +168,13 @@ SELECT * FROM test_enc ORDER BY id; 2 | 6 (2 rows) -SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false); +SELECT pg_tde_create_key_using_global_key_provider('rotated-keyX', 'file-2'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2'); pg_tde_set_key_using_global_key_provider ------------------------------------------ @@ -179,7 +209,10 @@ SELECT * FROM test_enc ORDER BY id; ALTER SYSTEM SET pg_tde.inherit_global_providers = off; -- server restart -SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false); +SELECT pg_tde_create_key_using_global_key_provider('rotated-keyX2', 'file-2'); +psql::1: ERROR: usage of global key providers is disabled +HINT: Set "pg_tde.inherit_global_providers = on" in postgresql.conf. +SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2'); psql::1: ERROR: usage of global key providers is disabled HINT: Set "pg_tde.inherit_global_providers = on" in postgresql.conf. SELECT provider_id, provider_name, key_name FROM pg_tde_key_info(); diff --git a/contrib/pg_tde/t/expected/tde_heap.out b/contrib/pg_tde/t/expected/tde_heap.out index f49a3586eb0aa..e0083b15570b0 100644 --- a/contrib/pg_tde/t/expected/tde_heap.out +++ b/contrib/pg_tde/t/expected/tde_heap.out @@ -5,6 +5,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per'); (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/unlogged_tables.out b/contrib/pg_tde/t/expected/unlogged_tables.out index b507e48ff3ba4..4c854576016e5 100644 --- a/contrib/pg_tde/t/expected/unlogged_tables.out +++ b/contrib/pg_tde/t/expected/unlogged_tables.out @@ -5,6 +5,12 @@ SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables (1 row) +SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault'); pg_tde_set_key_using_database_key_provider -------------------------------------------- diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 6a2bfa6100e8d..a1a22557980a2 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -13,6 +13,12 @@ SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info(); | | (1 row) +SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-010'); + pg_tde_create_key_using_global_key_provider +--------------------------------------------- + +(1 row) + SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010'); pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- diff --git a/contrib/pg_tde/t/key_rotate_tablespace.pl b/contrib/pg_tde/t/key_rotate_tablespace.pl index 5168d22527924..ff30aa3f33dcc 100644 --- a/contrib/pg_tde/t/key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/key_rotate_tablespace.pl @@ -26,6 +26,9 @@ PGTDE::psql($node, 'tbc', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tablespace.per');" ); +PGTDE::psql($node, 'tbc', + "SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault');" +); PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); @@ -48,7 +51,9 @@ "); PGTDE::psql($node, 'tbc', 'SELECT * FROM country_table;'); - +PGTDE::psql($node, 'tbc', + "SELECT pg_tde_create_key_using_database_key_provider('new-k', 'file-vault');" +); PGTDE::psql($node, 'tbc', "SELECT pg_tde_set_key_using_database_key_provider('new-k', 'file-vault');" ); diff --git a/contrib/pg_tde/t/multiple_extensions.pl b/contrib/pg_tde/t/multiple_extensions.pl index 2137082ca973f..3f52d0ea6528c 100644 --- a/contrib/pg_tde/t/multiple_extensions.pl +++ b/contrib/pg_tde/t/multiple_extensions.pl @@ -123,6 +123,10 @@ 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-provider', json_object('type' VALUE 'file', 'path' VALUE '/tmp/datafile-location'));", extra_params => ['-a']); +$node->psql( + 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-provider');", + extra_params => ['-a']); $node->psql( 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-provider');", diff --git a/contrib/pg_tde/t/pg_waldump_basic.pl b/contrib/pg_tde/t/pg_waldump_basic.pl index 7ec14eed121b6..5f86b7929ce32 100644 --- a/contrib/pg_tde/t/pg_waldump_basic.pl +++ b/contrib/pg_tde/t/pg_waldump_basic.pl @@ -32,6 +32,9 @@ $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_basic.per');" ); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" ); diff --git a/contrib/pg_tde/t/pg_waldump_fullpage.pl b/contrib/pg_tde/t/pg_waldump_fullpage.pl index 3caf2cfbcf329..b543b46bea456 100644 --- a/contrib/pg_tde/t/pg_waldump_fullpage.pl +++ b/contrib/pg_tde/t/pg_waldump_fullpage.pl @@ -46,6 +46,9 @@ sub get_block_lsn $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_fullpage.per');" ); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); $node->safe_psql('postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" ); diff --git a/contrib/pg_tde/t/replication.pl b/contrib/pg_tde/t/replication.pl index 21fbcdfbd1929..0a53fead4d3cb 100644 --- a/contrib/pg_tde/t/replication.pl +++ b/contrib/pg_tde/t/replication.pl @@ -32,6 +32,9 @@ PGTDE::psql($primary, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per');" ); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault');" +); PGTDE::psql($primary, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" ); @@ -65,6 +68,9 @@ PGTDE::psql($primary, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" ); +PGTDE::psql($primary, 'postgres', + "SELECT pg_tde_create_key_using_global_key_provider('test-global-key', 'file-vault');" +); PGTDE::psql($primary, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file-vault');" ); diff --git a/contrib/pg_tde/t/rotate_key.pl b/contrib/pg_tde/t/rotate_key.pl index b60c4b5836186..b836e664d1370 100644 --- a/contrib/pg_tde/t/rotate_key.pl +++ b/contrib/pg_tde/t/rotate_key.pl @@ -36,7 +36,9 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_list_all_database_key_providers();"); - +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); @@ -50,6 +52,9 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Rotate key +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('rotated-key1', 'file-vault');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key1', 'file-vault');" ); @@ -66,6 +71,9 @@ PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); # Again rotate key +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('rotated-key2', 'file-2');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('rotated-key2', 'file-2');" ); @@ -83,7 +91,10 @@ # Again rotate key PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3', false);" + "SELECT pg_tde_create_key_using_global_key_provider('rotated-key', 'file-3');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-key', 'file-3');" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -102,7 +113,10 @@ # Again rotate key PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2', false);" + "SELECT pg_tde_create_key_using_global_key_provider('rotated-keyX', 'file-2');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX', 'file-2');" ); PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;'); @@ -125,7 +139,10 @@ # But now can't be changed to another global provider PGTDE::psql($node, 'postgres', - "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2', false);" + "SELECT pg_tde_create_key_using_global_key_provider('rotated-keyX2', 'file-2');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_global_key_provider('rotated-keyX2', 'file-2');" ); PGTDE::psql($node, 'postgres', "SELECT provider_id, provider_name, key_name FROM pg_tde_key_info();"); diff --git a/contrib/pg_tde/t/tde_heap.pl b/contrib/pg_tde/t/tde_heap.pl index 1983c1e1b2eca..f0e6c0376b965 100644 --- a/contrib/pg_tde/t/tde_heap.pl +++ b/contrib/pg_tde/t/tde_heap.pl @@ -21,6 +21,9 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-db-key', 'file-vault');" ); diff --git a/contrib/pg_tde/t/unlogged_tables.pl b/contrib/pg_tde/t/unlogged_tables.pl index 3482d93e05ffd..742240d0f2169 100644 --- a/contrib/pg_tde/t/unlogged_tables.pl +++ b/contrib/pg_tde/t/unlogged_tables.pl @@ -20,6 +20,9 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('test-key', 'file-vault');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');" ); diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index 42ef87fe3682c..33b3adfa7136e 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -31,6 +31,9 @@ 'SELECT key_name, provider_name, provider_id FROM pg_tde_server_key_info();' ); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-010');" +); PGTDE::psql($node, 'postgres', "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-010');" ); From 7082cf86caa256a4424246214b4a414f0784f733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 13 Jun 2025 11:33:49 +0200 Subject: [PATCH 476/796] Be confident about pg_tde not existing in tests We no longer run these tests when pg_tde is turned on globally. There is no reason for us to CREATE IF NOT EXISTS in tests as we should _know_ what state the database is in when running them. --- contrib/pg_tde/expected/access_control.out | 2 +- contrib/pg_tde/expected/alter_index.out | 2 +- contrib/pg_tde/expected/cache_alloc.out | 2 +- contrib/pg_tde/expected/change_access_method.out | 2 +- contrib/pg_tde/expected/create_database.out | 2 +- contrib/pg_tde/expected/default_principal_key.out | 4 ++-- contrib/pg_tde/expected/delete_principal_key.out | 2 +- contrib/pg_tde/expected/insert_update_delete.out | 2 +- contrib/pg_tde/expected/key_provider.out | 2 +- contrib/pg_tde/expected/pg_tde_is_encrypted.out | 2 +- contrib/pg_tde/expected/recreate_storage.out | 2 +- contrib/pg_tde/expected/relocate.out | 3 --- contrib/pg_tde/expected/tablespace.out | 2 +- contrib/pg_tde/expected/toast_decrypt.out | 2 +- contrib/pg_tde/expected/vault_v2_test.out | 2 +- contrib/pg_tde/sql/access_control.sql | 2 +- contrib/pg_tde/sql/alter_index.sql | 2 +- contrib/pg_tde/sql/cache_alloc.sql | 2 +- contrib/pg_tde/sql/change_access_method.sql | 2 +- contrib/pg_tde/sql/create_database.sql | 2 +- contrib/pg_tde/sql/default_principal_key.sql | 4 ++-- contrib/pg_tde/sql/delete_principal_key.sql | 2 +- contrib/pg_tde/sql/insert_update_delete.sql | 2 +- contrib/pg_tde/sql/key_provider.sql | 2 +- contrib/pg_tde/sql/pg_tde_is_encrypted.sql | 2 +- contrib/pg_tde/sql/recreate_storage.sql | 2 +- contrib/pg_tde/sql/relocate.sql | 4 ---- contrib/pg_tde/sql/tablespace.sql | 2 +- contrib/pg_tde/sql/toast_decrypt.sql | 2 +- contrib/pg_tde/sql/vault_v2_test.sql | 2 +- contrib/pg_tde/t/basic.pl | 2 +- contrib/pg_tde/t/change_key_provider.pl | 2 +- contrib/pg_tde/t/crash_recovery.pl | 2 +- contrib/pg_tde/t/expected/basic.out | 2 +- contrib/pg_tde/t/expected/change_key_provider.out | 2 +- contrib/pg_tde/t/expected/crash_recovery.out | 2 +- contrib/pg_tde/t/expected/key_rotate_tablespace.out | 2 +- contrib/pg_tde/t/expected/multiple_extensions.out | 2 +- contrib/pg_tde/t/expected/replication.out | 2 +- contrib/pg_tde/t/expected/rotate_key.out | 2 +- contrib/pg_tde/t/expected/tde_heap.out | 2 +- contrib/pg_tde/t/expected/unlogged_tables.out | 2 +- contrib/pg_tde/t/expected/wal_encrypt.out | 2 +- contrib/pg_tde/t/key_rotate_tablespace.pl | 2 +- contrib/pg_tde/t/multiple_extensions.pl | 6 ++---- contrib/pg_tde/t/pg_tde_change_key_provider.pl | 2 +- contrib/pg_tde/t/pg_waldump_basic.pl | 2 +- contrib/pg_tde/t/pg_waldump_fullpage.pl | 2 +- contrib/pg_tde/t/replication.pl | 2 +- contrib/pg_tde/t/rotate_key.pl | 2 +- contrib/pg_tde/t/tde_heap.pl | 2 +- contrib/pg_tde/t/unlogged_tables.pl | 2 +- contrib/pg_tde/t/wal_encrypt.pl | 2 +- 53 files changed, 54 insertions(+), 63 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 2dd199677cfa1..22da3aff28df2 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/alter_index.out b/contrib/pg_tde/expected/alter_index.out index a4a627e7b97da..667c32687baef 100644 --- a/contrib/pg_tde/expected/alter_index.out +++ b/contrib/pg_tde/expected/alter_index.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/cache_alloc.out b/contrib/pg_tde/expected/cache_alloc.out index bbd27168097fc..528d83ed40c52 100644 --- a/contrib/pg_tde/expected/cache_alloc.out +++ b/contrib/pg_tde/expected/cache_alloc.out @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -- Just checking there are no mem debug WARNINGs during the cache population -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/change_access_method.out b/contrib/pg_tde/expected/change_access_method.out index 3d582af882507..d33b3e68f10aa 100644 --- a/contrib/pg_tde/expected/change_access_method.out +++ b/contrib/pg_tde/expected/change_access_method.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/create_database.out b/contrib/pg_tde/expected/create_database.out index ca31af47b1e3e..e320dea12c0e0 100644 --- a/contrib/pg_tde/expected/create_database.out +++ b/contrib/pg_tde/expected/create_database.out @@ -1,6 +1,6 @@ \! rm -f '/tmp/template_provider_global.per' \! rm -f '/tmp/template_provider.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; CREATE DATABASE template_db; SELECT current_database() AS regress_database \gset diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index a95337077e718..72ec579b6de3b 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_regression_default_key.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE EXTENSION IF NOT EXISTS pg_buffercache; +CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 17dda7d8ba435..550bcc217b2dd 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/expected/insert_update_delete.out b/contrib/pg_tde/expected/insert_update_delete.out index 88cc211d774f4..97e7f18d1aae5 100644 --- a/contrib/pg_tde/expected/insert_update_delete.out +++ b/contrib/pg_tde/expected/insert_update_delete.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 3be6375928e62..8110100372452 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -2,7 +2,7 @@ \! rm -f '/tmp/global-provider-file-1' \! rm -f '/tmp/pg_tde_test_keyring.per' \! rm -f '/tmp/pg_tde_test_keyring2.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT * FROM pg_tde_key_info(); key_name | provider_name | provider_id | key_creation_time ----------+---------------+-------------+------------------- diff --git a/contrib/pg_tde/expected/pg_tde_is_encrypted.out b/contrib/pg_tde/expected/pg_tde_is_encrypted.out index a6e13a57d9e13..eb3c68fbc7e74 100644 --- a/contrib/pg_tde/expected/pg_tde_is_encrypted.out +++ b/contrib/pg_tde/expected/pg_tde_is_encrypted.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/recreate_storage.out b/contrib/pg_tde/expected/recreate_storage.out index adfca5acb8d38..307f8c96f5c8d 100644 --- a/contrib/pg_tde/expected/recreate_storage.out +++ b/contrib/pg_tde/expected/recreate_storage.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/relocate.out b/contrib/pg_tde/expected/relocate.out index 740a69696e89b..256ad4e314980 100644 --- a/contrib/pg_tde/expected/relocate.out +++ b/contrib/pg_tde/expected/relocate.out @@ -1,6 +1,3 @@ --- Support pg_tde already being installed -SET client_min_messages = 'warning'; -DROP EXTENSION IF EXISTS pg_tde; CREATE SCHEMA other; CREATE EXTENSION pg_tde SCHEMA other; SELECT other.pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/expected/tablespace.out b/contrib/pg_tde/expected/tablespace.out index 4349c31ac8019..5d5e290207f99 100644 --- a/contrib/pg_tde/expected/tablespace.out +++ b/contrib/pg_tde/expected/tablespace.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/toast_decrypt.out b/contrib/pg_tde/expected/toast_decrypt.out index 7647a4e6795db..242c266cf4bd6 100644 --- a/contrib/pg_tde/expected/toast_decrypt.out +++ b/contrib/pg_tde/expected/toast_decrypt.out @@ -1,5 +1,5 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 33d9f9175444f..74493caefc7ac 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); diff --git a/contrib/pg_tde/sql/access_control.sql b/contrib/pg_tde/sql/access_control.sql index 78de2d3602d7d..69bc1eb35a6fe 100644 --- a/contrib/pg_tde/sql/access_control.sql +++ b/contrib/pg_tde/sql/access_control.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('local-file-provider', '/tmp/pg_tde_test_keyring.per'); diff --git a/contrib/pg_tde/sql/alter_index.sql b/contrib/pg_tde/sql/alter_index.sql index 23283386f465d..e00f1a407341b 100644 --- a/contrib/pg_tde/sql/alter_index.sql +++ b/contrib/pg_tde/sql/alter_index.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/cache_alloc.sql b/contrib/pg_tde/sql/cache_alloc.sql index 5098c26138681..4f6c9f05fa330 100644 --- a/contrib/pg_tde/sql/cache_alloc.sql +++ b/contrib/pg_tde/sql/cache_alloc.sql @@ -2,7 +2,7 @@ -- Just checking there are no mem debug WARNINGs during the cache population -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/change_access_method.sql b/contrib/pg_tde/sql/change_access_method.sql index cd4ff512c7e9d..e2a1faeef2394 100644 --- a/contrib/pg_tde/sql/change_access_method.sql +++ b/contrib/pg_tde/sql/change_access_method.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key', 'file-vault'); diff --git a/contrib/pg_tde/sql/create_database.sql b/contrib/pg_tde/sql/create_database.sql index 604293dd24a5a..a1a3aa6f1649d 100644 --- a/contrib/pg_tde/sql/create_database.sql +++ b/contrib/pg_tde/sql/create_database.sql @@ -1,7 +1,7 @@ \! rm -f '/tmp/template_provider_global.per' \! rm -f '/tmp/template_provider.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; CREATE DATABASE template_db; diff --git a/contrib/pg_tde/sql/default_principal_key.sql b/contrib/pg_tde/sql/default_principal_key.sql index 20acfd3eb3e99..8d2c60c63b85e 100644 --- a/contrib/pg_tde/sql/default_principal_key.sql +++ b/contrib/pg_tde/sql/default_principal_key.sql @@ -1,7 +1,7 @@ \! rm -f '/tmp/pg_tde_regression_default_key.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; -CREATE EXTENSION IF NOT EXISTS pg_buffercache; +CREATE EXTENSION pg_tde; +CREATE EXTENSION pg_buffercache; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per'); diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql index 262d456933555..abc4c574b5616 100644 --- a/contrib/pg_tde/sql/delete_principal_key.sql +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_global_key_provider('defalut-key','file-provider'); diff --git a/contrib/pg_tde/sql/insert_update_delete.sql b/contrib/pg_tde/sql/insert_update_delete.sql index 3ec1a8014d4e4..be2b3ff61f451 100644 --- a/contrib/pg_tde/sql/insert_update_delete.sql +++ b/contrib/pg_tde/sql/insert_update_delete.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/key_provider.sql b/contrib/pg_tde/sql/key_provider.sql index ad4467e787dd3..072176cccc836 100644 --- a/contrib/pg_tde/sql/key_provider.sql +++ b/contrib/pg_tde/sql/key_provider.sql @@ -3,7 +3,7 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' \! rm -f '/tmp/pg_tde_test_keyring2.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT * FROM pg_tde_key_info(); diff --git a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql index 5fb492c152146..36f1aa5f708b6 100644 --- a/contrib/pg_tde/sql/pg_tde_is_encrypted.sql +++ b/contrib/pg_tde/sql/pg_tde_is_encrypted.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/recreate_storage.sql b/contrib/pg_tde/sql/recreate_storage.sql index 60804f6e379db..26770e14e58e6 100644 --- a/contrib/pg_tde/sql/recreate_storage.sql +++ b/contrib/pg_tde/sql/recreate_storage.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/relocate.sql b/contrib/pg_tde/sql/relocate.sql index f3a79b279789e..1f06077208a0a 100644 --- a/contrib/pg_tde/sql/relocate.sql +++ b/contrib/pg_tde/sql/relocate.sql @@ -1,7 +1,3 @@ --- Support pg_tde already being installed -SET client_min_messages = 'warning'; -DROP EXTENSION IF EXISTS pg_tde; - CREATE SCHEMA other; CREATE EXTENSION pg_tde SCHEMA other; diff --git a/contrib/pg_tde/sql/tablespace.sql b/contrib/pg_tde/sql/tablespace.sql index 413a1159ba2de..920d51bf8cd6e 100644 --- a/contrib/pg_tde/sql/tablespace.sql +++ b/contrib/pg_tde/sql/tablespace.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/toast_decrypt.sql b/contrib/pg_tde/sql/toast_decrypt.sql index 34d8341c4ebd8..92ad6c104ae64 100644 --- a/contrib/pg_tde/sql/toast_decrypt.sql +++ b/contrib/pg_tde/sql/toast_decrypt.sql @@ -1,6 +1,6 @@ \! rm -f '/tmp/pg_tde_test_keyring.per' -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per'); SELECT pg_tde_create_key_using_database_key_provider('test-db-key','file-vault'); diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index 8b95e1cf27dce..d5ffde168426f 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE diff --git a/contrib/pg_tde/t/basic.pl b/contrib/pg_tde/t/basic.pl index 7d43a3bfcadac..54d19e43f179e 100644 --- a/contrib/pg_tde/t/basic.pl +++ b/contrib/pg_tde/t/basic.pl @@ -16,7 +16,7 @@ $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); # Only whitelisted C or security definer functions are granted to public by default PGTDE::psql( diff --git a/contrib/pg_tde/t/change_key_provider.pl b/contrib/pg_tde/t/change_key_provider.pl index 78ee711596290..1ffff0483efbe 100644 --- a/contrib/pg_tde/t/change_key_provider.pl +++ b/contrib/pg_tde/t/change_key_provider.pl @@ -18,7 +18,7 @@ $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per');" diff --git a/contrib/pg_tde/t/crash_recovery.pl b/contrib/pg_tde/t/crash_recovery.pl index 0c4a85c654683..65bae54751666 100644 --- a/contrib/pg_tde/t/crash_recovery.pl +++ b/contrib/pg_tde/t/crash_recovery.pl @@ -20,7 +20,7 @@ }); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per');" ); diff --git a/contrib/pg_tde/t/expected/basic.out b/contrib/pg_tde/t/expected/basic.out index 7070fb44af8a5..587d8a7a31bf9 100644 --- a/contrib/pg_tde/t/expected/basic.out +++ b/contrib/pg_tde/t/expected/basic.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_proc.oid::regprocedure FROM diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index cebcfa858ac2d..e444fa7463e37 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/crash_recovery.out b/contrib/pg_tde/t/expected/crash_recovery.out index 6b5958842a3cd..441c72b0ccd49 100644 --- a/contrib/pg_tde/t/expected/crash_recovery.out +++ b/contrib/pg_tde/t/expected/crash_recovery.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_global_key_provider_file('global_keyring', '/tmp/crash_recovery.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/t/expected/key_rotate_tablespace.out b/contrib/pg_tde/t/expected/key_rotate_tablespace.out index c850828f9f95d..0fa6c53b4d074 100644 --- a/contrib/pg_tde/t/expected/key_rotate_tablespace.out +++ b/contrib/pg_tde/t/expected/key_rotate_tablespace.out @@ -1,6 +1,6 @@ SET allow_in_place_tablespaces = true; CREATE TABLESPACE test_tblspace LOCATION ''; CREATE DATABASE tbc TABLESPACE = test_tblspace; -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tablespace.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/multiple_extensions.out b/contrib/pg_tde/t/expected/multiple_extensions.out index a27fc6c8113ff..e5903d1bd401d 100644 --- a/contrib/pg_tde/t/expected/multiple_extensions.out +++ b/contrib/pg_tde/t/expected/multiple_extensions.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; CREATE TABLE test_enc1 (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; INSERT INTO test_enc1 (k) VALUES (5), (6); SELECT * FROM test_enc1 ORDER BY id; diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index 9b44c223f2943..679adc1df335c 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -1,5 +1,5 @@ -- At primary -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/rotate_key.out b/contrib/pg_tde/t/expected/rotate_key.out index 4fc776991159c..65d542eeaf408 100644 --- a/contrib/pg_tde/t/expected/rotate_key.out +++ b/contrib/pg_tde/t/expected/rotate_key.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/rotate_key.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/tde_heap.out b/contrib/pg_tde/t/expected/tde_heap.out index e0083b15570b0..a5f7e3ee04feb 100644 --- a/contrib/pg_tde/t/expected/tde_heap.out +++ b/contrib/pg_tde/t/expected/tde_heap.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/unlogged_tables.out b/contrib/pg_tde/t/expected/unlogged_tables.out index 4c854576016e5..09532ce5ffeec 100644 --- a/contrib/pg_tde/t/expected/unlogged_tables.out +++ b/contrib/pg_tde/t/expected/unlogged_tables.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); pg_tde_add_database_key_provider_file --------------------------------------- diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index a1a22557980a2..183f20b9beda9 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -1,4 +1,4 @@ -CREATE EXTENSION IF NOT EXISTS pg_tde; +CREATE EXTENSION pg_tde; SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/wal_encrypt.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/t/key_rotate_tablespace.pl b/contrib/pg_tde/t/key_rotate_tablespace.pl index ff30aa3f33dcc..c1c85431cacb8 100644 --- a/contrib/pg_tde/t/key_rotate_tablespace.pl +++ b/contrib/pg_tde/t/key_rotate_tablespace.pl @@ -22,7 +22,7 @@ PGTDE::psql($node, 'postgres', 'CREATE DATABASE tbc TABLESPACE = test_tblspace;'); -PGTDE::psql($node, 'tbc', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'tbc', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'tbc', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/key_rotate_tablespace.per');" ); diff --git a/contrib/pg_tde/t/multiple_extensions.pl b/contrib/pg_tde/t/multiple_extensions.pl index 3f52d0ea6528c..1a16c2d5cc502 100644 --- a/contrib/pg_tde/t/multiple_extensions.pl +++ b/contrib/pg_tde/t/multiple_extensions.pl @@ -50,10 +50,8 @@ PGTDE::append_to_debug_file($stdout); # Create pg_tde extension -($cmdret, $stdout, $stderr) = $node->psql( - 'postgres', - 'CREATE EXTENSION IF NOT EXISTS pg_tde;', - extra_params => ['-a']); +($cmdret, $stdout, $stderr) = + $node->psql('postgres', 'CREATE EXTENSION pg_tde;', extra_params => ['-a']); ok($cmdret == 0, "CREATE PGTDE EXTENSION"); PGTDE::append_to_result_file($stdout); diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index fbcae2e77b9c2..c16f6c3591ba2 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -11,7 +11,7 @@ $node->append_conf('postgresql.conf', q{shared_preload_libraries = 'pg_tde'}); $node->start; -$node->safe_psql('postgres', q{CREATE EXTENSION IF NOT EXISTS pg_tde}); +$node->safe_psql('postgres', q{CREATE EXTENSION pg_tde}); $node->safe_psql('postgres', q{SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/pg_tde_change_key_provider-global')} ); diff --git a/contrib/pg_tde/t/pg_waldump_basic.pl b/contrib/pg_tde/t/pg_waldump_basic.pl index 5f86b7929ce32..57bc28cb38368 100644 --- a/contrib/pg_tde/t/pg_waldump_basic.pl +++ b/contrib/pg_tde/t/pg_waldump_basic.pl @@ -28,7 +28,7 @@ }); $node->start; -$node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_basic.per');" ); diff --git a/contrib/pg_tde/t/pg_waldump_fullpage.pl b/contrib/pg_tde/t/pg_waldump_fullpage.pl index b543b46bea456..5d5fe24c7e4a6 100644 --- a/contrib/pg_tde/t/pg_waldump_fullpage.pl +++ b/contrib/pg_tde/t/pg_waldump_fullpage.pl @@ -42,7 +42,7 @@ sub get_block_lsn }); $node->start; -$node->safe_psql('postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); $node->safe_psql('postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_fullpage.per');" ); diff --git a/contrib/pg_tde/t/replication.pl b/contrib/pg_tde/t/replication.pl index 0a53fead4d3cb..573ef3e8050cf 100644 --- a/contrib/pg_tde/t/replication.pl +++ b/contrib/pg_tde/t/replication.pl @@ -28,7 +28,7 @@ PGTDE::append_to_result_file("-- At primary"); -PGTDE::psql($primary, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($primary, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($primary, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/replication.per');" ); diff --git a/contrib/pg_tde/t/rotate_key.pl b/contrib/pg_tde/t/rotate_key.pl index b836e664d1370..8b65e5fb224dc 100644 --- a/contrib/pg_tde/t/rotate_key.pl +++ b/contrib/pg_tde/t/rotate_key.pl @@ -19,7 +19,7 @@ $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/rotate_key.per');" diff --git a/contrib/pg_tde/t/tde_heap.pl b/contrib/pg_tde/t/tde_heap.pl index f0e6c0376b965..a8fecc3d7df4f 100644 --- a/contrib/pg_tde/t/tde_heap.pl +++ b/contrib/pg_tde/t/tde_heap.pl @@ -16,7 +16,7 @@ $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/tde_heap.per');" diff --git a/contrib/pg_tde/t/unlogged_tables.pl b/contrib/pg_tde/t/unlogged_tables.pl index 742240d0f2169..365f89c8fa0c9 100644 --- a/contrib/pg_tde/t/unlogged_tables.pl +++ b/contrib/pg_tde/t/unlogged_tables.pl @@ -16,7 +16,7 @@ $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->start; -PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" ); diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index 33b3adfa7136e..7db5cd28dbf2a 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -19,7 +19,7 @@ #$node->append_conf('postgresql.conf', "pg_tde.wal_encrypt = 1"}); $node->start; -PGTDE::psql($node, 'postgres', "CREATE EXTENSION IF NOT EXISTS pg_tde;"); +PGTDE::psql($node, 'postgres', "CREATE EXTENSION pg_tde;"); PGTDE::psql($node, 'postgres', "SELECT pg_tde_add_global_key_provider_file('file-keyring-010', '/tmp/wal_encrypt.per');" From 28573139c0cb53b17b07171f61e49bcb00e89aee Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 13 Jun 2025 14:34:10 +0200 Subject: [PATCH 477/796] Remove unused includes in enc_tde.c These includes were used by functions which have now been removed. --- contrib/pg_tde/src/encryption/enc_tde.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index f5324741f0efa..04646256bb4cc 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -3,12 +3,8 @@ #include #include -#include "storage/bufmgr.h" - -#include "access/pg_tde_tdemap.h" #include "encryption/enc_tde.h" #include "encryption/enc_aes.h" -#include "pg_tde_defines.h" #ifdef FRONTEND #include "pg_tde_fe.h" From 56106393945a88368bf15a3f62625ead94175d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 16 Jun 2025 11:31:55 +0200 Subject: [PATCH 478/796] Update docs for change to set_key functions We removed the ability for the various set_key functions to actually create new keys and instead have a separate function for that as the API around "ensure_new_key" was confusing at best. --- .../documentation/docs/architecture/index.md | 17 +++-- .../pg_tde/documentation/docs/functions.md | 69 +++++++++---------- .../set-principal-key.md | 27 +++++--- .../docs/how-to/multi-tenant-setup.md | 35 +++++++--- 4 files changed, 86 insertions(+), 62 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index b643a393fac95..9abd070c4c9eb 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -276,15 +276,19 @@ pg_tde_REVOKE_database_key_management_FROM_role ### Creating and rotating keys -Principal keys can be created or rotated using the following functions: +Principal keys can be created using the following functions: ```sql -pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) -pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) -pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_create_key_using_(global/database)_key_provider('key-name', 'provider-name') ``` -`ensure_new_key` is a boolean parameter defaulting to false. If it is `true` the function might return an error instead of setting the key if it already exists on the provider. +Principal keys can be used or rotated using the following functions: + +```sql +pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name') +pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name') +pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name') +``` ### Default principal key @@ -296,7 +300,8 @@ With this feature, it is possible for the entire database server to easily use t You can manage a default key with the following functions: -* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name','true/false')` +* `pg_tde_create_key_using_global_key_provider('key-name','provider-name')` +* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name')` * `pg_tde_delete_default_key()` !!! note diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 0a6a7afde256e..563d359aa2cf4 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -230,85 +230,78 @@ These functions list the details of all key providers for the current database o ## Principal key management -Use these functions to create a new principal key for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. +Use these functions to create a new principal key at a given keyprover, and to use those keys for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. Princial keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. -### pg_tde_set_key_using_database_key_provider +### pg_tde_creates_key_using_database_key_provider -Creates or reuses a principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. +Creates a principal key at a database local key provider with the given name. For later use with pg_tde_set_key_using_database_key_provider(). -This function is typically used when working with per-database encryption through a local key provider. +```sql +SELECT pg_tde_create_key_using_database_key_provider( + 'key-name', + 'provider-name' +); +``` +### pg_tde_create_key_using_global_key_provider + +Creates a principal key at a global key provider with the given name. For later use with pg_tde_set_ series of functions. ```sql -SELECT pg_tde_set_key_using_database_key_provider( +SELECT pg_tde_create_key_using_global_key_provider( 'key-name', - 'provider-name', - 'false' -- or 'true' + 'provider-name' ); ``` -For the third parameter (`true`, `false`, or omitted): +### pg_tde_set_key_using_database_key_provider + +Sets the principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. -* `true`: Requires the key to be newly created. If a key with the same name already exists, the function fails. -* `false` (default if omitted): Reuses the existing key with that name, if present. If the key does not exist, a new key is created. +This function is typically used when working with per-database encryption through a local key provider. +```sql +SELECT pg_tde_set_key_using_database_key_provider( + 'key-name', + 'provider-name' +); +``` ### pg_tde_set_key_using_global_key_provider -Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. +Sets or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. ```sql SELECT pg_tde_set_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` - The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ### pg_tde_set_server_key_using_global_key_provider -Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. +Sets or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. ```sql SELECT pg_tde_set_server_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` -The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ### pg_tde_set_default_key_using_global_key_provider -Creates or rotates the default principal key for the server using the specified global key provider. +Sets or rotates the default principal key for the server using the specified global key provider. -The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. +The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` -The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ## Encryption status check ### pg_tde_is_encrypted diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 86bb9b1ada853..4c3cc3802d04e 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -4,13 +4,23 @@ You can configure a default principal key using a global key provider. This key ## Create a default principal key +To create a global principal key, run: + +```sql +SELECT pg_tde_create_key_using_global_key_provider( + 'key-name', + 'global_vault_provider' +); +``` + +## Configure a default principal key + To configure a global principal key, run: ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'global_vault_provider', - 'false' -- or 'true', or omit entirely + 'global_vault_provider' ); ``` @@ -18,13 +28,10 @@ SELECT pg_tde_set_default_key_using_global_key_provider( * `key-name` is the name under which the principal key is stored in the provider. * `global_vault_provider` is the name of the global key provider you previously configured. -* Third parameter (optional): - * `true` requires the key to be newly created. If the key already exists, the function fails. - * `false` or omitted (default), allows reuse of an existing key if it exists. If not, a new key is created under the specified name. ## How key generation works -If the specified key does **not** exist, a new encryption key is created under the given name. In this case, the key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. +The key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. !!! note This process sets the **default principal key** for the server. Any database without its own key configuration will use this key. @@ -34,10 +41,14 @@ If the specified key does **not** exist, a new encryption key is created under t This example is for testing purposes only. Replace the key name and provider name with your values: ```sql +SELECT pg_tde_create_key_using_global_key_provider( + 'test-db-master-key', + 'file-vault' +); + SELECT pg_tde_set_key_using_global_key_provider( 'test-db-master-key', - 'file-vault', - 'false' + 'file-vault' ); ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index b59c1e7fc585a..247a1878c254b 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -42,7 +42,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo !!! tip - You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: + You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: ```sh psql -d template1 -c 'CREATE EXTENSION pg_tde;' @@ -57,8 +57,8 @@ You must do these steps for every database where you have created the extension. === "With KMIP server" Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). - - For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. + + For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql SELECT pg_tde_add_database_key_provider_kmip('provider-name','kmip-addr', 5696, '/path_to/client_cert.pem', '/path_to/client_key.pem', '/path_to/server_certificate.pem'); @@ -85,9 +85,9 @@ You must do these steps for every database where you have created the extension. ```sql SELECT pg_tde_add_database_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); - ``` + ``` - where: + where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys @@ -113,25 +113,40 @@ You must do these steps for every database where you have created the extension. ```sql SELECT pg_tde_add_database_key_provider_file('file-keyring', '/tmp/pg_tde_test_local_keyring.per'); ``` - -2. Add a principal key +2. Create a key ```sql - SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key', 'provider-name','ensure_new_key'); + + SELECT pg_tde_create_key_using_database_key_provider('name-of-the-key', 'provider-name'); ``` where: * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. - * `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_create_key_using_database_key_provider('test-db-master-key', 'file-vault'); ``` !!! note The key is auto-generated. +3. Use the key as principal key + ```sql + + SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key', 'provider-name'); + ``` + + where: + + * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. + * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. + + :material-information: Warning: This example is for testing purposes only: + + ```sql + SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault'); + ``` From f631496d0b4064c9a8d38c40187842f326d64cbc Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 16 Jun 2025 17:59:40 +0200 Subject: [PATCH 479/796] PG-1663 Make sure indexes on paritioned tables are encrypted Since we only looked at the parent table and not on the whole tree when setting the status of the encrypted indexes we could easily accidentally create a plain text index on an encrypted table. This patch also makes sure to disallow adding indexes to an inheritance tree where the tables are a mix of encrypted and unecrypted tables. --- contrib/pg_tde/expected/partition_table.out | 46 +++++++++++++++++++++ contrib/pg_tde/sql/partition_table.sql | 29 +++++++++++++ contrib/pg_tde/src/pg_tde_event_capture.c | 21 +++++----- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index e58c181d5bca2..13553c5e0134a 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -161,4 +161,50 @@ SELECT pg_tde_is_encrypted('partition_child_tde_heap'); DROP TABLE partition_parent; RESET pg_tde.enforce_encryption; +-- Partitioned indexes should be encrypted +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; +CREATE INDEX ON partition_parent (a); +SELECT pg_tde_is_encrypted('partition_parent_a_idx'); -- Also check that the parent index is NULL + pg_tde_is_encrypted +--------------------- + +(1 row) + +SELECT pg_tde_is_encrypted('partition_child_a_idx'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partition_parent; +-- Partitioned indexes should be not encrypted with heap +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING heap; +CREATE INDEX ON partition_parent (a); +SELECT pg_tde_is_encrypted('partition_child_a_idx'); + pg_tde_is_encrypted +--------------------- + f +(1 row) + +DROP TABLE partition_parent; +-- We refuse to create an index when the inheritance heirarchy has mixed statuses +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING heap; +CREATE TABLE partition_child_tde_heap PARTITION OF partition_parent FOR VALUES FROM (10) TO (19) USING tde_heap; +CREATE INDEX ON partition_parent (a); +ERROR: Recursive CREATE INDEX on a mix of encrypted and unencrypted relations is not supported +DROP TABLE partition_parent; +-- Index should also be encrypted for new partitionins +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE INDEX ON partition_parent (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (10) TO (19) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child_a_idx'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partition_parent; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index 0885e55930c10..71ce2a1a7b28e 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -80,4 +80,33 @@ SELECT pg_tde_is_encrypted('partition_child_tde_heap'); DROP TABLE partition_parent; RESET pg_tde.enforce_encryption; +-- Partitioned indexes should be encrypted +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; +CREATE INDEX ON partition_parent (a); +SELECT pg_tde_is_encrypted('partition_parent_a_idx'); -- Also check that the parent index is NULL +SELECT pg_tde_is_encrypted('partition_child_a_idx'); +DROP TABLE partition_parent; + +-- Partitioned indexes should be not encrypted with heap +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING heap; +CREATE INDEX ON partition_parent (a); +SELECT pg_tde_is_encrypted('partition_child_a_idx'); +DROP TABLE partition_parent; + +-- We refuse to create an index when the inheritance heirarchy has mixed statuses +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE TABLE partition_child_heap PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING heap; +CREATE TABLE partition_child_tde_heap PARTITION OF partition_parent FOR VALUES FROM (10) TO (19) USING tde_heap; +CREATE INDEX ON partition_parent (a); +DROP TABLE partition_parent; + +-- Index should also be encrypted for new partitionins +CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); +CREATE INDEX ON partition_parent (a); +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (10) TO (19) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child_a_idx'); +DROP TABLE partition_parent; + DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 43ee36cbecfd9..900c8e80aa515 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -258,21 +258,22 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) if (IsA(parsetree, IndexStmt)) { IndexStmt *stmt = castNode(IndexStmt, parsetree); - Relation rel; TdeDdlEvent event = {.parsetree = parsetree}; + EncryptionMix encmix; + Oid relid = RangeVarGetRelid(stmt->relation, AccessShareLock, false); - rel = table_openrv(stmt->relation, AccessShareLock); + encmix = alter_table_encryption_mix(relid); - if (rel->rd_rel->relam == get_tde_table_am_oid()) - { + if (encmix == ENC_MIX_ENCRYPTED) event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; - checkPrincipalKeyConfigured(); - } - else + else if (encmix == ENC_MIX_PLAIN) event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; - - /* Hold on to lock until end of transaction */ - table_close(rel, NoLock); + else if (encmix == ENC_MIX_UNKNOWN) + event.encryptMode = TDE_ENCRYPT_MODE_RETAIN; + else + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Recursive CREATE INDEX on a mix of encrypted and unencrypted relations is not supported")); push_event_stack(&event); } From 93dcf725d2743a42a14f9c82c12a85d7fcf2f7df Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Wed, 18 Jun 2025 08:39:16 +0100 Subject: [PATCH 480/796] Update postgres and pg_tde version numbers --- ci_scripts/env.sh | 2 +- configure | 2 +- configure.ac | 2 +- contrib/pg_tde/Makefile | 2 +- contrib/pg_tde/expected/version.out | 6 +++--- contrib/pg_tde/meson.build | 2 +- contrib/pg_tde/{pg_tde--1.0-rc.sql => pg_tde--1.0.sql} | 0 contrib/pg_tde/pg_tde.control | 2 +- contrib/pg_tde/src/include/pg_tde.h | 2 +- contrib/pg_tde/t/expected/basic.out | 2 +- meson.build | 2 +- src/test/modules/test_misc/t/008_percona_server_version.pl | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) rename contrib/pg_tde/{pg_tde--1.0-rc.sql => pg_tde--1.0.sql} (100%) diff --git a/ci_scripts/env.sh b/ci_scripts/env.sh index a5bb68ac66560..ec1cff4f8f2a1 100644 --- a/ci_scripts/env.sh +++ b/ci_scripts/env.sh @@ -1,3 +1,3 @@ #!/bin/bash -export PERCONA_SERVER_VERSION=17.5.1 +export PERCONA_SERVER_VERSION=17.5.2 diff --git a/configure b/configure index d255f91601c3b..7960612b26b5c 100755 --- a/configure +++ b/configure @@ -2856,7 +2856,7 @@ cat >>confdefs.h <<_ACEOF _ACEOF -PG_PERCONAVERSION=1 +PG_PERCONAVERSION=2 cat >>confdefs.h <<_ACEOF #define PG_PERCONAVERSION "$PG_PERCONAVERSION" diff --git a/configure.ac b/configure.ac index 669e3bcffc10b..70fa3619d4bad 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AC_DEFINE_UNQUOTED(PG_MAJORVERSION, "$PG_MAJORVERSION", [PostgreSQL major versio AC_DEFINE_UNQUOTED(PG_MAJORVERSION_NUM, $PG_MAJORVERSION, [PostgreSQL major version number]) AC_DEFINE_UNQUOTED(PG_MINORVERSION_NUM, $PG_MINORVERSION, [PostgreSQL minor version number]) -[PG_PERCONAVERSION=1] +[PG_PERCONAVERSION=2] AC_DEFINE_UNQUOTED(PG_PERCONAVERSION, "$PG_PERCONAVERSION", [PostgreSQL Percona version as a string]) PGAC_ARG_REQ(with, extra-version, [STRING], [append STRING to version], diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index a8b7458b411f8..dd9f5541c6d19 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -1,7 +1,7 @@ PGFILEDESC = "pg_tde access method" MODULE_big = pg_tde EXTENSION = pg_tde -DATA = pg_tde--1.0-rc.sql +DATA = pg_tde--1.0.sql # Since meson supports skipping test suites this is a make only feature ifndef TDE_MODE diff --git a/contrib/pg_tde/expected/version.out b/contrib/pg_tde/expected/version.out index fddbafca98f12..44ebea8d04d32 100644 --- a/contrib/pg_tde/expected/version.out +++ b/contrib/pg_tde/expected/version.out @@ -1,8 +1,8 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_version(); - pg_tde_version ------------------ - pg_tde 1.0.0-rc + pg_tde_version +---------------- + pg_tde 1.0.0 (1 row) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 21f5b13c445a8..7429c08420f93 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -75,7 +75,7 @@ endif install_data( 'pg_tde.control', - 'pg_tde--1.0-rc.sql', + 'pg_tde--1.0.sql', kwargs: contrib_data_args, ) diff --git a/contrib/pg_tde/pg_tde--1.0-rc.sql b/contrib/pg_tde/pg_tde--1.0.sql similarity index 100% rename from contrib/pg_tde/pg_tde--1.0-rc.sql rename to contrib/pg_tde/pg_tde--1.0.sql diff --git a/contrib/pg_tde/pg_tde.control b/contrib/pg_tde/pg_tde.control index 73d135807fbe4..9ea82992d7490 100644 --- a/contrib/pg_tde/pg_tde.control +++ b/contrib/pg_tde/pg_tde.control @@ -1,4 +1,4 @@ comment = 'pg_tde access method' -default_version = '1.0-rc' +default_version = '1.0' module_pathname = '$libdir/pg_tde' relocatable = false diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index 652dc8eea18c6..4b6bb94d6d8f4 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -2,7 +2,7 @@ #define PG_TDE_H #define PG_TDE_NAME "pg_tde" -#define PG_TDE_VERSION "1.0.0-rc" +#define PG_TDE_VERSION "1.0.0" #define PG_TDE_VERSION_STRING PG_TDE_NAME " " PG_TDE_VERSION #define PG_TDE_DATA_DIR "pg_tde" diff --git a/contrib/pg_tde/t/expected/basic.out b/contrib/pg_tde/t/expected/basic.out index 587d8a7a31bf9..fdf976fb314c6 100644 --- a/contrib/pg_tde/t/expected/basic.out +++ b/contrib/pg_tde/t/expected/basic.out @@ -20,7 +20,7 @@ CREATE EXTENSION pg_tde; SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; extname | extversion ---------+------------ - pg_tde | 1.0-rc + pg_tde | 1.0 (1 row) CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; diff --git a/meson.build b/meson.build index 6fbc5163c0171..c054200660486 100644 --- a/meson.build +++ b/meson.build @@ -134,7 +134,7 @@ endif pg_version_major = pg_version_arr[0].to_int() pg_version_minor = pg_version_arr[1].to_int() pg_version_num = (pg_version_major * 10000) + pg_version_minor -pg_percona_ver = '1' +pg_percona_ver = '2' pg_url = 'https://www.postgresql.org/' diff --git a/src/test/modules/test_misc/t/008_percona_server_version.pl b/src/test/modules/test_misc/t/008_percona_server_version.pl index c7723525ebf22..347e173b523ed 100644 --- a/src/test/modules/test_misc/t/008_percona_server_version.pl +++ b/src/test/modules/test_misc/t/008_percona_server_version.pl @@ -17,7 +17,7 @@ # To make this testcase work, PERCONA_SERVER_VERSION variable should be available in environment. # If you are using ci_scripts it is already declated in ci_scripts/env.sh # If you are using command line make for regression then export like: -# export PERCONA_SERVER_VERSION=17.4.1 +# export PERCONA_SERVER_VERSION=17.5.2 if (!defined($ENV{PERCONA_SERVER_VERSION})) { From d48fdea0144ebfd2f74259c766642e6ef97e0038 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 18 Jun 2025 14:29:49 +0200 Subject: [PATCH 481/796] PG-1662 Handle changing access method of partitioned table correctly Since partitioned tables do not have any sotrage and only control the default access method of their children we should not try to change the encryption status of anything when changing the AM of a partitioned table. --- contrib/pg_tde/expected/partition_table.out | 24 +++++++++++++++++++++ contrib/pg_tde/sql/partition_table.sql | 9 ++++++++ contrib/pg_tde/src/pg_tde_event_capture.c | 13 ++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/expected/partition_table.out b/contrib/pg_tde/expected/partition_table.out index 13553c5e0134a..55835d3d4f0cd 100644 --- a/contrib/pg_tde/expected/partition_table.out +++ b/contrib/pg_tde/expected/partition_table.out @@ -161,6 +161,30 @@ SELECT pg_tde_is_encrypted('partition_child_tde_heap'); DROP TABLE partition_parent; RESET pg_tde.enforce_encryption; +-- Does not change encryption of child tables when rewriting and changing AM +CREATE TABLE partition_parent (a int, b int) PARTITION BY RANGE (a) USING tde_heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +ALTER TABLE partition_parent SET ACCESS METHOD heap, ALTER b TYPE bigint; +SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('partition_parent', 'partition_child') ORDER BY relname; + relname | amname +------------------+---------- + partition_child | tde_heap + partition_parent | heap +(2 rows) + +SELECT pg_tde_is_encrypted('partition_child'); + pg_tde_is_encrypted +--------------------- + t +(1 row) + +DROP TABLE partition_parent; -- Partitioned indexes should be encrypted CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; diff --git a/contrib/pg_tde/sql/partition_table.sql b/contrib/pg_tde/sql/partition_table.sql index 71ce2a1a7b28e..45e125dd0e8d9 100644 --- a/contrib/pg_tde/sql/partition_table.sql +++ b/contrib/pg_tde/sql/partition_table.sql @@ -80,6 +80,15 @@ SELECT pg_tde_is_encrypted('partition_child_tde_heap'); DROP TABLE partition_parent; RESET pg_tde.enforce_encryption; +-- Does not change encryption of child tables when rewriting and changing AM +CREATE TABLE partition_parent (a int, b int) PARTITION BY RANGE (a) USING tde_heap; +CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; +SELECT pg_tde_is_encrypted('partition_child'); +ALTER TABLE partition_parent SET ACCESS METHOD heap, ALTER b TYPE bigint; +SELECT relname, amname FROM pg_class JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname IN ('partition_parent', 'partition_child') ORDER BY relname; +SELECT pg_tde_is_encrypted('partition_child'); +DROP TABLE partition_parent; + -- Partitioned indexes should be encrypted CREATE TABLE partition_parent (a int) PARTITION BY RANGE (a); CREATE TABLE partition_child PARTITION OF partition_parent FOR VALUES FROM (0) TO (9) USING tde_heap; diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 900c8e80aa515..58bc91d4d8f3f 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -357,6 +357,7 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) ListCell *lcmd; TdeDdlEvent event = {.parsetree = parsetree}; EncryptionMix encmix; + Relation rel; foreach(lcmd, stmt->cmds) { @@ -380,17 +381,25 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) errmsg("Recursive ALTER TABLE on a mix of encrypted and unencrypted relations is not supported")); } + rel = relation_open(relid, NoLock); + /* * With a SET ACCESS METHOD clause, use that as the basis for * decisions. But if it's not present, look up encryption status * of the table. + * + * Since partitioned tables lack storage we do not need to set the + * encryption mode. */ - if (setAccessMethod) + if (setAccessMethod && RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) { event.rebuildSequencesFor = relid; if (shouldEncryptTable(setAccessMethod->name)) + { event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); + } else event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } @@ -405,6 +414,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } + relation_close(rel, NoLock); + push_event_stack(&event); checkEncryptionStatus(); } From 8b1f1cf398ebe1d3e7b640d96e8bd6f6ad517790 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 18 Jun 2025 17:56:33 +0200 Subject: [PATCH 482/796] Try to use poll_start instead of kill9_until_dead in TAP tests The poll_start function handles both slow kill -9 and slow startup while kill9_until_dead only really handles one of them. --- contrib/pg_tde/t/crash_recovery.pl | 16 ++++++++-------- contrib/pg_tde/t/pgtde.pm | 28 +++++++++++++++++++--------- contrib/pg_tde/t/replication.pl | 4 ++-- contrib/pg_tde/t/unlogged_tables.pl | 4 ++-- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/contrib/pg_tde/t/crash_recovery.pl b/contrib/pg_tde/t/crash_recovery.pl index 65bae54751666..4fa2ea2d3ef3f 100644 --- a/contrib/pg_tde/t/crash_recovery.pl +++ b/contrib/pg_tde/t/crash_recovery.pl @@ -51,10 +51,10 @@ PGTDE::psql($node, 'postgres', "ALTER SYSTEM SET pg_tde.wal_encrypt = 'on';"); PGTDE::append_to_result_file("-- kill -9"); -PGTDE::kill9_until_dead($node); +$node->kill9; PGTDE::append_to_result_file("-- server start"); -$node->start; +PGTDE::poll_start($node); PGTDE::append_to_result_file("-- rotate wal key"); PGTDE::psql($node, 'postgres', @@ -71,12 +71,12 @@ ); PGTDE::psql($node, 'postgres', "INSERT INTO test_enc (x) VALUES (3), (4);"); PGTDE::append_to_result_file("-- kill -9"); -PGTDE::kill9_until_dead($node); +$node->kill9; PGTDE::append_to_result_file("-- server start"); PGTDE::append_to_result_file( "-- check that pg_tde_save_principal_key_redo hasn't destroyed a WAL key created during the server start" ); -$node->start; +PGTDE::poll_start($node); PGTDE::append_to_result_file("-- rotate wal key"); PGTDE::psql($node, 'postgres', @@ -93,24 +93,24 @@ ); PGTDE::psql($node, 'postgres', "INSERT INTO test_enc (x) VALUES (5), (6);"); PGTDE::append_to_result_file("-- kill -9"); -PGTDE::kill9_until_dead($node); +$node->kill9; PGTDE::append_to_result_file("-- server start"); PGTDE::append_to_result_file( "-- check that the key rotation hasn't destroyed a WAL key created during the server start" ); -$node->start; +PGTDE::poll_start($node); PGTDE::psql($node, 'postgres', "TABLE test_enc;"); PGTDE::psql($node, 'postgres', "CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap;"); PGTDE::append_to_result_file("-- kill -9"); -PGTDE::kill9_until_dead($node); +$node->kill9; PGTDE::append_to_result_file("-- server start"); PGTDE::append_to_result_file( "-- check redo of the smgr internal key creation when the key is on disk" ); -$node->start; +PGTDE::poll_start($node); $node->stop; diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 3b5bb57f6c777..eb5c02b24ec17 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -37,22 +37,32 @@ sub psql } } -sub kill9_until_dead +# Copied from src/test/recovery/t/017_shm.pl +sub poll_start { my ($node) = @_; - return unless defined $node->{_pid}; # Cluster already stopped + my $max_attempts = 10 * $PostgreSQL::Test::Utils::timeout_default; + my $attempts = 0; - my $pid = $node->{_pid}; - $node->kill9; - - # Wait for process to actually die. - while (kill(0, $pid) != 0) + while ($attempts < $max_attempts) { - sleep(0.1); + $node->start(fail_ok => 1) && return 1; + + # Wait 0.1 second before retrying. + usleep(100_000); + + # Clean up in case the start attempt just timed out or some such. + $node->stop('fast', fail_ok => 1); + + $attempts++; } -} + # Try one last time without fail_ok, which will BAIL_OUT unless it + # succeeds. + $node->start && return 1; + return 0; +} sub append_to_result_file { diff --git a/contrib/pg_tde/t/replication.pl b/contrib/pg_tde/t/replication.pl index 573ef3e8050cf..14c3af1c2f2f2 100644 --- a/contrib/pg_tde/t/replication.pl +++ b/contrib/pg_tde/t/replication.pl @@ -82,10 +82,10 @@ PGTDE::psql($primary, 'postgres', "ALTER SYSTEM SET pg_tde.wal_encrypt = 'on';"); -PGTDE::kill9_until_dead($primary); +$primary->kill9; PGTDE::append_to_result_file("-- primary start"); -$primary->start; +PGTDE::poll_start($primary); $primary->wait_for_catchup('replica'); PGTDE::psql($replica, 'postgres', "SELECT * FROM test_enc2 ORDER BY x;"); diff --git a/contrib/pg_tde/t/unlogged_tables.pl b/contrib/pg_tde/t/unlogged_tables.pl index 365f89c8fa0c9..e348ebaef5466 100644 --- a/contrib/pg_tde/t/unlogged_tables.pl +++ b/contrib/pg_tde/t/unlogged_tables.pl @@ -35,10 +35,10 @@ PGTDE::psql($node, 'postgres', "CHECKPOINT;"); PGTDE::append_to_result_file("-- kill -9"); -PGTDE::kill9_until_dead($node); +$node->kill9; PGTDE::append_to_result_file("-- server start"); -$node->start; +PGTDE::poll_start($node); PGTDE::psql($node, 'postgres', "TABLE t;"); From 98c310907241f01d3fa23574b8eaa919749b3e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 23 Jun 2025 11:07:28 +0200 Subject: [PATCH 483/796] Remove extra word in error message for existing key This "to" had no business being in this message. --- contrib/pg_tde/expected/key_provider.out | 4 ++-- contrib/pg_tde/src/catalog/tde_principal_key.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 8110100372452..e2a018c606fc0 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -351,9 +351,9 @@ SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provid (1 row) SELECT pg_tde_create_key_using_database_key_provider('existing-key','file-provider'); -ERROR: cannot to create key "existing-key" because it already exists +ERROR: cannot create key "existing-key" because it already exists SELECT pg_tde_create_key_using_global_key_provider('existing-key','file-keyring'); -ERROR: cannot to create key "existing-key" because it already exists +ERROR: cannot create key "existing-key" because it already exists -- Setting principal key fails if key does not exist SELECT pg_tde_set_default_key_using_global_key_provider('not-existing', 'file-keyring'); ERROR: key "not-existing" does not exist diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 3944d123825db..6d0df4c641ea8 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -519,7 +519,7 @@ pg_tde_create_principal_key_internal(Oid providerOid, if (key_info != NULL) ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot to create key \"%s\" because it already exists", key_name)); + errmsg("cannot create key \"%s\" because it already exists", key_name)); key_info = KeyringGenerateNewKeyAndStore(provider, key_name, PRINCIPAL_KEY_LEN); From 65ac5d6c01d6bf8a24b470a233c3b8a7989368e7 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 6 Jun 2025 20:27:49 +0200 Subject: [PATCH 484/796] PG-1486 Add ARM64 builds in CI Add ARM64 based builds to build and test CI workflow triggered on main branch. --- .../{psp-reusable.yml => build-and-test.yml} | 10 ++--- .github/workflows/matirx.yml | 41 +++++++++++++++++++ .github/workflows/pgindent.yml | 7 +--- .github/workflows/psp-matrix.yml | 24 ----------- 4 files changed, 47 insertions(+), 35 deletions(-) rename .github/workflows/{psp-reusable.yml => build-and-test.yml} (96%) create mode 100644 .github/workflows/matirx.yml delete mode 100644 .github/workflows/psp-matrix.yml diff --git a/.github/workflows/psp-reusable.yml b/.github/workflows/build-and-test.yml similarity index 96% rename from .github/workflows/psp-reusable.yml rename to .github/workflows/build-and-test.yml index c363deb437221..41af1a40686ba 100644 --- a/.github/workflows/psp-reusable.yml +++ b/.github/workflows/build-and-test.yml @@ -1,4 +1,4 @@ -name: PSP-Reusable +name: Reusable build and test on: workflow_call: inputs: @@ -21,7 +21,7 @@ env: jobs: build: - name: Build PSP + name: Build runs-on: ${{ inputs.os }} steps: - name: Clone repository @@ -50,7 +50,7 @@ jobs: retention-days: 1 test: - name: Test PSP + name: Test runs-on: ${{ inputs.os }} needs: build @@ -93,8 +93,8 @@ jobs: src/src/test/*/tmp_check retention-days: 3 - test_tde: - name: Test PSP with TDE + test-default-tde: + name: Test with TDE as default access method runs-on: ${{ inputs.os }} if: inputs.build_script == 'make' needs: build diff --git a/.github/workflows/matirx.yml b/.github/workflows/matirx.yml new file mode 100644 index 0000000000000..86ad984050bd1 --- /dev/null +++ b/.github/workflows/matirx.yml @@ -0,0 +1,41 @@ +name: Build and Test +on: + pull_request: + push: + branches: + - TDE_REL_17_STABLE + workflow_dispatch: + +jobs: + main: + name: Main matrix + strategy: + matrix: + os: [ubuntu-22.04] + compiler: [gcc, clang] + build_type: [debugoptimized] + build_script: [make, meson] + uses: ./.github/workflows/build-and-test.yml + with: + os: ${{ matrix.os }} + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + build_script: ${{ matrix.build_script }} + secrets: inherit + + arm: + name: ARM matrix + if: github.event_name != 'pull_request' + strategy: + matrix: + os: [ubuntu-22.04-arm] + compiler: [gcc, clang] + build_type: [debugoptimized] + build_script: [make, meson] + uses: ./.github/workflows/build-and-test.yml + with: + os: ${{ matrix.os }} + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + build_script: ${{ matrix.build_script }} + secrets: inherit diff --git a/.github/workflows/pgindent.yml b/.github/workflows/pgindent.yml index c65ee645a8ba2..c1a99c63082e7 100644 --- a/.github/workflows/pgindent.yml +++ b/.github/workflows/pgindent.yml @@ -1,12 +1,8 @@ -name: PgIndent +name: Format on: pull_request: workflow_dispatch: -defaults: - run: - working-directory: ./src - jobs: check: name: Check @@ -15,7 +11,6 @@ jobs: - name: Clone repository uses: actions/checkout@v4 with: - path: 'src' submodules: recursive - name: Install dependencies diff --git a/.github/workflows/psp-matrix.yml b/.github/workflows/psp-matrix.yml deleted file mode 100644 index 635ab9ed78454..0000000000000 --- a/.github/workflows/psp-matrix.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: PSP -on: - pull_request: - push: - branches: - - TDE_REL_17_STABLE - workflow_dispatch: - -jobs: - build: - name: PSP - strategy: - matrix: - os: ['ubuntu-22.04'] - compiler: [gcc, clang] - build_type: [debugoptimized] - build_script: [make, meson] - uses: ./.github/workflows/psp-reusable.yml - with: - os: ${{ matrix.os }} - compiler: ${{ matrix.compiler }} - build_type: ${{ matrix.build_type }} - build_script: ${{ matrix.build_script }} - secrets: inherit From 600172dd3c502700131c6dd7e110bf7d508e8108 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 16 Jun 2025 20:01:34 +0200 Subject: [PATCH 485/796] Add Slack notifications on failed CI --- .github/workflows/matirx.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/matirx.yml b/.github/workflows/matirx.yml index 86ad984050bd1..39248232c4a4d 100644 --- a/.github/workflows/matirx.yml +++ b/.github/workflows/matirx.yml @@ -39,3 +39,24 @@ jobs: build_type: ${{ matrix.build_type }} build_script: ${{ matrix.build_script }} secrets: inherit + + slack-notification: + if: ${{ failure() }} && github.event_name == 'push' + needs: [ main, arm ] + name: Slack Notification + runs-on: ubuntu-24.04 + steps: + - name: Notify + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + blocks: + - type: "section" + text: + type: "mrkdwn" + text: "Workflow *${{ github.workflow }}* failed on branch *${{ github.head_ref }}*\n + Commit: <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>\n + \n + <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>" From 402b9be01e91c586cb4605444f27e2c2c89d6862 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 6 Jun 2025 20:51:49 +0200 Subject: [PATCH 486/796] Use ubuntu 24.04 in CI Ubuntu 24.04 image has Python 3.12 by default. PyKMIP server and CodeCheker tool don't support this version, so we have to downgrade Python version to 3.11. --- .github/workflows/build-and-test.yml | 19 ++++++++++++++++++- .github/workflows/codechecker.yml | 8 +++++++- .github/workflows/coverage.yml | 8 +++++++- .github/workflows/matirx.yml | 4 ++-- ci_scripts/ubuntu-deps.sh | 8 +------- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 41af1a40686ba..0d64496bb05e8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,6 +31,12 @@ jobs: submodules: recursive ref: ${{ github.ref }} + # KMIP server don't support Python 3.12 for now: https://github.com/OpenKMIP/PyKMIP/pull/707 + - name: Downgrade python to 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies run: src/ci_scripts/ubuntu-deps.sh @@ -64,9 +70,14 @@ jobs: - name: Extract artifact file run: tar -xzf artifacts.tar + - name: Downgrade python to 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies run: src/ci_scripts/ubuntu-deps.sh - + - name: Setup kmip and vault run: src/ci_scripts/setup-keyring-servers.sh @@ -109,6 +120,12 @@ jobs: - name: Extract artifact file run: tar -xzf artifacts.tar + # KMIP server don't support Python 3.12 for now: https://github.com/OpenKMIP/PyKMIP/pull/707 + - name: Downgrade python to 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies run: src/ci_scripts/ubuntu-deps.sh diff --git a/.github/workflows/codechecker.yml b/.github/workflows/codechecker.yml index 62d7e62cc5abf..d8f6866e9d661 100644 --- a/.github/workflows/codechecker.yml +++ b/.github/workflows/codechecker.yml @@ -11,13 +11,19 @@ env: jobs: run: name: Run - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Clone repository uses: actions/checkout@v4 with: submodules: recursive + # CodeChecker doesn't support python 3.12 for now: https://github.com/Ericsson/codechecker/issues/4350 + - name: Downgrade python to 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies run: ci_scripts/ubuntu-deps.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 28e2369bf12e4..3dd4891923dcd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,13 +11,19 @@ env: jobs: collect: name: Collect and upload - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Clone repository uses: actions/checkout@v4 with: submodules: recursive + # KMIP server doesn't support Python 3.12 for now: https://github.com/OpenKMIP/PyKMIP/pull/707 + - name: Downgrade python to 3.11 + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies run: ci_scripts/ubuntu-deps.sh diff --git a/.github/workflows/matirx.yml b/.github/workflows/matirx.yml index 39248232c4a4d..45e4bc6134243 100644 --- a/.github/workflows/matirx.yml +++ b/.github/workflows/matirx.yml @@ -11,7 +11,7 @@ jobs: name: Main matrix strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] compiler: [gcc, clang] build_type: [debugoptimized] build_script: [make, meson] @@ -28,7 +28,7 @@ jobs: if: github.event_name != 'pull_request' strategy: matrix: - os: [ubuntu-22.04-arm] + os: [ubuntu-24.04-arm] compiler: [gcc, clang] build_type: [debugoptimized] build_script: [make, meson] diff --git a/ci_scripts/ubuntu-deps.sh b/ci_scripts/ubuntu-deps.sh index d998289a55163..19696459d6c27 100755 --- a/ci_scripts/ubuntu-deps.sh +++ b/ci_scripts/ubuntu-deps.sh @@ -29,11 +29,6 @@ DEPS=( mawk perl pkgconf - python3-dev - python3 - python3-pip - python3-setuptools - python3-wheel systemtap-sdt-dev tcl-dev uuid-dev @@ -45,7 +40,6 @@ DEPS=( # Test libipc-run-perl # Test pg_tde - python3-pykmip libhttp-server-simple-perl lcov # Run pgperltidy @@ -56,7 +50,7 @@ sudo apt-get update sudo apt-get install -y ${DEPS[@]} sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -pip3 install meson +pip3 install meson pykmip cryptography setuptools wheel # Vault wget -O - https://apt.releases.hashicorp.com/gpg | sudo tee /etc/apt/keyrings/hashicorp-archive-keyring.asc From ef03f7b9fe528a4db10e339d3adb39004aef199c Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 25 Jun 2025 14:46:22 +0300 Subject: [PATCH 487/796] Create enforcement.md - Encryption Enforcement topic (#403) Wrote a general topic regarding Encrpytion Enforcement from pg_tde perspective. --- .../documentation/docs/how-to/enforcement.md | 65 +++++++++++++++++++ .../pg_tde/documentation/docs/variables.md | 26 ++++---- contrib/pg_tde/documentation/mkdocs.yml | 1 + 3 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 contrib/pg_tde/documentation/docs/how-to/enforcement.md diff --git a/contrib/pg_tde/documentation/docs/how-to/enforcement.md b/contrib/pg_tde/documentation/docs/how-to/enforcement.md new file mode 100644 index 0000000000000..7796b7ef809b4 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/how-to/enforcement.md @@ -0,0 +1,65 @@ +# Encryption Enforcement + +For `pg_tde`, encryption enforcement ensures that only encrypted storage is allowed for specific operations, tables, or the entire database. It prevents the accidental creation of unencrypted tables or indexes in environments where encryption is required for compliance, security, or policy enforcement. + +## What does enforcement do? + +When enabled, encryption enforcement: + +* Prevents creation of unencrypted tables or indexes +* Enforces consistent encryption usage across tenants, databases, or users +* Can be scoped globally, per database, or per role + +## Enforce encryption usage + +Use the following techniques to enforce the secure use of `pg_tde`. + +### 1. Enforce encryption across the server + +To enforce encryption cluster-wide, set the [`pg_tde.enforce_encryption`](../variables.md/#pg_tdeenforce_encryption) variable in `postgresql.conf`: + +```ini +pg_tde.enforce_encryption = on +``` + +!!! note + **Only** superusers can set or change this variable. + +This ensures that no user, including superusers, can create unencrypted tables. Superusers can however explicitly [override the variable in their session](#override-enforcement-for-trusted-sessions). + +### 2. Enforce encryption for a specific database + +To apply encryption enforcement only within a specific database, run: + +```sql +ALTER DATABASE example_db SET pg_tde.enforce_encryption = on; +``` + +This ensures encryption is enforced **only** when connected to that database. + +### 3. Enforce encryption for a specific user + +You can also enforce encryption on a per-user basis, run: + +```sql +ALTER USER example_user SET pg_tde.enforce_encryption = on; +``` + +This ensures that the user `example_user` cannot create unencrypted tables, regardless of which database they connect to. + +### Override enforcement for trusted sessions + +Superusers can override the variable at the session level: + +```sql +SET pg_tde.enforce_encryption = off; +``` + +This allows temporary creation of unencrypted tables in special cases, such as: + +* Loading trusted, public reference datasets +* Benchmarking and test environments +* Migration staging before re-encryption + +!!! note + While superusers can disable enforcement in their session, they must do so explicitly. Enforcement defaults remain active to protect from accidental misconfiguration. diff --git a/contrib/pg_tde/documentation/docs/variables.md b/contrib/pg_tde/documentation/docs/variables.md index 9947eacc385fe..46d27730857fa 100644 --- a/contrib/pg_tde/documentation/docs/variables.md +++ b/contrib/pg_tde/documentation/docs/variables.md @@ -32,14 +32,14 @@ Similarly, `ALTER TABLE SET ACCESS METHOD` is only allowed, if the access me Other DDL operations are still allowed. For example other `ALTER` commands are allowed on unencrypted tables, as long as the access method isn't changed. -You can set this variable at the following levels: +You can set this variable at the following levels: -* global - for the entire PostgreSQL cluster. -* database - for specific databases. -* user - for specific users. -* session - for the current session. +* global - for the entire PostgreSQL cluster +* database - for specific databases +* user - for specific users +* session - for the current session -Setting or changing the value requires superuser permissions. +Setting or changing the value requires superuser permissions. For examples, see the [Encryption Enforcement](how-to/enforcement.md) topic. ## pg_tde.inherit_global_providers @@ -52,12 +52,12 @@ If disabled, functions that change the key providers can only work with database In this case, the default principal key, if set, is also disabled. -You can set this variable at the following levels: +You can set this variable at the following levels: -* global - for the entire PostgreSQL cluster. -* database - for specific databases. -* user - for specific users. -* session - for the current session. +* global - for the entire PostgreSQL cluster +* database - for specific databases +* user - for specific users +* session - for the current session - -Setting this variable doesn't affect existing uses of global keys. It only prevents the creation of new principal keys using global providers. +!!! note + Setting this variable doesn't affect existing uses of global keys. It only prevents the creation of new principal keys using global providers. diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 9d542f2156a1c..72ae254c60a19 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -198,6 +198,7 @@ nav: - "pg_checksums": command-line-tools/pg-tde-checksums.md - "Uninstall pg_tde": how-to/uninstall.md - "Configure Multi-tenancy": how-to/multi-tenant-setup.md + - "Encryption Enforcement": how-to/enforcement.md - "Decrypt an Encrypted Table": how-to/decrypt.md - faq.md - "Release Notes": From 2a1f301e627cc25008b220e01397707835777c96 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 25 Jun 2025 16:27:11 +0300 Subject: [PATCH 488/796] Created replication.md quick walkthrough for pg_tde (#319) Created new replication topic which outlines how to set up PostgreSQL streaming replication when the `pg_tde` extension, specifically the `tde_heap` access method, is enabled on the primary server. --- .../pg_tde/documentation/docs/replication.md | 105 +++++++++++++++++- contrib/pg_tde/documentation/mkdocs.yml | 1 + 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/replication.md b/contrib/pg_tde/documentation/docs/replication.md index b745107a2f222..88af22ba04dde 100644 --- a/contrib/pg_tde/documentation/docs/replication.md +++ b/contrib/pg_tde/documentation/docs/replication.md @@ -1,4 +1,103 @@ -# Replication +# Streaming Replication with tde_heap - ## Are my backups safe? Can I restore from them? @@ -162,3 +167,7 @@ To restore from an encrypted backup, you must have the same principal encryption ## I'm using OpenSSL in FIPS mode and need to use pg_tde. Does pg_tde comply with FIPS requirements? Can I use my own FIPS-mode OpenSSL library with pg_tde? Yes. `pg_tde` works with the FIPS-compliant version of OpenSSL, whether it is provided by your operating system or if you use your own OpenSSL libraries. If you use your own libraries, make sure they are FIPS certified. + +## Is post-quantum encryption supported? + +No. Post-quantum encryption is not currently supported. diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index c61692e530e25..68cfa9c65e864 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -1,7 +1,7 @@ # Configure WAL Encryption (tech preview) !!! warning - The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. + The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. Before enabling WAL encryption, follow the steps below to create a principal key and configure it for WAL: From 5a2c081a959a5a763ceb02b2e2acc395078266e3 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 26 Jun 2025 16:32:49 +0200 Subject: [PATCH 499/796] PG-1257 Add key deletion funcs to documentation Add principal key deletion functions to documentation. Fix couple uncertainties on architecture docs page. --- .../documentation/docs/architecture/index.md | 4 ++-- contrib/pg_tde/documentation/docs/functions.md | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 9abd070c4c9eb..54542ee94e0b6 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -305,12 +305,12 @@ You can manage a default key with the following functions: * `pg_tde_delete_default_key()` !!! note - `pg_tde_delete_default_key()` is only possible if there's no table currently using the default principal key. + `pg_tde_delete_default_key()` is only possible if there's no database currently using the default principal key. Changing the default principal key will rotate the encryption of internal keys for all databases using the current default principal key. #### Delete a key -The `pg_tde_delete_key()` function removes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a global default principal key, internal keys will be encrypted with the default key. +The `pg_tde_delete_key()` function removes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. !!! note WAL keys **cannot** be deleted, as server keys are managed separately. diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 563d359aa2cf4..fcc7d03c51ef4 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -302,6 +302,22 @@ SELECT pg_tde_set_default_key_using_global_key_provider( ); ``` +### pg_tde_delete_key + +Deletes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. + +```sql +SELECT pg_tde_delete_key(); +``` + +### pg_tde_delete_default_key + +Deletes default principal key. It's possible only if no database uses default principal key. + +```sql +SELECT pg_tde_delete_default_key(); +``` + ## Encryption status check ### pg_tde_is_encrypted From 38961e0b98c431659759a75f3d65fe625460af03 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 26 Jun 2025 16:49:56 +0200 Subject: [PATCH 500/796] Skip CI run for documentation changes There is no need to run all our CI workflows if only documentation were changed. --- .github/workflows/codechecker.yml | 2 ++ .github/workflows/coverage.yml | 4 ++++ .github/workflows/matrix.yml | 4 ++++ .github/workflows/pgindent.yml | 2 ++ .github/workflows/sanitizers.yml | 4 ++++ .github/workflows/stormweaver.yml | 4 ++++ 6 files changed, 20 insertions(+) diff --git a/.github/workflows/codechecker.yml b/.github/workflows/codechecker.yml index d8f6866e9d661..ab847a4e6d70b 100644 --- a/.github/workflows/codechecker.yml +++ b/.github/workflows/codechecker.yml @@ -3,6 +3,8 @@ on: push: branches: - TDE_REL_17_STABLE + paths-ignore: + - contrib/pg_tde/documentation/** env: CC: clang diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3dd4891923dcd..70d227e18bea3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,9 +1,13 @@ name: Code coverage on: pull_request: + paths-ignore: + - contrib/pg_tde/documentation/** push: branches: - TDE_REL_17_STABLE + paths-ignore: + - contrib/pg_tde/documentation/** env: PGCTLTIMEOUT: 120 # Avoid failures on slow recovery diff --git a/.github/workflows/matrix.yml b/.github/workflows/matrix.yml index 88a022b82213c..913ef6eb7453c 100644 --- a/.github/workflows/matrix.yml +++ b/.github/workflows/matrix.yml @@ -1,10 +1,14 @@ name: Build and Test on: pull_request: + paths-ignore: + - contrib/pg_tde/documentation/** push: branches: - TDE_REL_17_STABLE - release-[0-9]+.[0-9]+* + paths-ignore: + - contrib/pg_tde/documentation/** workflow_dispatch: jobs: diff --git a/.github/workflows/pgindent.yml b/.github/workflows/pgindent.yml index c1a99c63082e7..73f6bee116324 100644 --- a/.github/workflows/pgindent.yml +++ b/.github/workflows/pgindent.yml @@ -1,6 +1,8 @@ name: Format on: pull_request: + paths-ignore: + - contrib/pg_tde/documentation/** workflow_dispatch: jobs: diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 540b32e67d3ea..b1196d41f4fbe 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -1,9 +1,13 @@ name: Sanitizers on: pull_request: + paths-ignore: + - contrib/pg_tde/documentation/** push: branches: - TDE_REL_17_STABLE + paths-ignore: + - contrib/pg_tde/documentation/** env: CC: clang diff --git a/.github/workflows/stormweaver.yml b/.github/workflows/stormweaver.yml index 655e97ff0adee..779723740129c 100644 --- a/.github/workflows/stormweaver.yml +++ b/.github/workflows/stormweaver.yml @@ -1,10 +1,14 @@ name: Stormweaver on: pull_request: + paths-ignore: + - contrib/pg_tde/documentation/** push: branches: - TDE_REL_17_STABLE - release-[0-9]+.[0-9]+* + paths-ignore: + - contrib/pg_tde/documentation/** workflow_dispatch: jobs: From aa2357803fddc35330892c975c752cb95ad9d687 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 26 Jun 2025 19:14:00 +0300 Subject: [PATCH 501/796] Docs 17.5.1 revert commit 56106 (#458) reverted set key changes for architecture, functions, set principal key and multi-tenant-setup.md --- .../documentation/docs/architecture/index.md | 17 ++--- .../pg_tde/documentation/docs/functions.md | 69 ++++++++++--------- .../set-principal-key.md | 27 +++----- .../docs/how-to/multi-tenant-setup.md | 57 ++++----------- 4 files changed, 64 insertions(+), 106 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 54542ee94e0b6..f1c424bb7e2a7 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -276,19 +276,15 @@ pg_tde_REVOKE_database_key_management_FROM_role ### Creating and rotating keys -Principal keys can be created using the following functions: +Principal keys can be created or rotated using the following functions: ```sql -pg_tde_create_key_using_(global/database)_key_provider('key-name', 'provider-name') +pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) ``` -Principal keys can be used or rotated using the following functions: - -```sql -pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name') -pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name') -pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name') -``` +`ensure_new_key` is a boolean parameter defaulting to false. If it is `true` the function might return an error instead of setting the key if it already exists on the provider. ### Default principal key @@ -300,8 +296,7 @@ With this feature, it is possible for the entire database server to easily use t You can manage a default key with the following functions: -* `pg_tde_create_key_using_global_key_provider('key-name','provider-name')` -* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name')` +* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name','true/false')` * `pg_tde_delete_default_key()` !!! note diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index fcc7d03c51ef4..39f1371fee4d1 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -230,78 +230,85 @@ These functions list the details of all key providers for the current database o ## Principal key management -Use these functions to create a new principal key at a given keyprover, and to use those keys for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. +Use these functions to create a new principal key for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. Princial keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. -### pg_tde_creates_key_using_database_key_provider - -Creates a principal key at a database local key provider with the given name. For later use with pg_tde_set_key_using_database_key_provider(). - -```sql -SELECT pg_tde_create_key_using_database_key_provider( - 'key-name', - 'provider-name' -); -``` -### pg_tde_create_key_using_global_key_provider - -Creates a principal key at a global key provider with the given name. For later use with pg_tde_set_ series of functions. - -```sql -SELECT pg_tde_create_key_using_global_key_provider( - 'key-name', - 'provider-name' -); -``` - ### pg_tde_set_key_using_database_key_provider -Sets the principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. +Creates or reuses a principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. This function is typically used when working with per-database encryption through a local key provider. ```sql SELECT pg_tde_set_key_using_database_key_provider( 'key-name', - 'provider-name' + 'provider-name', + 'false' -- or 'true' ); ``` + +For the third parameter (`true`, `false`, or omitted): + +* `true`: Requires the key to be newly created. If a key with the same name already exists, the function fails. +* `false` (default if omitted): Reuses the existing key with that name, if present. If the key does not exist, a new key is created. + ### pg_tde_set_key_using_global_key_provider -Sets or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. +Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. ```sql SELECT pg_tde_set_key_using_global_key_provider( 'key-name', - 'provider-name' + 'provider-name', + 'ensure_new_key' ); ``` + The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: + +* If set to `true`, a new key must be unique. + If the provider already stores a key by that name, the function returns an error. +* If set to `false` (default), an existing principal key may be reused. + ### pg_tde_set_server_key_using_global_key_provider -Sets or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. +Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. ```sql SELECT pg_tde_set_server_key_using_global_key_provider( 'key-name', - 'provider-name' + 'provider-name', + 'ensure_new_key' ); ``` +The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: + +* If set to `true`, a new key must be unique. + If the provider already stores a key by that name, the function returns an error. +* If set to `false` (default), an existing principal key may be reused. + ### pg_tde_set_default_key_using_global_key_provider -Sets or rotates the default principal key for the server using the specified global key provider. +Creates or rotates the default principal key for the server using the specified global key provider. -The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. +The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'provider-name' + 'provider-name', + 'ensure_new_key' ); ``` +The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: + +* If set to `true`, a new key must be unique. + If the provider already stores a key by that name, the function returns an error. +* If set to `false` (default), an existing principal key may be reused. + ### pg_tde_delete_key Deletes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 4c3cc3802d04e..86bb9b1ada853 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -4,23 +4,13 @@ You can configure a default principal key using a global key provider. This key ## Create a default principal key -To create a global principal key, run: - -```sql -SELECT pg_tde_create_key_using_global_key_provider( - 'key-name', - 'global_vault_provider' -); -``` - -## Configure a default principal key - To configure a global principal key, run: ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'global_vault_provider' + 'global_vault_provider', + 'false' -- or 'true', or omit entirely ); ``` @@ -28,10 +18,13 @@ SELECT pg_tde_set_default_key_using_global_key_provider( * `key-name` is the name under which the principal key is stored in the provider. * `global_vault_provider` is the name of the global key provider you previously configured. +* Third parameter (optional): + * `true` requires the key to be newly created. If the key already exists, the function fails. + * `false` or omitted (default), allows reuse of an existing key if it exists. If not, a new key is created under the specified name. ## How key generation works -The key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. +If the specified key does **not** exist, a new encryption key is created under the given name. In this case, the key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. !!! note This process sets the **default principal key** for the server. Any database without its own key configuration will use this key. @@ -41,14 +34,10 @@ The key material (actual cryptographic key) is auto-generated by `pg_tde` and st This example is for testing purposes only. Replace the key name and provider name with your values: ```sql -SELECT pg_tde_create_key_using_global_key_provider( - 'test-db-master-key', - 'file-vault' -); - SELECT pg_tde_set_key_using_global_key_provider( 'test-db-master-key', - 'file-vault' + 'file-vault', + 'false' ); ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 19a9974b3df28..6e9454dded4b5 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -42,7 +42,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo !!! tip - You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: + You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: ```sh psql -d template1 -c 'CREATE EXTENSION pg_tde;' @@ -59,8 +59,8 @@ You must do these steps for every database where you have created the extension. The KMIP server setup is out of scope of this document. Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). - - For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. + + For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql SELECT pg_tde_add_database_key_provider_kmip( @@ -100,16 +100,10 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_database_key_provider_vault_v2( - 'provider-name', - 'url', - 'mount', - 'secret_token_path', - 'ca_path' - ); - ``` + SELECT pg_tde_add_database_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); + ``` - where: + where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys @@ -147,52 +141,25 @@ You must do these steps for every database where you have created the extension. '/tmp/pg_tde_test_local_keyring.per' ); ``` + +2. Add a principal key -2. Create a key ```sql - - SELECT pg_tde_create_key_using_database_key_provider( - 'name-of-the-key', - 'provider-name' - ); + SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key', 'provider-name','ensure_new_key'); ``` where: * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. - * `provider-name` is the name of the key provider you added before. The principal key is associated with this provider and it is the location where it is stored and fetched from. + * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. + * `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_create_key_using_database_key_provider( - 'test-db-master-key', - 'file-vault' - ); + SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); ``` !!! note The key is auto-generated. -3. Use the key as principal key - ```sql - - SELECT pg_tde_set_key_using_database_key_provider( - 'name-of-the-key', - 'provider-name' - ); - ``` - - where: - - * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. - * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. - - :material-information: Warning: This example is for testing purposes only: - - ```sql - SELECT pg_tde_set_key_using_database_key_provider( - 'test-db-master-key', - 'file-vault' - ); - ``` From 8d88d3f28a060d060db0e1eedb5aa47ce3d1150f Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 27 Jun 2025 15:26:09 +0300 Subject: [PATCH 502/796] Updated principal-key/features/functions.md based on AA feedback (#441) In set-principal-key.md: * updated with correct code example using set_server_key_using_global parameter * updated note to reflect correct config In features.md: * Removed temporary tables feature to clear confusion, removed logical replication mention, removed WAL encryption as a feature. In functions.md: * Added ON FUNCTION for grant/revoke execution * Modified sensitive info bolded paragraph to important note * Small modifications to notes display, title cases and text fixes * added note to Add or modify Vault providers for keeping the same principal key. * Added warning for WAL in pg_tde_create_key_using_global_key_provider In general: * Removed all logical replication mentions except the FAQ and in RC2 release note. --- contrib/pg_tde/documentation/docs/features.md | 4 +- .../pg_tde/documentation/docs/functions.md | 56 +++++++++++++------ .../set-principal-key.md | 9 ++- .../docs/index/supported-versions.md | 4 +- .../documentation/docs/index/tde-encrypts.md | 3 +- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index e7dc760e1c821..ce9cd10826a81 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -9,16 +9,14 @@ The following features are available for the extension: * Data tables * Index data for encrypted tables * TOAST tables - * Temporary tables created during database operations + * Temporary tables !!! note Metadata of those tables is not encrypted. -* Global Write-Ahead Log (WAL) encryption for data in both encrypted and non-encrypted tables * Single-tenancy support via a global keyring provider * Multi-tenancy support * Table-level granularity for encryption and access control * Multiple Key management options -* Logical replication of encrypted tables [Overview](index/index.md){.md-button} [Get Started](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 39f1371fee4d1..735c33cfae809 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -8,15 +8,15 @@ By default, `pg_tde` is locked down. No one is allowed to do any operations unti However, database owners can run the “view keys” and “set principal key” functions on their own databases. You can delegate these rights to other roles with the following commands: -* `GRANT EXECUTE` -* `REVOKE EXECUTE` +* `GRANT EXECUTE ON FUNCTION` +* `REVOKE EXECUTE ON FUNCTION` ## Key provider management A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: * local file (not recommended for production use) -* Hashicorp Vault / OpenBao +* HashiCorp Vault / OpenBao * KMIP compatible providers Key provider management includes the following operations: @@ -52,9 +52,11 @@ The `change` functions require the same parameters as the `add` functions. They Provider specific parameters differ for each implementation. Refer to the respective subsection for details. -**Some provider specific parameters contain sensitive information, such as passwords. Never specify these directly, use the remote configuration option instead.** +!!! note + The updated provider must be able to retrieve the same principal keys as the original configuration. + If the new configuration cannot access existing keys, encrypted data and backups will become unreadable. -#### Adding or modifying Vault providers +#### Add or modify Vault providers The Vault provider connects to a HashiCorp Vault or an OpenBao server, and stores the keys on a key-value store version 2. @@ -106,7 +108,7 @@ where: * `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point * **[optional]** `ca_path` is the path of the CA file used for SSL verification -#### Adding or modifying KMIP providers +#### Add or modify KMIP providers The KMIP provider uses a remote KMIP server. @@ -165,16 +167,16 @@ where: !!! note The specified access parameters require permission to read and write keys at the server. -### Adding or modifying local keyfile providers +### Add or modify local key file providers -This provider manages database keys using a local keyfile. +This provider manages database keys using a local key file. This function is intended for development or quick testing, and stores the keys unencrypted in the specified data file. !!! important - Local keyfile providers are **not recommended** for production environments, they lack the security and manageability of external key management systems. + Local key file providers are **not recommended** for production environments, they lack the security and manageability of external key management systems. -Add a local keyfile provider: +Add a local key file provider: ```sql SELECT pg_tde_add_database_key_provider_file( @@ -188,7 +190,7 @@ SELECT pg_tde_add_global_key_provider_file( ); ``` -Change a local keyfile provider: +Change a local key file provider: ```sql SELECT pg_tde_change_database_key_provider_file( @@ -225,14 +227,33 @@ These functions list the details of all key providers for the current database o * `pg_tde_list_all_database_key_providers()` * `pg_tde_list_all_global_key_providers()` -!!! important - All configuration values include possibly sensitive values, such as passwords. **Never** specify these directly, use the remote configuration option instead. - ## Principal key management -Use these functions to create a new principal key for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. +Use these functions to create a new principal key at a given key provider, and to use those keys for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. + +Principal keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. + +### pg_tde_creates_key_using_database_key_provider -Princial keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. +Creates a principal key using the database-local key provider with the specified name. Use this key later with [`pg_tde_set_key_using_database_key_provider()`](#pg_tde_set_key_using_database_key_provider). + +```sql +SELECT pg_tde_create_key_using_database_key_provider( + 'key-name', + 'provider-name' +); +``` + +### pg_tde_create_key_using_global_key_provider + +Creates a principal key at a global key provider with the given name. Use this key later with the `pg_tde_set_` series of functions. + +```sql +SELECT pg_tde_create_key_using_global_key_provider( + 'key-name', + 'provider-name' +); +``` ### pg_tde_set_key_using_database_key_provider @@ -283,6 +304,9 @@ SELECT pg_tde_set_server_key_using_global_key_provider( ); ``` +!!! warning + The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. +======= The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: * If set to `true`, a new key must be unique. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 86bb9b1ada853..347dc9dff9094 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -27,17 +27,16 @@ SELECT pg_tde_set_default_key_using_global_key_provider( If the specified key does **not** exist, a new encryption key is created under the given name. In this case, the key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. !!! note - This process sets the **default principal key** for the server. Any database without its own key configuration will use this key. + This process sets the **default principal key for the entire server**. Any database without a key explicitly configured will fall back to this key. ## Example This example is for testing purposes only. Replace the key name and provider name with your values: ```sql -SELECT pg_tde_set_key_using_global_key_provider( - 'test-db-master-key', - 'file-vault', - 'false' +SELECT pg_tde_set_server_key_using_global_key_provider( + 'key-name', + 'provider-name' ); ``` diff --git a/contrib/pg_tde/documentation/docs/index/supported-versions.md b/contrib/pg_tde/documentation/docs/index/supported-versions.md index ed4758d0b671c..d943451450d58 100644 --- a/contrib/pg_tde/documentation/docs/index/supported-versions.md +++ b/contrib/pg_tde/documentation/docs/index/supported-versions.md @@ -1,6 +1,6 @@ # Versions and Supported PostgreSQL Deployments -The `pg_tde` extension is available for [Percona Server for PostgreSQL 17.x](https://docs.percona.com/postgresql/17/postgresql-server.html), an open source, drop-in replacement for PostgreSQL Community. This version provides the `tde_heap` access method and offers [full encryption capabilities](../features.md), including encryption of tables, indexes, WAL data, and support for logical replication. +The `pg_tde` extension is available for [Percona Server for PostgreSQL 17.x](https://docs.percona.com/postgresql/17/postgresql-server.html), an open source, drop-in replacement for PostgreSQL Community. This version provides the `tde_heap` access method and offers [full encryption capabilities](../features.md), including encryption of tables, indexes and WAL data. The extension is tightly integrated with Percona Server for PostgreSQL to deliver enhanced encryption functionality that is not available in community builds. @@ -8,7 +8,7 @@ The extension is tightly integrated with Percona Server for PostgreSQL to delive By using our PostgreSQL distribution, you get: -- **Full encryption support** through the `tde_heap` access method, including tables, indexes, WAL data, and logical replication. +- **Full encryption support** through the `tde_heap` access method, including tables, indexes and WAL data. - **Enhanced performance and enterprise-ready features** not available in community builds. - **Regular updates and security patches** backed by Percona’s expert support team. - **Professional support** and guidance for secure PostgreSQL deployments. diff --git a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md index 53bbb82b2198f..1bc2c70cef3e4 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md +++ b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md @@ -6,6 +6,7 @@ * **Temporary tables** created during the query execution, for data tables created using the extension. * **Write-Ahead Log (WAL) data** for the entire database cluster. This includes WAL data in encrypted and non-encrypted tables. * **Indexes** on encrypted tables. -* **Logical replication data** for encrypted tables (ensures encrypted content is preserved across replicas). + +!!! [Table Access Methods and TDE](table-access-method.md){.md-button} From 58153f9f23b932a438638d0114c0c63579568779 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 27 Jun 2025 15:43:51 +0300 Subject: [PATCH 503/796] Add OpenBao Topic ver 2 (#459) - added openbao topic and toc update for new file - content based on vault.md descriptions --- .../kmip-openbao.md | 48 +++++++++++++++++++ contrib/pg_tde/documentation/mkdocs.yml | 1 + 2 files changed, 49 insertions(+) create mode 100644 contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md new file mode 100644 index 0000000000000..7b11a694a3d26 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md @@ -0,0 +1,48 @@ +# Using OpenBao as a Key Provider + +You can configure `pg_tde` to use OpenBao as a global key provider for managing encryption keys securely. + +!!! note + This guide assumes that your OpenBao server is already set up and accessible. OpenBao configuration is outside the scope of this document, see [OpenBao's official documentation](https://openbao.org/docs/) for more information. + +## Example usage + +To register an OpenBao server as a global key provider: + +```sql +SELECT pg_tde_add_global_key_provider_vault_v2( + 'provider-name', + 'url', + 'mount', + 'secret_token_path', + 'ca_path' +); +``` + +## Parameter descriptions + +* `provider-name` is the name to identify this key provider +* `secret_token_path` is a path to the file that contains an access token with read and write access to the above mount point +* `url` is the URL of the Vault server +* `mount` is the mount point where the keyring should store the keys +* [optional] `ca_path` is the path of the CA file used for SSL verification + +The following example is for testing purposes only. Use secure tokens and proper SSL validation in production environments: + +```sql +SELECT pg_tde_add_global_key_provider_vault_v2( + 'my-openbao-provider', + 'https://openbao.example.com:8200', + 'secret/data', + '/path/to/token_file', + '/path/to/ca_cert.pem' +); +``` + +For more information on related functions, see the link below: + +[Percona pg_tde Function Reference](../functions.md){.md-button} + +## Next steps + +[Global Principal Key Configuration :material-arrow-right:](set-principal-key.md){.md-button} diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 3a60bca374459..c5ebac5eda6d8 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -181,6 +181,7 @@ nav: - "Fortanix Configuration": global-key-provider-configuration/kmip-fortanix.md - "Vault Configuration": global-key-provider-configuration/vault.md - "Thales Configuration": global-key-provider-configuration/kmip-thales.md + - "Using OpenBao as a Key Provider": global-key-provider-configuration/kmip-openbao.md - "Keyring File Configuration": global-key-provider-configuration/keyring.md - "2.2 Global Principal Key Configuration": global-key-provider-configuration/set-principal-key.md - "3. Validate Encryption with pg_tde": test.md From f10eae39202e7137523c40ba065ecfad31d71ed3 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 27 Jun 2025 14:48:52 +0200 Subject: [PATCH 504/796] Clarify key deletion funcs description in docs Key deletion fucntions don't delete anything as keys stored in external key management system. So these functions just remove keys from TDE. --- contrib/pg_tde/documentation/docs/architecture/index.md | 4 ++-- contrib/pg_tde/documentation/docs/functions.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index f1c424bb7e2a7..957173e72620e 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -305,10 +305,10 @@ You can manage a default key with the following functions: #### Delete a key -The `pg_tde_delete_key()` function removes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. +The `pg_tde_delete_key()` function unsets the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. !!! note - WAL keys **cannot** be deleted, as server keys are managed separately. + WAL keys **cannot** be unset, as server keys are managed separately. ### Current key details diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 735c33cfae809..ed21ae3a49d9a 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -335,7 +335,7 @@ The `ensure_new_key` parameter instructs the function how to handle a principal ### pg_tde_delete_key -Deletes the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. +Unsets the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. ```sql SELECT pg_tde_delete_key(); @@ -343,7 +343,7 @@ SELECT pg_tde_delete_key(); ### pg_tde_delete_default_key -Deletes default principal key. It's possible only if no database uses default principal key. +Unsets default principal key. It's possible only if no database uses default principal key. ```sql SELECT pg_tde_delete_default_key(); From 33af938d3b179050c2630a0c3eadbb327d4a7428 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 30 Jun 2025 10:00:03 +0300 Subject: [PATCH 505/796] Re-apply set key changes: revert of revert commit (#461) reverted the revert for set key changes for architecture, functions, set principal key and multi-tenant-setup.md --- .../documentation/docs/architecture/index.md | 17 ++++-- .../pg_tde/documentation/docs/functions.md | 42 ++++---------- .../set-principal-key.md | 30 +++++++--- .../docs/how-to/multi-tenant-setup.md | 57 +++++++++++++++---- 4 files changed, 87 insertions(+), 59 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 957173e72620e..08332e56fe7c6 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -276,15 +276,19 @@ pg_tde_REVOKE_database_key_management_FROM_role ### Creating and rotating keys -Principal keys can be created or rotated using the following functions: +Principal keys can be created using the following functions: ```sql -pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) -pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) -pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name', ensure_new_key) +pg_tde_create_key_using_(global/database)_key_provider('key-name', 'provider-name') ``` -`ensure_new_key` is a boolean parameter defaulting to false. If it is `true` the function might return an error instead of setting the key if it already exists on the provider. +Principal keys can be used or rotated using the following functions: + +```sql +pg_tde_set_key_using_(global/database)_key_provider('key-name', 'provider-name') +pg_tde_set_server_key_using_(global/database)_key_provider('key-name', 'provider-name') +pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provider-name') +``` ### Default principal key @@ -296,7 +300,8 @@ With this feature, it is possible for the entire database server to easily use t You can manage a default key with the following functions: -* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name','true/false')` +* `pg_tde_create_key_using_global_key_provider('key-name','provider-name')` +* `pg_tde_set_default_key_using_global_key_provider('key-name','provider-name')` * `pg_tde_delete_default_key()` !!! note diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index ed21ae3a49d9a..410d83e7f4815 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -229,7 +229,7 @@ These functions list the details of all key providers for the current database o ## Principal key management -Use these functions to create a new principal key at a given key provider, and to use those keys for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. +Use these functions to create a new principal key at a given keyprover, and to use those keys for a specific scope such as a current database, a global or default scope. You can also use them to start using a different existing key for a specific scope. Principal keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. @@ -257,50 +257,35 @@ SELECT pg_tde_create_key_using_global_key_provider( ### pg_tde_set_key_using_database_key_provider -Creates or reuses a principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. +Sets the principal key for the **current** database, using the specified local key provider. It also rotates internal encryption keys to use the specified principal key. This function is typically used when working with per-database encryption through a local key provider. ```sql SELECT pg_tde_set_key_using_database_key_provider( 'key-name', - 'provider-name', - 'false' -- or 'true' + 'provider-name' ); ``` - -For the third parameter (`true`, `false`, or omitted): - -* `true`: Requires the key to be newly created. If a key with the same name already exists, the function fails. -* `false` (default if omitted): Reuses the existing key with that name, if present. If the key does not exist, a new key is created. - ### pg_tde_set_key_using_global_key_provider -Creates or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. +Sets or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. ```sql SELECT pg_tde_set_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` - The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ### pg_tde_set_server_key_using_global_key_provider -Creates or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. +Sets or rotates the server principal key using the specified global key provider. Use this function to set a principal key for WAL encryption. ```sql SELECT pg_tde_set_server_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` @@ -315,24 +300,17 @@ The `ensure_new_key` parameter instructs the function how to handle a principal ### pg_tde_set_default_key_using_global_key_provider -Creates or rotates the default principal key for the server using the specified global key provider. +Sets or rotates the default principal key for the server using the specified global key provider. -The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. +The default key is automatically used as a principal key by any database that doesn't have an individual key provider and key configuration. ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'provider-name', - 'ensure_new_key' + 'provider-name' ); ``` -The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ### pg_tde_delete_key Unsets the principal key for the current database. If the current database has any encrypted tables, and there isn’t a default principal key configured, it reports an error instead. If there are encrypted tables, but there’s also a default principal key, internal keys will be encrypted with the default key. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 347dc9dff9094..c069f44e98209 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -4,13 +4,23 @@ You can configure a default principal key using a global key provider. This key ## Create a default principal key +To create a global principal key, run: + +```sql +SELECT pg_tde_create_key_using_global_key_provider( + 'key-name', + 'global_vault_provider' +); +``` + +## Configure a default principal key + To configure a global principal key, run: ```sql SELECT pg_tde_set_default_key_using_global_key_provider( 'key-name', - 'global_vault_provider', - 'false' -- or 'true', or omit entirely + 'global_vault_provider' ); ``` @@ -18,13 +28,10 @@ SELECT pg_tde_set_default_key_using_global_key_provider( * `key-name` is the name under which the principal key is stored in the provider. * `global_vault_provider` is the name of the global key provider you previously configured. -* Third parameter (optional): - * `true` requires the key to be newly created. If the key already exists, the function fails. - * `false` or omitted (default), allows reuse of an existing key if it exists. If not, a new key is created under the specified name. ## How key generation works -If the specified key does **not** exist, a new encryption key is created under the given name. In this case, the key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. +The key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. !!! note This process sets the **default principal key for the entire server**. Any database without a key explicitly configured will fall back to this key. @@ -34,9 +41,14 @@ If the specified key does **not** exist, a new encryption key is created under t This example is for testing purposes only. Replace the key name and provider name with your values: ```sql -SELECT pg_tde_set_server_key_using_global_key_provider( - 'key-name', - 'provider-name' +SELECT pg_tde_create_key_using_global_key_provider( + 'test-db-master-key', + 'file-vault' +); + +SELECT pg_tde_set_key_using_global_key_provider( + 'test-db-master-key', + 'file-vault' ); ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 6e9454dded4b5..19a9974b3df28 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -42,7 +42,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo !!! tip - You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: + You can have the `pg_tde` extension automatically enabled for every newly created database. Modify the template `template1` database as follows: ```sh psql -d template1 -c 'CREATE EXTENSION pg_tde;' @@ -59,8 +59,8 @@ You must do these steps for every database where you have created the extension. The KMIP server setup is out of scope of this document. Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). - - For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. + + For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. ```sql SELECT pg_tde_add_database_key_provider_kmip( @@ -100,10 +100,16 @@ You must do these steps for every database where you have created the extension. The Vault server setup is out of scope of this document. ```sql - SELECT pg_tde_add_database_key_provider_vault_v2('provider-name', 'url', 'mount', 'secret_token_path', 'ca_path'); - ``` + SELECT pg_tde_add_database_key_provider_vault_v2( + 'provider-name', + 'url', + 'mount', + 'secret_token_path', + 'ca_path' + ); + ``` - where: + where: * `url` is the URL of the Vault server * `mount` is the mount point where the keyring should store the keys @@ -141,25 +147,52 @@ You must do these steps for every database where you have created the extension. '/tmp/pg_tde_test_local_keyring.per' ); ``` - -2. Add a principal key +2. Create a key ```sql - SELECT pg_tde_set_key_using_database_key_provider('name-of-the-key', 'provider-name','ensure_new_key'); + + SELECT pg_tde_create_key_using_database_key_provider( + 'name-of-the-key', + 'provider-name' + ); ``` where: * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. - * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. - * `ensure_new_key` defines if a principal key must be unique. The default value `true` means that you must speficy a unique key during key rotation. The `false` value allows reusing an existing principal key. + * `provider-name` is the name of the key provider you added before. The principal key is associated with this provider and it is the location where it is stored and fetched from. :material-information: Warning: This example is for testing purposes only: ```sql - SELECT pg_tde_set_key_using_database_key_provider('test-db-master-key','file-vault','ensure_new_key'); + SELECT pg_tde_create_key_using_database_key_provider( + 'test-db-master-key', + 'file-vault' + ); ``` !!! note The key is auto-generated. +3. Use the key as principal key + ```sql + + SELECT pg_tde_set_key_using_database_key_provider( + 'name-of-the-key', + 'provider-name' + ); + ``` + + where: + + * `name-of-the-key` is the name of the principal key. You will use this name to identify the key. + * `provider-name` is the name of the key provider you added before. The principal key will be associated with this provider. + + :material-information: Warning: This example is for testing purposes only: + + ```sql + SELECT pg_tde_set_key_using_database_key_provider( + 'test-db-master-key', + 'file-vault' + ); + ``` From dfcef9f1d2e5c726f3d948f165e15fdf6dbaae79 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 30 Jun 2025 15:11:37 +0300 Subject: [PATCH 506/796] Prepare general docs for GA release (#434) Updated the introduction with the proper extension name, updated the intro to reflect this. Removed important note about not meant for production and added the No upgrade warning from previous versions (like RC2) to GA. Updates: * removed the block announcement for RC2 at the top of the HTML page in the intro * Added the warning note before installation begins too. * Updated site name to full name. --- .../_resource/overrides/main.html | 7 ------ .../_resourcepdf/overrides/main.html | 7 ------ .../documentation/docs/architecture/index.md | 23 +------------------ contrib/pg_tde/documentation/docs/faq.md | 6 +++-- .../pg_tde/documentation/docs/functions.md | 9 -------- .../kmip-server.md | 2 +- .../docs/how-to/multi-tenant-setup.md | 14 +++++------ contrib/pg_tde/documentation/docs/index.md | 9 ++++---- contrib/pg_tde/documentation/docs/install.md | 6 ++--- .../docs/templates/pdf_cover_page.tpl | 2 +- contrib/pg_tde/documentation/mkdocs.yml | 2 +- 11 files changed, 23 insertions(+), 64 deletions(-) diff --git a/contrib/pg_tde/documentation/_resource/overrides/main.html b/contrib/pg_tde/documentation/_resource/overrides/main.html index 1fca8c3d06631..3cf5dc8d34ff9 100644 --- a/contrib/pg_tde/documentation/_resource/overrides/main.html +++ b/contrib/pg_tde/documentation/_resource/overrides/main.html @@ -3,13 +3,6 @@ -#} {% extends "base.html" %} -{% block announce %} - This is the Release Candidate 2 (RC2) of Percona Transparent Data Encryption (TDE) extension. -

It is not recommended for production environments at this stage.

-

We encourage you to test it and give your feedback. - This will help us improve the product and make it production-ready faster.

-{% endblock %} - {% block scripts %} {{ super() }} diff --git a/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html b/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html index 1fca8c3d06631..3cf5dc8d34ff9 100644 --- a/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html +++ b/contrib/pg_tde/documentation/_resourcepdf/overrides/main.html @@ -3,13 +3,6 @@ -#} {% extends "base.html" %} -{% block announce %} - This is the Release Candidate 2 (RC2) of Percona Transparent Data Encryption (TDE) extension. -

It is not recommended for production environments at this stage.

-

We encourage you to test it and give your feedback. - This will help us improve the product and make it production-ready faster.

-{% endblock %} - {% block scripts %} {{ super() }} diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 08332e56fe7c6..87a2e863d4c14 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -239,21 +239,17 @@ This is also the reason why it requires a `dbOid` instead of a name, as it has n ### Deleting providers -Providers can be deleted by using the +Providers can be deleted by using the following functions: ```sql pg_tde_delete_database_key_provider(provider_name) pg_tde_delete_global_key_provider(provider_name) ``` -functions. - For database specific providers, the function first checks if the provider is used or not, and the provider is only deleted if it's not used. For global providers, the function checks if the provider is used anywhere, WAL or any specific database, and returns an error if it is. -This somewhat goes against the principle that `pg_tde` should not interact with other databases than the one the user is connected to, but on the other hand, it only does this lookup in the internal `pg_tde` metadata, not in postgres catalogs, so it is a gray zone. Making this check makes more sense than potentially making some databases inaccessible. - ### Listing/querying providers `pg_tde` provides 2 functions to show providers: @@ -263,17 +259,6 @@ This somewhat goes against the principle that `pg_tde` should not interact with These functions return a list of provider names, type and configuration. -### Provider permissions - -`pg_tde` implements access control based on execution rights on the administration functions. - -For keys and providers administration, it provides two pair of functions: - -```sql -pg_tde_GRANT_database_key_management_TO_role -pg_tde_REVOKE_database_key_management_FROM_role -``` - ### Creating and rotating keys Principal keys can be created using the following functions: @@ -325,12 +310,6 @@ The `pg_tde_delete_key()` function unsets the principal key for the current data `pg_tde_verify_key()` checks that the key provider is accessible, that the current principal key can be downloaded from it, and that it is the same as the current key stored in memory - if any of these fail, it reports an appropriate error. -### Key permissions - -Users with management permissions to a specific database `(pg_tde_(grant/revoke)_(global/databse)_key_management_(to/from)_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. - -Also the `pg_tde_(grant/revoke)_database_key_management_to_role` function deals with only the specific permission for the above function: it allows a user to change the key for the database, but not to modify the provider configuration. - ### Creating encrypted tables To create an encrypted table or modify an existing table to be encrypted, use the following commands: diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index 82a8b536e4d4b..36430b722a1c4 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -27,7 +27,7 @@ Using TDE helps you avoid the following risks: If to translate sensitive data to files stored in your database, these are user data in tables, temporary files, WAL files. TDE has you covered encrypting all these files. -`pg_tde` does not encrypt system catalogs yet. This means that statistics data and database metadata are not encrypted. The encryption of system catalogs is planned for future releases. +`pg_tde` does not encrypt system catalogs yet. This means that statistics data and database metadata are not encrypted. ## Will logical replication work with pg_tde? @@ -121,7 +121,9 @@ We advise encrypting the whole database only if all your data is sensitive, like For WAL encryption, AES-CTR-128 is used. -The support of other encryption mechanisms such as AES256 is planned for future releases. Reach out to us with your requirements and usage scenarios of other encryption methods are needed. +## Is post-quantum encryption supported? + +No, it's not yet supported. In our implementation we reply on OpenSSL libraries that don't yet support post-quantum encryption. ## Can I encrypt an existing table? diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 410d83e7f4815..b5c51ec07cdf0 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -2,15 +2,6 @@ The `pg_tde` extension provides functions for managing different aspects of its operation: -## Permission management - -By default, `pg_tde` is locked down. No one is allowed to do any operations until you grant them permissions. Only superusers may add or alter global key providers. - -However, database owners can run the “view keys” and “set principal key” functions on their own databases. You can delegate these rights to other roles with the following commands: - -* `GRANT EXECUTE ON FUNCTION` -* `REVOKE EXECUTE ON FUNCTION` - ## Key provider management A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md index aea589ffb08fb..46d93e7f31a63 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md @@ -15,7 +15,7 @@ For testing purposes, you can use a lightweight PyKMIP server, which enables eas SELECT pg_tde_add_global_key_provider_kmip( 'provider-name', 'kmip-IP', - 5696, + `port`, '/path_to/server_certificate.pem', '/path_to/client_cert.pem', '/path_to/client_key.pem' diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 19a9974b3df28..c8eaac1afcfe8 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -64,13 +64,13 @@ You must do these steps for every database where you have created the extension. ```sql SELECT pg_tde_add_database_key_provider_kmip( - 'provider-name', - 'kmip-addr', - 5696, - '/path_to/client_cert.pem', - '/path_to/client_key.pem', - '/path_to/server_certificate.pem' - ); + 'provider-name', + 'kmip-addr', + `port`, + '/path_to/client_cert.pem', + '/path_to/client_key.pem', + '/path_to/server_certificate.pem' + ); ``` where: diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 30bf17a431bd2..5f2a091a250ff 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -1,9 +1,10 @@ -# pg_tde Documentation +# Percona Transparent Data Encryption for PostgreSQL documentation -`pg_tde` is the open source, community driven and futureproof PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. `pg_tde` ensures that the data stored on disk is encrypted, and that no one can read it without the proper encryption keys, even if they gain access to the physical storage media. +Percona Transparent Data Encryption for PostgreSQL (`pg_tde`) is an open source, community driven and futureproof PostgreSQL extension that provides Transparent Data Encryption (TDE) to protect data at rest. `pg_tde` ensures that the data stored on disk is encrypted, and that no one can read it without the proper encryption keys, even if they gain access to the physical storage media. -!!! important - This is the {{release}} version of the extension and **it is not meant for production use yet**. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). +!!! warning "No upgrade path from RC to GA" + There is no safe upgrade path from the previous versions, such as Release Candidate 2, to the General Availability (GA) version of `pg_tde`. + We recommend starting with a **clean installation** for GA deployments. Avoid using RC environments in production. [Overview](index/index.md){.md-button} [Get Started](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/install.md b/contrib/pg_tde/documentation/docs/install.md index 11cc03623c32d..273bb899cd29c 100644 --- a/contrib/pg_tde/documentation/docs/install.md +++ b/contrib/pg_tde/documentation/docs/install.md @@ -1,8 +1,8 @@ # Install pg_tde - +!!! warning "No upgrade path from RC to GA" + There is no safe upgrade path from the previous versions, such as Release Candidate 2, to the General Availability (GA) version of `pg_tde`. + We recommend starting with a **clean installation** for GA deployments. Avoid using RC environments in production. To install `pg_tde`, use one of the following methods: diff --git a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl index 286838e89fc54..9f4fb22ba71de 100644 --- a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl +++ b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl @@ -7,5 +7,5 @@ {% if config.site_description %}

{{ config.site_description }}

{% endif %} -

Release Candidate (2025-03-27)

+

1.0 (2025-06-30)

\ No newline at end of file diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index c5ebac5eda6d8..51e45a2b4a833 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -1,6 +1,6 @@ # MkDocs general configuration -site_name: pg_tde documentation +site_name: Percona Transparent Data Encryption for PostgreSQL site_description: Documentation site_author: Percona LLC copyright: > From b6c130569076306c424f27e66a0bf7b88c6a192f Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 30 Jun 2025 15:30:52 +0300 Subject: [PATCH 507/796] Update architecture/index.md (#439) Updated the Architecture topic with the following: - New intro detailing the long term tde goals in a paragraph - Updated the ## Typical setup scenarios topic with better writing and improved flow - Added note to WAL Encryption that it is not to be used in prod env - General small fixes to paragraphs, wrongly written words and such --- .../documentation/docs/architecture/index.md | 113 +++++++----------- 1 file changed, 45 insertions(+), 68 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/index.md index 87a2e863d4c14..76bc66e3c9ae4 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/index.md @@ -2,12 +2,12 @@ `pg_tde` is a **customizable, complete, data at rest encryption extension** for PostgreSQL. -Let's break down what it means. +The following sections break down the key architectural components of this design. **Customizable** means that `pg_tde` aims to support many different use cases: * Encrypting either every table in every database or only some tables in some databases -* Encryption keys can be stored on various external key storage servers including Hashicorp Vault, KMIP servers. +* Encryption keys can be stored on various external key storage servers including HashiCorp Vault and KMIP servers. * Using one key for everything or different keys for different databases * Storing every key on the same key storage, or using different storages for different databases * Handling permissions: who can manage database specific or global permissions, who can create encrypted or not encrypted tables @@ -20,7 +20,7 @@ Let's break down what it means. * Indexes * Sequences * Temporary tables -* Write Ahead Log (WAL) +* Write Ahead Log (WAL), still in beta. **Do not enable this feature in production environments**. **Extension** means that `pg_tde` should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. This requires changes in the PostgreSQL core to make it more extensible. Therefore, `pg_tde` currently works only with the [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/index.html) - a binary replacement of community PostgreSQL and included in Percona Distribution for PostgreSQL. @@ -82,13 +82,16 @@ Later decisions are made using a slightly modified Storage Manager (SMGR) API: w ### WAL encryption +!!! note + The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. + WAL encryption is controlled globally via a global GUC variable, `pg_tde.wal_encrypt`, that requires a server restart. WAL keys also contain the [LSN](https://www.postgresql.org/docs/17/wal-internals.html) of the first WAL write after key creation. This allows `pg_tde` to know which WAL ranges are encrypted or not and with which key. -The setting only controls writes so that only WAL writes are encrypted when WAL encryption is enabled. This means that WAL files can contain both encrypted and unencrpyted data, depending on what the status of this variable was when writing the data. +The setting only controls writes so that only WAL writes are encrypted when WAL encryption is enabled. This means that WAL files can contain both encrypted and unencrypted data, depending on what the status of this variable was when writing the data. -`pg_tde` keeps track of the encryption status of WAL records using internal keys. When the server is restarted it writes a new internal key if WAL encryption is enabled, or if it is disabled and was previously enabled it writes a dummy key signalling that WAL encryption ended. +`pg_tde` keeps track of the encryption status of WAL records using internal keys. When the server is restarted it writes a new internal key if WAL encryption is enabled, or if it is disabled and was previously enabled it writes a dummy key signaling that WAL encryption ended. With this information the WAL reader code can decide if a specific WAL record has to be decrypted or not and which key it should use to decrypt it. @@ -156,7 +159,7 @@ With these details `pg_tde` does the following based on user operations: * Uploads a new principal key to it after this key is created * Retrieves the principal key from the service when it is required for decryption -Retreival of the principal key is cached so it only happens when necessary. +Retrieval of the principal key is cached so it only happens when necessary. ### Key provider management @@ -169,24 +172,23 @@ For such situations, `pg_tde` also provides [command line tools](../command-line ### Sensitive key provider information !!! important - Authentication details for key providers are sensitive and must be protected. Do not store these credentials in the `$PGDATA` directory alongside the database. Instead, ensure they are stored in a secure location with strict file system permissions to prevent unauthorized access. ## User interface -### Setting up pg_tde +### Set up pg_tde -To use `pg_tde`, users are required to: +To get started with `pg_tde`, follow these steps: * Add `pg_tde` to the `shared_preload_libraries` in `postgresql.conf` as this is required for the SMGR extensions * Execute `CREATE EXTENSION pg_tde` in the databases where they want to use encryption * Optionally, enable `pg_tde.wal_encrypt` in `postgresql.conf` -* Optionally, disable `pg_tde.inherit_global_providers` in `postgresql.conf` (enabled by default) +* Optionally, disable `pg_tde.inherit_global_providers` in `postgresql.conf` (it is enabled by default) -### Adding providers +### Add providers -Keyring providers can be added to either the global or to the database specific scope. +You can add keyring providers to either the global or database specific scope. If `pg_tde.inherit_global_providers` is `on`, global providers are visible for all databases, and can be used. If `pg_tde.inherit_global_providers` is `off`, global providers are only used for WAL encryption. @@ -203,7 +205,7 @@ To add a database specific provider: pg_tde_add_database_key_provider_('provider_name', ... details ...) ``` -### Changing providers +### Change providers To change a value of a global provider: @@ -217,13 +219,9 @@ To change a value of a database specific provider: pg_tde_change_database_key_provider_('provider_name', ... details ...) ``` -These functions also allow changing the type of a provider. - -The functions however do not migrate any data. They are expected to be used during infrastructure migration, for example when the address of a server changes. +These functions also allow changing the type of a provider but **do not** migrate any data. They are expected to be used during infrastructure migration, for example when the address of a server changes. -Note that in these functions do not verify the parameters. For that, see `pg_tde_verify_key`. - -### Changing providers from the command line +### Change providers from the command line To change a provider from a command line, `pg_tde` provides the `pg_tde_change_key_provider` command line tool. @@ -233,11 +231,12 @@ This tool work similarly to the above functions, with the following syntax: pg_tde_change_key_provider ... details ... ``` -Note that since this tool is expected to be offline, it bypasses all permission checks! +!!! note + Since this tool is expected to be offline, it bypasses all permission checks. This is also the reason why it requires a `dbOid` instead of a name, as it has no way to process the catalog and look up names. -This is also the reason why it requires a `dbOid` instead of a name, as it has no way to process the catalog and look up names. + This tool does not validate any parameters. -### Deleting providers +### Delete providers Providers can be deleted by using the following functions: @@ -250,7 +249,7 @@ For database specific providers, the function first checks if the provider is us For global providers, the function checks if the provider is used anywhere, WAL or any specific database, and returns an error if it is. -### Listing/querying providers +### List/query providers `pg_tde` provides 2 functions to show providers: @@ -259,7 +258,18 @@ For global providers, the function checks if the provider is used anywhere, WAL These functions return a list of provider names, type and configuration. -### Creating and rotating keys +### Provider permissions + +`pg_tde` implements access control based on execution rights on the administration functions. + +For keys and providers administration, it provides two pair of functions: + +```sql +pg_tde_GRANT_database_key_management_TO_role +pg_tde_REVOKE_database_key_management_FROM_role +``` + +### Create and rotate keys Principal keys can be created using the following functions: @@ -279,7 +289,7 @@ pg_tde_set_default_key_using_(global/database)_key_provider('key-name', 'provide With `pg_tde.inherit_global_key_providers`, it is also possible to set up a default global principal key, which will be used by any database which has the `pg_tde` extension enabled, but doesn't have a database specific principal key configured using `pg_tde_set_key_using_(global/database)_key_provider`. -With this feature, it is possible for the entire database server to easily use the same principal key for all databases, completely disabling multi-tenency. +With this feature, it is possible for the entire database server to easily use the same principal key for all databases, completely disabling multi-tenancy. #### Manage a default key @@ -310,7 +320,13 @@ The `pg_tde_delete_key()` function unsets the principal key for the current data `pg_tde_verify_key()` checks that the key provider is accessible, that the current principal key can be downloaded from it, and that it is the same as the current key stored in memory - if any of these fail, it reports an appropriate error. -### Creating encrypted tables +### Key permissions + +Users with management permissions to a specific database `(pg_tde_(grant/revoke)_(global/databse)_key_management_(to/from)_role)` can change the keys for the database, and use the current key functions. This includes creating keys using global providers, if `pg_tde.inherit_global_providers` is enabled. + +Also the `pg_tde_(grant/revoke)_database_key_management_to_role` function deals with only the specific permission for the above function: it allows a user to change the key for the database, but not to modify the provider configuration. + +### Create encrypted tables To create an encrypted table or modify an existing table to be encrypted, use the following commands: @@ -319,47 +335,8 @@ CREATE TABLE t1(a INT) USING tde_heap; ALTER TABLE t1 SET ACCESS METHOD tde_heap; ``` -### Changing the `pg_tde.inherit_global_keys` setting - -It is possible for users to use `pg_tde` with `inherit_global_keys = on`, refer to global keys / keyrings in databases, and then change this setting to `off`. - -In this case existing references to global providers, or the global default principal key will remain working as before, but new references to the global scope can't be made. - -## Typical setup scenarios - -### Simple "one principal key" encryption - -1. Passing the option from the postgres config file the extension: `shared_preload_libraries=‘pg_tde’` -2. `CREATE EXTENSION pg_tde;` in `template1` -3. Adding a global key provider -4. Adding a default principal key using the same global provider -5. Enable WAL encryption to use the default principal key using `ALTER SYSTEM SET pg_tde.wal_encrypt=‘ON’` -6. Restart the server -7. Optionally: setting the `default_table_access_method` to `tde_heap` so that tables are encrypted by default - -Database users don't need permissions to any of the encryption functions: -encryption is managed by the admins, normal users only have to create tables with encryption, which requires no specific permissions. - -### One key storage, but different keys per database - -1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` -2. `CREATE EXTENSION pg_tde;` in `template1` -3. Adding a global key provider -4. Changing the WAL encryption to use the proper global key provider -5. Giving users that are expected to manage database keys permissions for database specific key management, but not database specific key provider management: - specific databases HAVE to use the global key provider - -Note: setting the `default_table_access_method` to `tde_heap` is possible, but instead of `ALTER SYSTEM` only per database using `ALTER DATABASE`, after a principal key is configured for that specific database. - -Alternatively `ALTER SYSTEM` is possible, but table creation in the database will fail if there's no principal key for the database, that has to be created first. - -### Complete multi tenancy - -1. Installing the extension: `shared_preload_libraries` + `pg_tde.wal_encrypt` (that's not multi tenant currently) -2. `CREATE EXTENSION pg_tde;` in any database -3. Adding a global key provider for WAL -4. Changing the WAL encryption to use the proper global key provider +### Change the pg_tde.inherit_global_keys setting -No default configuration: key providers / principal keys are configured as a per database level, permissions are managed per database +It is possible to use `pg_tde` with `inherit_global_keys = on`, refer to the global keys or keyrings in databases, and then change this setting to `off`. -Same note about `default_table_access_method` as above - but in a multi tenant setup, `ALTER SYSTEM` doesn't make much sense. +In this case, existing references to global providers or the global default principal key keep working as before, but new references to the global scope cannot be made. From 85037c48e06841b4bdc7041a8efb86d0ccd0cdb2 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 30 Jun 2025 19:12:51 +0300 Subject: [PATCH 508/796] Create Release Notes for 1.0 (#432) Added initial files and modifications to include 1.0 release notes to the TOC and variables. Updates: * updated the ToC names to make them in line with style guide * updated variable with new release branch and fixed small release note name * updated ## Release Highlights with topics: * Added tickets * Updated Upgrade considerations --- .../docs/release-notes/release-notes-v1.0.md | 61 +++++++++++++++++++ .../docs/release-notes/release-notes.md | 2 + contrib/pg_tde/documentation/mkdocs.yml | 19 +++--- contrib/pg_tde/documentation/variables.yml | 6 +- 4 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md new file mode 100644 index 0000000000000..96a7ce3b1ea2a --- /dev/null +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md @@ -0,0 +1,61 @@ +# pg_tde 1.0 ({{date.GA10}}) + +The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. + +[Get Started](../install.md){.md-button} + +## Release Highlights + +* **`pg_tde` 1.0 is now GA (Generally Available)** + +And **stable** for encrypting relational data in PostgreSQL using [Transparent Data Encryption (TDE)](../index/index.md). This milestone brings production-level data protection to PostgreSQL workloads. + +* **WAL encryption is still in Beta** + +The WAL encryption feature is currently still in beta and is not effective unless explicitly enabled. **It is not yet production ready.** Do **not** enable this feature in production environments. + +## Upgrade considerations + +`pg_tde` {{tdeversion}} is **not** backward compatible with previous `pg_tde` versions, like Release Candidate 2, due to significant changes in code. This means you **cannot** directly upgrade from one version to another. You must do **a clean installation** of `pg_tde`. + +## Known issues + +* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. + +To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: + +* temporarily for the current session using the `ulimit -l ` command. +* set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + +Adjust the limits with caution since it affects other processes running in your system. + +## Changelog + +### New Features + +- [PG-1257](https://perconadev.atlassian.net/browse/PG-1257) – Added SQL function to remove the current principal key + +### Improvements + +- [PG-1617](https://perconadev.atlassian.net/browse/PG-1617) – Removed relation key cache +- [PG-1635](https://perconadev.atlassian.net/browse/PG-1635) – User-facing TDE functions now return void +- [PG-1605](https://perconadev.atlassian.net/browse/PG-1605) – Removed undeclared dependencies for `pg_tde_grant_database_key_management_to_role()` + +### Bugs Fixed + +- [PG-1581](https://perconadev.atlassian.net/browse/PG-1581) – Fixed PostgreSQL crashes on table access when KMIP key is unavailable after restart +- [PG-1583](https://perconadev.atlassian.net/browse/PG-1583) – Fixed a crash when dropping the `pg_tde` extension with CASCADE after changing the key provider file +- [PG-1585](https://perconadev.atlassian.net/browse/PG-1585) – Fixed the vault provider re-addition that failed after server restart with a new token +- [PG-1592](https://perconadev.atlassian.net/browse/PG-1592) – Improve error logs when Server Key Info is requested without being created +- [PG-1593](https://perconadev.atlassian.net/browse/PG-1593) – Fixed runtime failures when invalid Vault tokens are allowed during key provider creation +- [PG-1600](https://perconadev.atlassian.net/browse/PG-1600) – Fixed Postmaster error when dropping a table with an unavailable key provider +- [PG-1606](https://perconadev.atlassian.net/browse/PG-1606) – Fixed missing superuser check in role grant function leads to misleading errors +- [PG-1607](https://perconadev.atlassian.net/browse/PG-1607) – Improved CA parameter order and surrounding documentation for clearer interpretation +- [PG-1608](https://perconadev.atlassian.net/browse/PG-1608) – Updated and fixed global key configuration parameters in documentation +- [PG-1613](https://perconadev.atlassian.net/browse/PG-1613) – Tested and improved the `pg_tde_change_key_provider` CLI utility +- [PG-1637](https://perconadev.atlassian.net/browse/PG-1637) – Fixed unused keys in key files which caused issues after OID wraparound +- [PG-1651](https://perconadev.atlassian.net/browse/PG-1651) – Fixed the CLI tool when working with Vault key export/import +- [PG-1652](https://perconadev.atlassian.net/browse/PG-1652) – Fixed when the server fails to find encryption keys after CLI-based provider change +- [PG-1662](https://perconadev.atlassian.net/browse/PG-1662) – Fixed the creation of inconsistent encryption status when altering partitioned tables +- [PG-1663](https://perconadev.atlassian.net/browse/PG-1663) – Fixed the indexes on partitioned tables which were not encrypted +- [PG-1700](https://perconadev.atlassian.net/browse/PG-1700) – Fixed the error hint when the principal key is missing diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index f5306753eaa77..7a5175285dc82 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -2,6 +2,8 @@ `pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +* [Percona Transparent Database Encryption for PostgreSQL 1.0 ({{date.GA10}})](release-notes-v1.0.md) +* [pg_tde Release Candidate 2 (RC2) ({{date.RC2}})](rc2.md) * [pg_tde Release Candidate 2 (RC2) ({{date.RC2}})](rc2.md) * [pg_tde Release Candidate ({{date.RC}})](rc.md) * [pg_tde Beta2 (2024-12-16)](beta2.md) diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 51e45a2b4a833..eca6171c87aab 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -163,14 +163,14 @@ nav: - "Features": features.md - "Overview": - "What is Transparent Data Encryption (TDE)?": - - "TDE Overview": index/index.md - - "TDE Benefits": index/how-tde-helps.md - - "How TDE Works": index/how-does-tde-work.md - - "Encrypted Data Scope": index/tde-encrypts.md - - "Table Access Methods and TDE": index/table-access-method.md + - "TDE overview": index/index.md + - "TDE benefits": index/how-tde-helps.md + - "How TDE works": index/how-does-tde-work.md + - "Encrypted data scope": index/tde-encrypts.md + - "Table access methods and TDE": index/table-access-method.md - "Limitations of TDE": index/tde-limitations.md - - "Versions and Supported PostgreSQL Deployments": index/supported-versions.md - - "Get Started": + - "Versions and supported PostgreSQL deployments": index/supported-versions.md + - "Get started": - "1. Install pg_tde": install.md - "1.1 Via apt": apt.md - "1.2 Via yum": yum.md @@ -189,7 +189,7 @@ nav: - "Technical Reference": - "Overview": advanced-topics/index.md - "Architecture": architecture/index.md - - "GUC Variables": variables.md + - "GUC variables": variables.md - "Functions": functions.md - "Streaming Replication with tde_heap": replication.md - "TDE Operations": @@ -204,8 +204,9 @@ nav: - "Decrypt an Encrypted Table": how-to/decrypt.md - "Restore an encrypted pg_tde backup": how-to/restore-backups.md - faq.md - - "Release Notes": + - "Release notes": - "pg_tde release notes": release-notes/release-notes.md + - release-notes/release-notes-v1.0.md - release-notes/rc2.md - release-notes/rc.md - release-notes/beta2.md diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index 9d4aa480f8364..2938e44a53a96 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -1,9 +1,11 @@ #Variables used throughout the docs -release: 'RC2' +tdeversion: '1.0' +release: '1.0' pgversion17: '17.5' -tdebranch: TDE_REL_17_STABLE +tdebranch: release-17.5.2 date: + GA10: '2025-06-30' RC2: '2025-05-29' RC: '2025-03-27' From 44ff89539499b9417f166e8f4c2948bffbc7ff7b Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 9 Jul 2025 14:27:28 +0300 Subject: [PATCH 509/796] Typo fix in RN index and TOC (#464) - remove index body as it is out of place --- .../docs/release-notes/release-notes.md | 20 +++++++++---------- contrib/pg_tde/documentation/mkdocs.yml | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index 7a5175285dc82..d1e7b8e1424d8 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -1,12 +1,10 @@ -# `pg_tde` release notes index +# Release notes index -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. - -* [Percona Transparent Database Encryption for PostgreSQL 1.0 ({{date.GA10}})](release-notes-v1.0.md) -* [pg_tde Release Candidate 2 (RC2) ({{date.RC2}})](rc2.md) -* [pg_tde Release Candidate 2 (RC2) ({{date.RC2}})](rc2.md) -* [pg_tde Release Candidate ({{date.RC}})](rc.md) -* [pg_tde Beta2 (2024-12-16)](beta2.md) -* [pg_tde Beta (2024-06-30)](beta.md) -* [pg_tde Alpha1 (2024-03-28)](alpha1.md) -* [pg_tde MVP (2023-12-12)](mvp.md) +* [Percona Transparent Database Encryption for PostgreSQL 1.0](release-notes-v1.0.md) ({{date.GA10}}) +* [pg_tde Release Candidate 2 (RC2)](rc2.md) ({{date.RC2}}) +* [pg_tde Release Candidate 1 (RC1)](rc2.md) ({{date.RC}}) +* [pg_tde Release Candidate](rc.md) ({{date.RC}}) +* [pg_tde Beta2](beta2.md) (2024-12-16) +* [pg_tde Beta](beta.md) (2024-06-30) +* [pg_tde Alpha1](alpha1.md) (2024-03-28) +* [pg_tde MVP](mvp.md) (2023-12-12) diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index eca6171c87aab..aa490d2dd0ada 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -205,7 +205,7 @@ nav: - "Restore an encrypted pg_tde backup": how-to/restore-backups.md - faq.md - "Release notes": - - "pg_tde release notes": release-notes/release-notes.md + - "Release notes index": release-notes/release-notes.md - release-notes/release-notes-v1.0.md - release-notes/rc2.md - release-notes/rc.md From c8bfe692c785a0d40304ab7edeaf29857d624224 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoi Date: Thu, 10 Jul 2025 15:13:06 +0300 Subject: [PATCH 510/796] Tidy sanitizers' suppressions list and fix some mem leaks (#438) This commit makes a suppression list as specific as possible, so it won't cover up new issues. And adds comments to existing suppressions. Also, fixes memory leaks in bin/pgctl code (all related to frontend usage) and low-hanging fruits in pgctl. --- ci_scripts/suppressions/lsan.supp | 56 +++++++++++++++--- contrib/pg_tde/src/access/pg_tde_tdemap.c | 7 +++ contrib/pg_tde/src/catalog/tde_keyring.c | 58 ++++++++++++++++++- .../pg_tde/src/catalog/tde_principal_key.c | 2 +- .../pg_tde/src/include/access/pg_tde_tdemap.h | 5 +- .../pg_tde/src/include/catalog/tde_keyring.h | 1 + contrib/pg_tde/src/keyring/keyring_vault.c | 1 + .../pg_tde/src/pg_tde_change_key_provider.c | 1 + 8 files changed, 119 insertions(+), 12 deletions(-) diff --git a/ci_scripts/suppressions/lsan.supp b/ci_scripts/suppressions/lsan.supp index 00ab2f110a22c..087f3164fc72c 100644 --- a/ci_scripts/suppressions/lsan.supp +++ b/ci_scripts/suppressions/lsan.supp @@ -1,14 +1,52 @@ +# external submodule +leak:kmip.c + +# the parser of command line arguments of the backend leak:save_ps_display_args + +# a bunch of not freed addrinfo sctructs, escaped values etc. leak:initdb.c -leak:fe_memutils.c -leak:fe-exec.c -leak:fe-connect.c -leak:pqexpbuffer.c -leak:strdup -leak:preproc.y -leak:pgtypeslib/common.c -leak:ecpglib/memory.c -leak:kmip.c + +# shlibs loaded with dlsym() never gets dlclose'd +# TODO: needs an investigation as it is down the call stack +# of exec_simple_query->PortalRun +leak:internal_load_library + +# pg_config utility does not free configdata +leak:pg_config/pg_config.c + +# A psprintf() result assigned to the global var pgdata_opt +leak:bin/pg_ctl/pg_ctl.c + +# TODO: A couple of not freed char *. Whilst wit's trivially to fix +# meson complains with "maybe-uninitialized" on free() +leak:bin/psql/describe.c + +# options parsing during the regressions start +leak:regression_main + +# Static funcs in /bin/psql/startup.c are used +# during the start to parse options +leak:simple_action_list_append +leak:parse_psql_options + +# A bunch of parameters, options and identifiers are not being freed. +# +# TODO?: ReceiveArchiveStreamChunk->CreateBackupStreamer down in the +# callstak creates a streamer that is never freed. It's being called in a loop +# but it looks like it is not a repetitive action and rather happens once per +# receiving CopyData message from server. +# +# bin/pg_basebackup/pg_basebackup.c +leak:BaseBackup + +# `varname` doesn't get freed in the case of warning and `continue` in the loop +# +# bin/psql/common.c +leak:StoreQueryTuple + +# does not free a bunch of parsed flags +leak:bin/pg_waldump/pg_waldump.c #pg_dump leak:getSchemaData diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 0a9cb939a18e5..07ad317dec06d 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1076,6 +1076,10 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); +#ifdef FRONTEND + /* The backend frees it after copying to the cache. */ + pfree(principal_key); +#endif LWLockRelease(lock_pk); CloseTransientFile(fd); return wal_rec; @@ -1106,6 +1110,9 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) return_wal_rec = wal_rec; } } +#ifdef FRONTEND + pfree(principal_key); +#endif LWLockRelease(lock_pk); CloseTransientFile(fd); diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 80f5337066f1f..0f7b53164c1e2 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -562,6 +562,51 @@ check_provider_record(KeyringProviderRecord *provider_record) #endif /* !FRONTEND */ +void +free_keyring(GenericKeyring *keyring) +{ + FileKeyring *file = (FileKeyring *) keyring; + VaultV2Keyring *vault = (VaultV2Keyring *) keyring; + KmipKeyring *kmip = (KmipKeyring *) keyring; + + switch (keyring->type) + { + case FILE_KEY_PROVIDER: + if (file->file_name) + pfree(file->file_name); + break; + case VAULT_V2_KEY_PROVIDER: + if (vault->vault_ca_path) + pfree(vault->vault_ca_path); + if (vault->vault_mount_path) + pfree(vault->vault_mount_path); + if (vault->vault_token) + pfree(vault->vault_token); + if (vault->vault_token_path) + pfree(vault->vault_token_path); + if (vault->vault_url) + pfree(vault->vault_url); + break; + case KMIP_KEY_PROVIDER: + if (kmip->kmip_ca_path) + pfree(kmip->kmip_ca_path); + if (kmip->kmip_cert_path) + pfree(kmip->kmip_cert_path); + if (kmip->kmip_host) + pfree(kmip->kmip_host); + if (kmip->kmip_key_path) + pfree(kmip->kmip_key_path); + if (kmip->kmip_port) + pfree(kmip->kmip_port); + break; + default: + Assert(false); + break; + } + + pfree(keyring); +} + void write_key_provider_info(KeyringProviderRecordInFile *record, bool write_xlog) { @@ -682,6 +727,8 @@ simple_list_free(SimplePtrList *list) pfree(cell); cell = next; } + + pfree(list); } #endif /* FRONTEND */ @@ -818,6 +865,8 @@ load_file_keyring_provider_options(char *keyring_options) ereport(WARNING, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("file path is missing in the keyring options")); + + free_keyring((GenericKeyring *) file_keyring); return NULL; } @@ -845,6 +894,8 @@ load_vaultV2_keyring_provider_options(char *keyring_options) (vaultV2_keyring->vault_token_path != NULL && vaultV2_keyring->vault_token_path[0] != '\0') ? "" : " tokenPath", (vaultV2_keyring->vault_url != NULL && vaultV2_keyring->vault_url[0] != '\0') ? "" : " url", (vaultV2_keyring->vault_mount_path != NULL && vaultV2_keyring->vault_mount_path[0] != '\0') ? "" : " mountPath")); + + free_keyring((GenericKeyring *) vaultV2_keyring); return NULL; } @@ -878,6 +929,8 @@ load_kmip_keyring_provider_options(char *keyring_options) (kmip_keyring->kmip_ca_path != NULL && kmip_keyring->kmip_ca_path[0] != '\0') ? "" : " caPath", (kmip_keyring->kmip_cert_path != NULL && kmip_keyring->kmip_cert_path[0] != '\0') ? "" : " certPath", (kmip_keyring->kmip_key_path != NULL && kmip_keyring->kmip_key_path[0] != '\0') ? "" : " keyPath")); + + free_keyring((GenericKeyring *) kmip_keyring); return NULL; } @@ -946,7 +999,10 @@ debug_print_kerying(GenericKeyring *keyring) static inline void get_keyring_infofile_path(char *resPath, Oid dbOid) { - join_path_components(resPath, pg_tde_get_data_dir(), psprintf(PG_TDE_KEYRING_FILENAME, dbOid)); + char *fname = psprintf(PG_TDE_KEYRING_FILENAME, dbOid); + + join_path_components(resPath, pg_tde_get_data_dir(), fname); + pfree(fname); } static int diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index fcdfa5ea9a39b..48efa0056b7a1 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -960,7 +960,7 @@ get_principal_key_from_keyring(Oid dbOid) Assert(dbOid == principalKey->keyInfo.databaseId); pfree(keyInfo); - pfree(keyring); + free_keyring(keyring); pfree(principalKeyInfo); return principalKey; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index f7d99f735c890..f3177544cf551 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -81,7 +81,10 @@ extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocato static inline void pg_tde_set_db_file_path(Oid dbOid, char *path) { - join_path_components(path, pg_tde_get_data_dir(), psprintf(PG_TDE_MAP_FILENAME, dbOid)); + char *fname = psprintf(PG_TDE_MAP_FILENAME, dbOid); + + join_path_components(path, pg_tde_get_data_dir(), fname); + pfree(fname); } extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key); diff --git a/contrib/pg_tde/src/include/catalog/tde_keyring.h b/contrib/pg_tde/src/include/catalog/tde_keyring.h index db4a83ca02194..9aa7c279bf748 100644 --- a/contrib/pg_tde/src/include/catalog/tde_keyring.h +++ b/contrib/pg_tde/src/include/catalog/tde_keyring.h @@ -40,5 +40,6 @@ extern void redo_key_provider_info(KeyringProviderRecordInFile *xlrec); extern void ParseKeyringJSONOptions(ProviderType provider_type, GenericKeyring *out_opts, char *in_buf, int buf_len); +extern void free_keyring(GenericKeyring *keyring); #endif /* TDE_KEYRING_H */ diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 74ab07c6c7bd0..d3cad80da37b7 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -438,5 +438,6 @@ json_resp_object_field_start(void *state, char *fname, bool isnull) break; } + pfree(fname); return JSON_SUCCESS; } diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index c66eb874de1ed..e842200d5b6c1 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -249,6 +249,7 @@ main(int argc, char *argv[]) controlfile->state != DB_SHUTDOWNED_IN_RECOVERY) pg_fatal("cluster must be shut down"); + pfree(controlfile); cptr = strcat(cptr, datadir); cptr = strcat(cptr, "/"); cptr = strcat(cptr, PG_TDE_DATA_DIR); From 9d42b12df90ef9c866d6863e7d4345b61bfeb292 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 20 Jun 2025 17:25:19 +0200 Subject: [PATCH 511/796] PG-1661 Validate key coming from key providers Check that key that we retreived from key provider is valid. --- contrib/pg_tde/expected/key_provider.out | 6 +- .../pg_tde/src/catalog/tde_principal_key.c | 70 ++++++++++++----- .../pg_tde/src/include/keyring/keyring_api.h | 8 +- contrib/pg_tde/src/keyring/keyring_api.c | 67 +++++++++++++++- contrib/pg_tde/src/keyring/keyring_kmip.c | 2 +- contrib/pg_tde/src/keyring/keyring_vault.c | 4 +- contrib/pg_tde/t/expected/basic.out | 2 +- .../pg_tde/t/expected/change_key_provider.out | 4 +- contrib/pg_tde/t/expected/key_validation.out | 27 +++++++ contrib/pg_tde/t/key_validation.pl | 77 +++++++++++++++++++ 10 files changed, 236 insertions(+), 31 deletions(-) create mode 100644 contrib/pg_tde/t/expected/key_validation.out create mode 100644 contrib/pg_tde/t/key_validation.pl diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index e2a018c606fc0..5563df5aa4d9c 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -252,7 +252,7 @@ WARNING: The WAL encryption feature is currently in beta and may be unstable. D (1 row) SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2'); -ERROR: could not fetch key "server-key" used as server key from modified key provider "global-provider": 0 +ERROR: key "server-key" used as server key not found in modified key provider "global-provider" -- Modifying key providers fails if new settings can't fetch existing database key SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1'); pg_tde_add_global_key_provider_file @@ -279,7 +279,7 @@ SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider \c :regress_database SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2'); -ERROR: could not fetch key "database-key" used by database "db_using_global_provider" from modified key provider "global-provider2": 0 +ERROR: key "database-key" used by database "db_using_global_provider" not found in modified key provider "global-provider2" DROP DATABASE db_using_global_provider; CREATE DATABASE db_using_database_provider; \c db_using_database_provider; @@ -303,7 +303,7 @@ SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider') (1 row) SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2'); -ERROR: could not fetch key "database-key" used by database "db_using_database_provider" from modified key provider "db-provider": 0 +ERROR: key "database-key" used by database "db_using_database_provider" not found in modified key provider "db-provider" \c :regress_database DROP DATABASE db_using_database_provider; -- Deleting key providers fails if key name is NULL diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 48efa0056b7a1..38f962b4794f2 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -225,7 +225,7 @@ set_principal_key_with_keyring(const char *key_name, LWLock *lock_files = tde_lwlock_enc_keys(); bool already_has_key; GenericKeyring *new_keyring; - const KeyInfo *keyInfo = NULL; + KeyInfo *keyInfo = NULL; /* * Try to get principal key from cache. @@ -238,14 +238,15 @@ set_principal_key_with_keyring(const char *key_name, new_keyring = GetKeyProviderByName(provider_name, providerOid); { - KeyringReturnCode kr_ret; + KeyringReturnCode return_code; - keyInfo = KeyringGetKey(new_keyring, key_name, &kr_ret); + keyInfo = KeyringGetKey(new_keyring, key_name, &return_code); - if (kr_ret != KEYRING_CODE_SUCCESS) + if (return_code != KEYRING_CODE_SUCCESS) { ereport(ERROR, - errmsg("could not successfully query key provider \"%s\"", new_keyring->provider_name)); + errmsg("failed to retrieve principal key \"%s\" from key provider \"%s\"", key_name, new_keyring->provider_name), + errdetail("%s", KeyringErrorCodeToString(return_code))); } } @@ -289,6 +290,7 @@ set_principal_key_with_keyring(const char *key_name, LWLockRelease(lock_files); + pfree(keyInfo); pfree(new_keyring); pfree(new_principal_key); } @@ -303,7 +305,7 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) TDEPrincipalKey *new_principal_key; GenericKeyring *new_keyring; KeyInfo *keyInfo; - KeyringReturnCode kr_ret; + KeyringReturnCode return_code; LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); @@ -316,19 +318,20 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) } new_keyring = GetKeyProviderByID(xlrec->keyringId, xlrec->databaseId); - keyInfo = KeyringGetKey(new_keyring, xlrec->keyName, &kr_ret); + keyInfo = KeyringGetKey(new_keyring, xlrec->keyName, &return_code); - if (kr_ret != KEYRING_CODE_SUCCESS) + if (return_code != KEYRING_CODE_SUCCESS) { ereport(ERROR, - errmsg("failed to retrieve principal key from keyring provider: \"%s\"", new_keyring->provider_name), - errdetail("Error code: %d", kr_ret)); + errmsg("failed to retrieve principal key \"%s\" from key provider \"%s\"", xlrec->keyName, new_keyring->provider_name), + errdetail("%s", KeyringErrorCodeToString(return_code))); } /* The new key should be on keyring by this time */ if (keyInfo == NULL) { - ereport(ERROR, errmsg("failed to retrieve principal key from keyring for database %u.", xlrec->databaseId)); + ereport(ERROR, errmsg("failed to retrieve principal key \"%s\" from key provider \"%s\" for database %u", + xlrec->keyName, new_keyring->provider_name, xlrec->databaseId)); } new_principal_key = palloc_object(TDEPrincipalKey); @@ -347,6 +350,7 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) LWLockRelease(tde_lwlock_enc_keys()); + pfree(keyInfo); pfree(new_keyring); pfree(new_principal_key); } @@ -514,7 +518,8 @@ pg_tde_create_principal_key_internal(Oid providerOid, if (return_code != KEYRING_CODE_SUCCESS) ereport(ERROR, - errmsg("could not successfully query key provider \"%s\"", provider->provider_name)); + errmsg("failed to retrieve principal key \"%s\" from key provider \"%s\"", key_name, provider_name), + errdetail("%s", KeyringErrorCodeToString(return_code))); if (key_info != NULL) ereport(ERROR, @@ -939,11 +944,17 @@ get_principal_key_from_keyring(Oid dbOid) principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); keyInfo = KeyringGetKey(keyring, principalKeyInfo->data.name, &keyring_ret); + + if (keyring_ret != KEYRING_CODE_SUCCESS) + ereport(ERROR, + errmsg("failed to retrieve principal key \"%s\" from key provider \"%s\"", principalKeyInfo->data.name, keyring->provider_name), + errdetail("%s", KeyringErrorCodeToString(keyring_ret))); + if (keyInfo == NULL) ereport(ERROR, errcode(ERRCODE_NO_DATA_FOUND), - errmsg("failed to retrieve principal key %s from keyring with ID %d", - principalKeyInfo->data.name, principalKeyInfo->data.keyringId)); + errmsg("key \"%s\" not found in key provider \"%s\"", + principalKeyInfo->data.name, keyring->provider_name)); if (!pg_tde_verify_principal_key_info(principalKeyInfo, &keyInfo->data)) ereport(ERROR, @@ -1184,11 +1195,21 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) KeyInfo *proposed_key; proposed_key = KeyringGetKey(modified_provider, key_name, &return_code); + + if (return_code != KEYRING_CODE_SUCCESS) + { + ereport(ERROR, + errmsg("failed to retreive \"%s\" key used as server key from modified key provider \"%s\"", + key_name, modified_provider->provider_name), + errdetail("%s", KeyringErrorCodeToString(return_code))); + } + if (!proposed_key) { ereport(ERROR, - errmsg("could not fetch key \"%s\" used as server key from modified key provider \"%s\": %d", - key_name, modified_provider->provider_name, return_code)); + errcode(ERRCODE_NO_DATA_FOUND), + errmsg("key \"%s\" used as server key not found in modified key provider \"%s\"", + key_name, modified_provider->provider_name)); } if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) @@ -1197,6 +1218,8 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) errmsg("key \"%s\" from modified key provider \"%s\" does not match existing server key", key_name, modified_provider->provider_name)); } + + pfree(proposed_key); } if (existing_principal_key) @@ -1219,11 +1242,20 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) KeyInfo *proposed_key; proposed_key = KeyringGetKey(modified_provider, key_name, &return_code); + if (return_code != KEYRING_CODE_SUCCESS) + { + ereport(ERROR, + errmsg("failed to retreive \"%s\" key used by database \"%s\" from modified key provider \"%s\"", + key_name, database->datname.data, modified_provider->provider_name), + errdetail("%s", KeyringErrorCodeToString(return_code))); + } + if (!proposed_key) { ereport(ERROR, - errmsg("could not fetch key \"%s\" used by database \"%s\" from modified key provider \"%s\": %d", - key_name, database->datname.data, modified_provider->provider_name, return_code)); + errcode(ERRCODE_NO_DATA_FOUND), + errmsg("key \"%s\" used by database \"%s\" not found in modified key provider \"%s\"", + key_name, database->datname.data, modified_provider->provider_name)); } if (!pg_tde_verify_principal_key_info(existing_principal_key, &proposed_key->data)) @@ -1232,6 +1264,8 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) errmsg("key \"%s\" from modified key provider \"%s\" does not match existing key used by database \"%s\"", key_name, modified_provider->provider_name, database->datname.data)); } + + pfree(proposed_key); } if (existing_principal_key) diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index ab1add2dd8b20..8584386bfd37d 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -14,7 +14,9 @@ typedef enum ProviderType } ProviderType; #define TDE_KEY_NAME_LEN 256 -#define MAX_KEY_DATA_SIZE 32 /* maximum 256 bit encryption */ +#define KEY_DATA_SIZE_128 16 /* 128 bit encryption */ +#define KEY_DATA_SIZE_256 32 /* 256 bit encryption, not yet supported */ +#define MAX_KEY_DATA_SIZE KEY_DATA_SIZE_256 /* maximum 256 bit encryption */ #define INTERNAL_KEY_LEN 16 typedef struct KeyData @@ -35,7 +37,7 @@ typedef enum KeyringReturnCode KEYRING_CODE_INVALID_PROVIDER = 1, KEYRING_CODE_RESOURCE_NOT_AVAILABLE = 2, KEYRING_CODE_INVALID_RESPONSE = 5, - KEYRING_CODE_INVALID_KEY_SIZE = 6, + KEYRING_CODE_INVALID_KEY = 6, KEYRING_CODE_DATA_CORRUPTED = 7, } KeyringReturnCode; @@ -87,5 +89,7 @@ extern void RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderTy extern KeyInfo *KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCode *returnCode); extern KeyInfo *KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len); extern void KeyringValidate(GenericKeyring *keyring); +extern bool ValidateKey(KeyInfo *key); +extern char *KeyringErrorCodeToString(KeyringReturnCode code); #endif /* KEYRING_API_H */ diff --git a/contrib/pg_tde/src/keyring/keyring_api.c b/contrib/pg_tde/src/keyring/keyring_api.c index 2cd1aad6c6728..11da816681f4d 100644 --- a/contrib/pg_tde/src/keyring/keyring_api.c +++ b/contrib/pg_tde/src/keyring/keyring_api.c @@ -100,16 +100,57 @@ RegisterKeyProviderType(const TDEKeyringRoutine *routine, ProviderType type) KeyInfo * KeyringGetKey(GenericKeyring *keyring, const char *key_name, KeyringReturnCode *returnCode) { + KeyInfo *key = NULL; RegisteredKeyProviderType *kp = find_key_provider_type(keyring->type); if (kp == NULL) { ereport(WARNING, - errmsg("Key provider of type %d not registered", keyring->type)); + errmsg("key provider of type %d not registered", keyring->type)); *returnCode = KEYRING_CODE_INVALID_PROVIDER; return NULL; } - return kp->routine->keyring_get_key(keyring, key_name, returnCode); + key = kp->routine->keyring_get_key(keyring, key_name, returnCode); + + if (*returnCode != KEYRING_CODE_SUCCESS || key == NULL) + return NULL; + + if (!ValidateKey(key)) + { + *returnCode = KEYRING_CODE_INVALID_KEY; + pfree(key); + return NULL; + } + + return key; +} + +bool +ValidateKey(KeyInfo *key) +{ + Assert(key != NULL); + + if (key->name[0] == '\0') + { + ereport(WARNING, errmsg("invalid key: name is empty")); + return false; + } + + if (key->data.len == 0) + { + ereport(WARNING, errmsg("invalid key: data length is zero")); + return false; + } + + /* For now we only support 128-bit keys */ + if (key->data.len != KEY_DATA_SIZE_128) + { + ereport(WARNING, + errmsg("invalid key: unsupported key length \"%u\"", key->data.len)); + return false; + } + + return true; } static void @@ -163,3 +204,25 @@ KeyringValidate(GenericKeyring *keyring) kp->routine->keyring_validate(keyring); } + +char * +KeyringErrorCodeToString(KeyringReturnCode code) +{ + switch (code) + { + case KEYRING_CODE_SUCCESS: + return "Success"; + case KEYRING_CODE_INVALID_PROVIDER: + return "Invalid key"; + case KEYRING_CODE_RESOURCE_NOT_AVAILABLE: + return "Resource not available"; + case KEYRING_CODE_INVALID_RESPONSE: + return "Invalid response from keyring provider"; + case KEYRING_CODE_INVALID_KEY: + return "Invalid key"; + case KEYRING_CODE_DATA_CORRUPTED: + return "Data corrupted"; + default: + return "Unknown error code"; + } +} diff --git a/contrib/pg_tde/src/keyring/keyring_kmip.c b/contrib/pg_tde/src/keyring/keyring_kmip.c index 74c70fc52f27a..df10fd56ced62 100644 --- a/contrib/pg_tde/src/keyring/keyring_kmip.c +++ b/contrib/pg_tde/src/keyring/keyring_kmip.c @@ -178,7 +178,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (key->data.len > sizeof(key->data.data)) { ereport(WARNING, errmsg("keyring provider returned invalid key size: %d", key->data.len)); - *return_code = KEYRING_CODE_INVALID_KEY_SIZE; + *return_code = KEYRING_CODE_INVALID_KEY; pfree(key); BIO_free_all(ctx.bio); SSL_CTX_free(ctx.ssl); diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index d3cad80da37b7..2bda196e65ce1 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -220,7 +220,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (!curl_perform(vault_keyring, url, &str, &httpCode, NULL)) { - *return_code = KEYRING_CODE_INVALID_KEY_SIZE; + *return_code = KEYRING_CODE_INVALID_KEY; ereport(WARNING, errmsg("HTTP(S) request to keyring provider \"%s\" failed", vault_keyring->keyring.provider_name)); @@ -266,7 +266,7 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode if (key->data.len > MAX_KEY_DATA_SIZE) { - *return_code = KEYRING_CODE_INVALID_KEY_SIZE; + *return_code = KEYRING_CODE_INVALID_KEY; ereport(WARNING, errmsg("keyring provider \"%s\" returned invalid key size: %d", vault_keyring->keyring.provider_name, key->data.len)); diff --git a/contrib/pg_tde/t/expected/basic.out b/contrib/pg_tde/t/expected/basic.out index 466ecb62371a5..3947c0988f891 100644 --- a/contrib/pg_tde/t/expected/basic.out +++ b/contrib/pg_tde/t/expected/basic.out @@ -64,6 +64,6 @@ SELECT * FROM test_enc ORDER BY id; TABLEFILE FOUND: yes CONTAINS FOO (should be empty): SELECT pg_tde_verify_key() -psql::1: ERROR: failed to retrieve principal key test-db-key from keyring with ID 1 +psql::1: ERROR: key "test-db-key" not found in key provider "file-vault" DROP TABLE test_enc; DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/t/expected/change_key_provider.out b/contrib/pg_tde/t/expected/change_key_provider.out index e444fa7463e37..59de1281ae1a3 100644 --- a/contrib/pg_tde/t/expected/change_key_provider.out +++ b/contrib/pg_tde/t/expected/change_key_provider.out @@ -99,7 +99,7 @@ SELECT * FROM test_enc ORDER BY id; -- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_1.per -- server restart SELECT pg_tde_verify_key(); -psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +psql::1: ERROR: key "test-key" not found in key provider "file-vault" SELECT pg_tde_is_encrypted('test_enc'); pg_tde_is_encrypted --------------------- @@ -107,7 +107,7 @@ SELECT pg_tde_is_encrypted('test_enc'); (1 row) SELECT * FROM test_enc ORDER BY id; -psql::1: ERROR: failed to retrieve principal key test-key from keyring with ID 1 +psql::1: ERROR: key "test-key" not found in key provider "file-vault" SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_1.per'); pg_tde_change_database_key_provider_file ------------------------------------------ diff --git a/contrib/pg_tde/t/expected/key_validation.out b/contrib/pg_tde/t/expected/key_validation.out new file mode 100644 index 0000000000000..fe700ec77295e --- /dev/null +++ b/contrib/pg_tde/t/expected/key_validation.out @@ -0,0 +1,27 @@ +CREATE EXTENSION pg_tde; +SELECT pg_tde_add_database_key_provider_file('test-file-provider', '/tmp/pg_tde_test_key_validation.per'); + pg_tde_add_database_key_provider_file +--------------------------------------- + +(1 row) + +SELECT pg_tde_create_key_using_database_key_provider('key1', 'test-file-provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + +SELECT pg_tde_create_key_using_database_key_provider('key2', 'test-file-provider'); + pg_tde_create_key_using_database_key_provider +----------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_database_key_provider('key1', 'test-file-provider'); +psql::1: WARNING: invalid key: data length is zero +psql::1: ERROR: failed to retrieve principal key "key1" from key provider "test-file-provider" +DETAIL: Invalid key +SELECT pg_tde_set_key_using_database_key_provider('key2', 'test-file-provider'); +psql::1: WARNING: invalid key: unsupported key length "4294967295" +psql::1: ERROR: failed to retrieve principal key "key2" from key provider "test-file-provider" +DETAIL: Invalid key diff --git a/contrib/pg_tde/t/key_validation.pl b/contrib/pg_tde/t/key_validation.pl new file mode 100644 index 0000000000000..915d2e7bf5357 --- /dev/null +++ b/contrib/pg_tde/t/key_validation.pl @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use Fcntl 'SEEK_CUR'; +use Test::More; +use lib 't'; +use pgtde; + +PGTDE::setup_files_dir(basename($0)); + +unlink('/tmp/pg_tde_test_key_validation.per'); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); +$node->start; + +PGTDE::psql($node, 'postgres', 'CREATE EXTENSION pg_tde;'); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_add_database_key_provider_file('test-file-provider', '/tmp/pg_tde_test_key_validation.per');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('key1', 'test-file-provider');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_create_key_using_database_key_provider('key2', 'test-file-provider');" +); + + +corrupt_key_file('/tmp/pg_tde_test_key_validation.per'); + + +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('key1', 'test-file-provider');" +); +PGTDE::psql($node, 'postgres', + "SELECT pg_tde_set_key_using_database_key_provider('key2', 'test-file-provider');" +); + +sub corrupt_key_file +{ + my ($keyfile) = @_; + + my $fh; + open($fh, '+<', $keyfile) + or BAIL_OUT("open failed: $!"); + binmode $fh; + + # Corrupt the first page of the key file by zeroing key data length. + # Offset is TDE_KEY_NAME_LEN + MAX_KEY_DATA_SIZE. See keyring_api.h for details. + sysseek($fh, 256 + 32, 0) + or BAIL_OUT("sysseek failed: $!"); + syswrite($fh, pack("L*", 0x00000000)) or BAIL_OUT("syswrite failed: $!"); + + # Corrupt the second page of the key file by setting incorrect key length. + # Offset is TDE_KEY_NAME_LEN + MAX_KEY_DATA_SIZE. See keyring_api.h for details. + sysseek($fh, 256 + 32, SEEK_CUR) + or BAIL_OUT("sysseek failed: $!"); + syswrite($fh, pack("L*", 0xFFFFFFFF)) or BAIL_OUT("syswrite failed: $!"); + + + close($fh) + or BAIL_OUT("close failed: $!"); +} + +$node->stop; + +# Compare the expected and out file +my $compare = PGTDE->compare_results(); + +is($compare, 0, + "Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files." +); + +done_testing(); From 09b2af7344e1b02a10d0acde0846d288c89b3a7b Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Fri, 20 Jun 2025 19:08:53 +0200 Subject: [PATCH 512/796] Fix null dereference if vault key provider Key provider may return json without expected fields. Check key field for non-null value before processing. --- contrib/pg_tde/src/keyring/keyring_vault.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 2bda196e65ce1..6e77df52096e9 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -253,6 +253,15 @@ get_key_by_name(GenericKeyring *keyring, const char *key_name, KeyringReturnCode goto cleanup; } + if (parse.key == NULL) + { + *return_code = KEYRING_CODE_INVALID_RESPONSE; + ereport(WARNING, + errmsg("HTTP(S) request to keyring provider \"%s\" returned no key", + vault_keyring->keyring.provider_name)); + goto cleanup; + } + responseKey = parse.key; #if KEYRING_DEBUG From 9ea8dd6ddb717cf67ebb126c99b440016f1a9c46 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Wed, 2 Jul 2025 21:45:14 +0200 Subject: [PATCH 513/796] PG-1667 Validate vault key provider engine type pg_tde supports only Key/Value version 2 engine type for Hashicorp Vault. Add validation for that by quering mountpoint metadata. --- ci_scripts/setup-keyring-servers.sh | 4 + .../vault.md | 9 + contrib/pg_tde/expected/vault_v2_test.out | 29 +- contrib/pg_tde/sql/vault_v2_test.sql | 15 +- contrib/pg_tde/src/keyring/keyring_vault.c | 250 +++++++++++++++++- 5 files changed, 273 insertions(+), 34 deletions(-) diff --git a/ci_scripts/setup-keyring-servers.sh b/ci_scripts/setup-keyring-servers.sh index 4a9a5aba52ab2..1906422e43912 100755 --- a/ci_scripts/setup-keyring-servers.sh +++ b/ci_scripts/setup-keyring-servers.sh @@ -25,6 +25,10 @@ export VAULT_ROOT_TOKEN_FILE=$(mktemp) jq -r .root_token "$CLUSTER_INFO" > "$VAULT_ROOT_TOKEN_FILE" export VAULT_CACERT_FILE=$(jq -r .ca_cert_path "$CLUSTER_INFO") rm "$CLUSTER_INFO" + +## We need to enable key/value version 1 engine for just for tests +vault secrets enable -ca-cert="$VAULT_CACERT_FILE" -path=kv-v1 -version=1 kv + if [ -v GITHUB_ACTIONS ]; then echo "VAULT_ROOT_TOKEN_FILE=$VAULT_ROOT_TOKEN_FILE" >> $GITHUB_ENV echo "VAULT_CACERT_FILE=$VAULT_CACERT_FILE" >> $GITHUB_ENV diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index 93bbab47cb9d8..d60024a62e7c3 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -41,6 +41,15 @@ For more information on related functions, see the link below: [Percona pg_tde Function Reference](../functions.md){.md-button} +## Required permissions +`pg_tde` requires given permissions on listed Vault's API endpoints +* `sys/mounts/` - **read** permissions +* `/data/*` - **create**, **read** permissions +* `/metadata` - **list** permissions + +!!! note + For more information on Vault permissions, see the [following documentation](https://developer.hashicorp.com/vault/docs/concepts/policies). + ## Next steps [Global Principal Key Configuration :material-arrow-right:](set-principal-key.md){.md-button} diff --git a/contrib/pg_tde/expected/vault_v2_test.out b/contrib/pg_tde/expected/vault_v2_test.out index 550915f5e2c78..be25e0052e78c 100644 --- a/contrib/pg_tde/expected/vault_v2_test.out +++ b/contrib/pg_tde/expected/vault_v2_test.out @@ -1,22 +1,17 @@ CREATE EXTENSION pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); - pg_tde_add_database_key_provider_vault_v2 -------------------------------------------- - -(1 row) - --- FAILS -SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); -ERROR: Invalid HTTP response from keyring provider "vault-incorrect": 404 -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING tde_heap; -ERROR: principal key not configured -HINT: Use pg_tde_set_key_using_database_key_provider() or pg_tde_set_key_using_global_key_provider() to configure one. +-- FAILS as mount path does not exist +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-MOUNT-PATH', :'root_token_file', :'cacert_file'); +ERROR: failed to get mount info for "https://127.0.0.1:8200" at mountpoint "DUMMY-MOUNT-PATH" (HTTP 400) +-- FAILS as it's not supported engine type +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'cubbyhole', :'root_token_file', :'cacert_file'); +ERROR: vault mount at "cubbyhole" has unsupported engine type "cubbyhole" +HINT: The only supported vault engine type is Key/Value version "2" +-- FAILS as it's not supported engine version +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'kv-v1', :'root_token_file', :'cacert_file'); +ERROR: vault mount at "kv-v1" has unsupported Key/Value engine version "1" +HINT: The only supported vault engine type is Key/Value version "2" SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', :'cacert_file'); pg_tde_add_database_key_provider_vault_v2 ------------------------------------------- @@ -69,5 +64,5 @@ SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'https://127.0.0 ERROR: HTTP(S) request to keyring provider "vault-v2" failed -- HTTP against HTTPS server fails SELECT pg_tde_change_database_key_provider_vault_v2('vault-v2', 'http://127.0.0.1:8200', 'secret', :'root_token_file', NULL); -ERROR: Listing secrets of "http://127.0.0.1:8200" at mountpoint "secret" failed +ERROR: failed to get mount info for "http://127.0.0.1:8200" at mountpoint "secret" (HTTP 400) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/sql/vault_v2_test.sql b/contrib/pg_tde/sql/vault_v2_test.sql index d5ffde168426f..50fb17f07e812 100644 --- a/contrib/pg_tde/sql/vault_v2_test.sql +++ b/contrib/pg_tde/sql/vault_v2_test.sql @@ -3,15 +3,14 @@ CREATE EXTENSION pg_tde; \getenv root_token_file VAULT_ROOT_TOKEN_FILE \getenv cacert_file VAULT_CACERT_FILE -SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-TOKEN', :'root_token_file', :'cacert_file'); --- FAILS -SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-incorrect'); +-- FAILS as mount path does not exist +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'DUMMY-MOUNT-PATH', :'root_token_file', :'cacert_file'); -CREATE TABLE test_enc( - id SERIAL, - k INTEGER DEFAULT '0' NOT NULL, - PRIMARY KEY (id) - ) USING tde_heap; +-- FAILS as it's not supported engine type +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'cubbyhole', :'root_token_file', :'cacert_file'); + +-- FAILS as it's not supported engine version +SELECT pg_tde_add_database_key_provider_vault_v2('vault-incorrect', 'https://127.0.0.1:8200', 'kv-v1', :'root_token_file', :'cacert_file'); SELECT pg_tde_add_database_key_provider_vault_v2('vault-v2', 'https://127.0.0.1:8200', 'secret', :'root_token_file', :'cacert_file'); SELECT pg_tde_create_key_using_database_key_provider('vault-v2-key', 'vault-v2'); diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index 6e77df52096e9..b322cb328b100 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -33,6 +33,16 @@ typedef enum JRESP_EXPECT_KEY } JsonVaultRespSemState; +typedef enum +{ + JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD, + JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE, + JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE, + JRESP_MOUNT_INFO_EXPECT_OPTIONS_START, + JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD, +} JsonVaultRespMountInfoSemState; + + typedef enum { JRESP_F_UNUSED, @@ -49,12 +59,27 @@ typedef struct JsonVaultRespState char *key; } JsonVaultRespState; +typedef struct JsonVaultMountInfoState +{ + JsonVaultRespMountInfoSemState state; + int level; + + char *type; + char *version; +} JsonVaultMountInfoState; + static JsonParseErrorType json_resp_object_start(void *state); static JsonParseErrorType json_resp_object_end(void *state); static JsonParseErrorType json_resp_scalar(void *state, char *token, JsonTokenType tokentype); static JsonParseErrorType json_resp_object_field_start(void *state, char *fname, bool isnull); static JsonParseErrorType parse_json_response(JsonVaultRespState *parse, JsonLexContext *lex); +static JsonParseErrorType json_mountinfo_object_start(void *state); +static JsonParseErrorType json_mountinfo_object_end(void *state); +static JsonParseErrorType json_mountinfo_scalar(void *state, char *token, JsonTokenType tokentype); +static JsonParseErrorType json_mountinfo_object_field_start(void *state, char *fname, bool isnull); +static JsonParseErrorType parse_vault_mount_info(JsonVaultMountInfoState *state, JsonLexContext *lex); + static char *get_keyring_vault_url(VaultV2Keyring *keyring, const char *key_name, char *out, size_t out_size); static bool curl_perform(VaultV2Keyring *keyring, const char *url, CurlString *outStr, long *httpCode, const char *postData); @@ -299,38 +324,99 @@ validate(GenericKeyring *keyring) { VaultV2Keyring *vault_keyring = (VaultV2Keyring *) keyring; char url[VAULT_URL_MAX_LEN]; + int len = 0; CurlString str; long httpCode = 0; + JsonParseErrorType json_error; + JsonLexContext *jlex = NULL; + JsonVaultMountInfoState parse; /* - * Validate connection by listing available keys at the root level of the - * mount point + * Validate that the mount has the correct engine type and version. */ - snprintf(url, VAULT_URL_MAX_LEN, "%s/v1/%s/metadata/?list=true", - vault_keyring->vault_url, vault_keyring->vault_mount_path); + len = snprintf(url, VAULT_URL_MAX_LEN, "%s/v1/sys/mounts/%s", vault_keyring->vault_url, vault_keyring->vault_mount_path); + if (len >= VAULT_URL_MAX_LEN) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("vault mounts URL is too long")); + + if (!curl_perform(vault_keyring, url, &str, &httpCode, NULL)) + ereport(ERROR, + errmsg("HTTP(S) request to keyring provider \"%s\" failed", + vault_keyring->keyring.provider_name)); + + if (httpCode != 200) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("failed to get mount info for \"%s\" at mountpoint \"%s\" (HTTP %ld)", + vault_keyring->vault_url, vault_keyring->vault_mount_path, httpCode)); + + jlex = makeJsonLexContextCstringLen(NULL, str.ptr, str.len, PG_UTF8, true); + json_error = parse_vault_mount_info(&parse, jlex); + + if (json_error != JSON_SUCCESS) + ereport(ERROR, + errcode(ERRCODE_INVALID_JSON_TEXT), + errmsg("failed to parse mount info for \"%s\" at mountpoint \"%s\": %s", + vault_keyring->vault_url, vault_keyring->vault_mount_path, json_errdetail(json_error, jlex))); + + if (parse.type == NULL) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing type field", + vault_keyring->vault_url, vault_keyring->vault_mount_path)); + + if (strcmp(parse.type, "kv") != 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("vault mount at \"%s\" has unsupported engine type \"%s\"", + vault_keyring->vault_mount_path, parse.type), + errhint("The only supported vault engine type is Key/Value version \"2\"")); + + if (parse.version == NULL) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing version field", + vault_keyring->vault_url, vault_keyring->vault_mount_path)); + + if (strcmp(parse.version, "2") != 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("vault mount at \"%s\" has unsupported Key/Value engine version \"%s\"", + vault_keyring->vault_mount_path, parse.version), + errhint("The only supported vault engine type is Key/Value version \"2\"")); + + /* + * Validate that we can read the secrets at the mount point. + */ + len = snprintf(url, VAULT_URL_MAX_LEN, "%s/v1/%s/metadata/?list=true", + vault_keyring->vault_url, vault_keyring->vault_mount_path); + if (len >= VAULT_URL_MAX_LEN) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("vault metadata URL is too long")); if (!curl_perform(vault_keyring, url, &str, &httpCode, NULL)) - { ereport(ERROR, errmsg("HTTP(S) request to keyring provider \"%s\" failed", vault_keyring->keyring.provider_name)); - } /* If the mount point doesn't have any secrets yet, we'll get a 404. */ if (httpCode != 200 && httpCode != 404) - { ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Listing secrets of \"%s\" at mountpoint \"%s\" failed", vault_keyring->vault_url, vault_keyring->vault_mount_path)); - } if (str.ptr != NULL) pfree(str.ptr); + + if (jlex != NULL) + freeJsonLexContext(jlex); } /* - * JSON parser routines + * JSON parser routines for key response * * We expect the response in the form of: * { @@ -445,6 +531,152 @@ json_resp_object_field_start(void *state, char *fname, bool isnull) if (strcmp(fname, "key") == 0 && parse->level == 2) parse->field = JRESP_F_KEY; break; + default: + /* NOP */ + break; + } + + return JSON_SUCCESS; +} + +/* + * JSON parser routines for mount info + * + * We expect the response in the form of: + * { + * ... + * "type": "kv", + * "options": { + * "version": "2" + * } + * ... + * } + * + * the rest fields are ignored + */ + +static JsonParseErrorType +parse_vault_mount_info(JsonVaultMountInfoState *state, JsonLexContext *lex) +{ + JsonSemAction sem; + + state->state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD; + state->type = NULL; + state->version = NULL; + state->level = -1; + + memset(&sem, 0, sizeof(sem)); + sem.semstate = state; + sem.object_start = json_mountinfo_object_start; + sem.object_end = json_mountinfo_object_end; + sem.scalar = json_mountinfo_scalar; + sem.object_field_start = json_mountinfo_object_field_start; + + return pg_parse_json(lex, &sem); +} + +static JsonParseErrorType +json_mountinfo_object_start(void *state) +{ + JsonVaultMountInfoState *parse = (JsonVaultMountInfoState *) state; + + switch (parse->state) + { + case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START: + parse->state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD; + break; + default: + /* NOP */ + break; + } + + parse->level++; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_mountinfo_object_end(void *state) +{ + JsonVaultMountInfoState *parse = (JsonVaultMountInfoState *) state; + + if (parse->state == JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD) + parse->state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD; + + parse->level--; + + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_mountinfo_scalar(void *state, char *token, JsonTokenType tokentype) +{ + JsonVaultMountInfoState *parse = (JsonVaultMountInfoState *) state; + + switch (parse->state) + { + case JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE: + parse->type = token; + parse->state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD; + break; + case JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE: + parse->version = token; + parse->state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD; + break; + case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START: + + /* + * Reset "options" object expectations if we got scalar. Most + * likely just a null. + */ + parse->state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD; + break; + default: + /* NOP */ + break; + } + + return JSON_SUCCESS; +} + +static JsonParseErrorType +json_mountinfo_object_field_start(void *state, char *fname, bool isnull) +{ + JsonVaultMountInfoState *parse = (JsonVaultMountInfoState *) state; + + switch (parse->state) + { + case JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD: + if (parse->level == 0) + { + if (strcmp(fname, "type") == 0) + { + parse->state = JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE; + break; + } + + if (strcmp(fname, "options") == 0) + { + parse->state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_START; + break; + } + } + break; + + case JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD: + if (parse->level == 1) + { + if (strcmp(fname, "version") == 0) + { + parse->state = JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE; + break; + } + } + break; + + default: + /* NOP */ + break; } pfree(fname); From 2805fa898b94fe06bf37d6be8869e9b0904f1c99 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 9 Jul 2025 13:09:15 +0200 Subject: [PATCH 514/796] PG-1443 Make pg_tde_change_key_provider CLI follow conventions The pg_tde_change_key_provider tool should act more like PostgreSQL's own CLI tools, which includes changing the usage slightly (but not entirely) to match, making the error messages more similar to PostgreSQL's and making the code a bit more PG-like. --- .../pg_tde/src/pg_tde_change_key_provider.c | 169 ++++++++---------- .../pg_tde/t/pg_tde_change_key_provider.pl | 10 +- 2 files changed, 84 insertions(+), 95 deletions(-) diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/pg_tde_change_key_provider.c index e842200d5b6c1..58db875f0ac27 100644 --- a/contrib/pg_tde/src/pg_tde_change_key_provider.c +++ b/contrib/pg_tde/src/pg_tde_change_key_provider.c @@ -5,37 +5,29 @@ #include "common/controldata_utils.h" #include "common/logging.h" +#include "pg_getopt.h" #include "catalog/tde_global_space.h" #include "catalog/tde_keyring.h" #include "common/pg_tde_utils.h" #include "pg_tde.h" -/* version string we expect back from pg_tde_change_key_provider */ -#define PROGNAME "pg_tde_change_key_provider (PostgreSQL) " PG_VERSION "\n" +static const char *progname; static void -help(void) +usage(void) { - puts("pg_tde_change_key_provider changes the configuration of a pg_tde key provider"); - puts(""); - puts("Usage:"); - puts(""); - puts("pg_tde_change_key_provider [-D ] "); - puts(""); - puts(" Where can be file, vault-v2 or kmip"); - puts(""); - puts("Depending on the provider type, the complete parameter list is:"); - puts(""); - puts("pg_tde_change_key_provider [-D ] file "); - puts("pg_tde_change_key_provider [-D ] vault-v2 []"); - puts("pg_tde_change_key_provider [-D ] kmip []"); - puts(""); - printf("Use dbOid %d for global key providers.\n", GLOBAL_DATA_TDE_OID); - puts(""); - puts("WARNING:"); - puts(""); - puts("This tool only changes the values, without properly XLogging the changes, or validating that keys can be fetched using them. Only use it in case the database is inaccessible and can't be started.\n"); + printf(_("%s changes the configuration of a pg_tde key provider\n\n"), progname); + printf(_("Usage:\n")); + printf(_(" %s [-D ] \n\n"), progname); + printf(_(" Where can be file, vault-v2 or kmip\n\n")); + printf(_("Depending on the provider type, the complete parameter list is:\n\n")); + printf(_("pg_tde_change_key_provider [-D ] file \n")); + printf(_("pg_tde_change_key_provider [-D ] vault-v2 []\n")); + printf(_("pg_tde_change_key_provider [-D ] kmip []\n")); + printf(_("\nUse dbOid %d for global key providers.\n\n"), GLOBAL_DATA_TDE_OID); + printf(_("WARNING:\n")); + printf(_(" This tool only changes the values, without properly XLogging the changes, or validating that keys can be fetched using them. Only use it in case the database is inaccessible and can't be started.\n")); } #define BUFFER_SIZE 1024 @@ -84,7 +76,7 @@ build_json(char *buffer, int count,...) } if (ptr - buffer > BUFFER_SIZE) { - fprintf(stderr, "Error: Configuration too long.\n"); + pg_log_error("configuration too long"); return false; } } @@ -94,7 +86,7 @@ build_json(char *buffer, int count,...) if (ptr - buffer > BUFFER_SIZE) { - fprintf(stderr, "Error: Configuration too long.\n"); + pg_log_error("configuration too long"); return false; } @@ -104,31 +96,33 @@ build_json(char *buffer, int count,...) int main(int argc, char *argv[]) { + static struct option long_options[] = { + {"pgdata", required_argument, NULL, 'D'}, + {NULL, 0, NULL, 0} + }; + + int c; + int option_index; + char *datadir = NULL; + Oid db_oid; char *provider_name; char *new_provider_type; - char *datadir = getenv("PGDATA"); - - int argstart = 0; - - char json[BUFFER_SIZE * 2] = {0,}; + char json[BUFFER_SIZE * 2] = {0}; ControlFileData *controlfile; bool crc_ok; - char tdedir[MAXPGPATH] = {0,}; + char tdedir[MAXPGPATH] = {0}; char *cptr = tdedir; - bool provider_found = false; KeyringProviderRecordInFile record; - Oid db_oid; - pg_logging_init(argv[0]); - pg_logging_set_level(PG_LOG_WARNING); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_tde_change_key_provider")); + progname = get_progname(argv[0]); if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { - help(); + usage(); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) @@ -138,101 +132,99 @@ main(int argc, char *argv[]) } } - if (argc > 3) + while ((c = getopt_long(argc, argv, "D:", long_options, &option_index)) != -1) { - if (strcmp(argv[1], "-D") == 0) + switch (c) { - datadir = argv[2]; + case 'D': + datadir = optarg; + break; + default: + /* getopt_long already emitted a complaint */ + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); } - argstart += 2; } - if (datadir == NULL || strlen(datadir) == 0) + if (datadir == NULL) { - help(); - puts("\n"); - fprintf(stderr, "Error: Data directory missing.\n"); - exit(1); + datadir = getenv("PGDATA"); + + /* If no datadir was specified, and none could be found, error out */ + if (datadir == NULL) + { + pg_log_error("no data directory specified"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } } - if (argc - argstart <= 3) + if (argc - optind < 3) { - help(); + pg_log_error("too few arguments"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1); } - db_oid = atoi(argv[1 + argstart]); - provider_name = argv[2 + argstart]; - new_provider_type = argv[3 + argstart]; + db_oid = atoi(argv[optind++]); + provider_name = argv[optind++]; + new_provider_type = argv[optind++]; if (strcmp("file", new_provider_type) == 0) { - provider_found = true; - - if (argc - argstart != 5) + if (argc - optind != 1) { - help(); - puts("\n"); - fprintf(stderr, "Error: wrong number of arguments.\n"); + pg_log_error("wrong number of arguments for \"%s\"", new_provider_type); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1); } - if (!build_json(json, 1, "path", argv[4 + argstart])) + if (!build_json(json, 1, "path", argv[optind])) { exit(1); } } - - if (strcmp("vault-v2", new_provider_type) == 0) + else if (strcmp("vault-v2", new_provider_type) == 0) { - provider_found = true; - - if (argc - argstart != 7 && argc - argstart != 8) + if (argc - optind != 3 && argc - optind != 4) { - help(); - puts("\n"); - fprintf(stderr, "Error: wrong number of arguments.\n"); + pg_log_error("wrong number of arguments for \"%s\"", new_provider_type); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1); } if (!build_json(json, 4, - "url", argv[4 + argstart], - "mountPath", argv[5 + argstart], - "tokenPath", argv[6 + argstart], - "caPath", (argc - argstart > 7 ? argv[7 + argstart] : ""))) + "url", argv[optind], + "mountPath", argv[optind + 1], + "tokenPath", argv[optind + 2], + "caPath", (argc - optind > 3 ? argv[optind + 3] : ""))) { exit(1); } } - - if (strcmp("kmip", new_provider_type) == 0) + else if (strcmp("kmip", new_provider_type) == 0) { - provider_found = true; - - if (argc - argstart != 8 && argc - argstart != 9) + if (argc - optind != 4 && argc - optind != 5) { - help(); - puts("\n"); - fprintf(stderr, "Error: wrong number of arguments.\n"); + pg_log_error("wrong number of arguments for \"%s\"", new_provider_type); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1); } if (!build_json(json, 5, - "host", argv[4 + argstart], - "port", argv[5 + argstart], - "certPath", argv[6 + argstart], - "keyPath", argv[7 + argstart], - "caPath", argc - argstart > 8 ? argv[8 + argstart] : "")) + "host", argv[optind], + "port", argv[optind + 1], + "certPath", argv[optind + 2], + "keyPath", argv[optind + 3], + "caPath", argc - optind > 4 ? argv[optind + 4] : "")) { exit(1); } } - - if (!provider_found) + else { - help(); - puts("\n"); - fprintf(stderr, "Error: Unknown provider type: %s\n.", new_provider_type); + pg_log_error("unknown provider type \"%s\"", new_provider_type); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1); } @@ -256,10 +248,7 @@ main(int argc, char *argv[]) pg_tde_set_data_dir(tdedir); if (get_keyring_info_file_record_by_name(provider_name, db_oid, &record) == false) - { - fprintf(stderr, "Error: provider not found\n."); - exit(1); - } + pg_fatal("provder \"%s\" not found for database %u", provider_name, db_oid); record.provider.provider_type = get_keyring_provider_from_typename(new_provider_type); memset(record.provider.options, 0, sizeof(record.provider.options)); diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index c16f6c3591ba2..9cd2746a80e92 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -263,7 +263,7 @@ 'file', '/tmp/file', ], - qr/Error: provider not found/, + qr/error: provder "incorrect-global-provider" not found for database 1664/, 'gives error on unknown key provider'); command_fails_like( @@ -274,7 +274,7 @@ 'global-provider', 'incorrect-provider-type', ], - qr/Error: Unknown provider type: incorrect-provider-type/, + qr/error: unknown provider type "incorrect-provider-type"/, 'gives error on unknown provider type'); command_fails_like( @@ -285,7 +285,7 @@ 'global-provider', 'file', ], - qr/Error: wrong number of arguments./, + qr/error: wrong number of arguments for "file"/, 'gives error on missing arguments for file provider'); command_fails_like( @@ -296,7 +296,7 @@ 'global-provider', 'kmip', ], - qr/Error: wrong number of arguments./, + qr/error: wrong number of arguments for "kmip"/, 'gives error on missing arguments for kmip provider'); command_fails_like( @@ -307,7 +307,7 @@ 'global-provider', 'vault-v2', ], - qr/Error: wrong number of arguments./, + qr/error: wrong number of arguments for "vault-v2"/, 'gives error on missing arguments for vault-v2 provider'); done_testing(); From 9cddf8d6b15da566aa46e86db0bbb14323d96caa Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 15 Jul 2025 08:29:48 +0100 Subject: [PATCH 515/796] Upload logs and datadirs on stormweaver failure Currently this is missing, and we don't have detailed information about the run/failure. --- .github/workflows/stormweaver.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/stormweaver.yml b/.github/workflows/stormweaver.yml index 779723740129c..89910baa11ceb 100644 --- a/.github/workflows/stormweaver.yml +++ b/.github/workflows/stormweaver.yml @@ -52,3 +52,21 @@ jobs: - name: Run Stormweaver run: bin/stormweaver scenarios/basic.lua -i ../../pginst working-directory: stormweaver + + - name: Upload logs on test fail + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: stormweaver-logs + path: | + stormweaver/logs/* + retention-days: 3 + + - name: Upload datadir on test fail + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: stormweaver-datadirs + path: | + stormweaver/datadirs/* + retention-days: 3 From 54d012893d582f471b8b3fd532832bb1bac60aee Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 15 Jul 2025 10:27:42 +0100 Subject: [PATCH 516/796] Fix pg installation reference in stormweaver run The path had an additional "../" section, which means it referenced outside the action working directory. For some reason (possibly earlier scripts during development?) we had an existing postgres installation there, but it was old and never updated. As it still had bugs in the partition handling code, it always reported an error. --- .github/workflows/stormweaver.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stormweaver.yml b/.github/workflows/stormweaver.yml index 89910baa11ceb..f66ca0b3a6c39 100644 --- a/.github/workflows/stormweaver.yml +++ b/.github/workflows/stormweaver.yml @@ -50,7 +50,7 @@ jobs: working-directory: postgres - name: Run Stormweaver - run: bin/stormweaver scenarios/basic.lua -i ../../pginst + run: bin/stormweaver scenarios/basic.lua -i ../pginst working-directory: stormweaver - name: Upload logs on test fail From 6e3a607fd51433c6f821ec70a70fe4c9bfe50390 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 16 Jul 2025 11:59:53 +0200 Subject: [PATCH 517/796] Make sure to always use unqiue key provider file in tests Some of the code in the replication tests used the wrong file. --- contrib/pg_tde/t/expected/replication.out | 2 +- contrib/pg_tde/t/replication.pl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index 679adc1df335c..fa8ffa53d3d28 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -62,7 +62,7 @@ SELECT * FROM test_plain ORDER BY x; (2 rows) -- check primary crash with WAL encryption -SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per'); +SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/replication.per'); pg_tde_add_global_key_provider_file ------------------------------------- diff --git a/contrib/pg_tde/t/replication.pl b/contrib/pg_tde/t/replication.pl index 14c3af1c2f2f2..3545ee9111fb8 100644 --- a/contrib/pg_tde/t/replication.pl +++ b/contrib/pg_tde/t/replication.pl @@ -66,7 +66,7 @@ PGTDE::append_to_result_file("-- check primary crash with WAL encryption"); PGTDE::psql($primary, 'postgres', - "SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/unlogged_tables.per');" + "SELECT pg_tde_add_global_key_provider_file('file-vault', '/tmp/replication.per');" ); PGTDE::psql($primary, 'postgres', "SELECT pg_tde_create_key_using_global_key_provider('test-global-key', 'file-vault');" From c9a8ebb0ec46edaab53324a27dbf379916a3615b Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Jul 2025 18:41:33 +0300 Subject: [PATCH 518/796] Make pg_rewind work with encrypted WAL --- src/bin/pg_rewind/Makefile | 10 ++++++++++ src/bin/pg_rewind/meson.build | 10 ++++++++++ src/bin/pg_rewind/parsexlog.c | 10 ++++++++++ src/bin/pg_rewind/pg_rewind.c | 15 +++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile index 12b138b2f2ce3..f03de034ab1d8 100644 --- a/src/bin/pg_rewind/Makefile +++ b/src/bin/pg_rewind/Makefile @@ -30,6 +30,16 @@ OBJS = \ timeline.o \ xlogreader.o +ifeq ($(enable_percona_ext),yes) + +OBJS += \ + $(top_srcdir)/src/fe_utils/simple_list.o \ + $(top_builddir)/src/libtde/libtdexlog.a \ + $(top_builddir)/src/libtde/libtde.a + +override CPPFLAGS := -I$(top_srcdir)/contrib/pg_tde/src/include -I$(top_srcdir)/contrib/pg_tde/src/libkmip/libkmip/include $(CPPFLAGS) +endif + EXTRA_CLEAN = xlogreader.c all: pg_rewind diff --git a/src/bin/pg_rewind/meson.build b/src/bin/pg_rewind/meson.build index 200ebf84eb9e1..4b56f4ac71f1e 100644 --- a/src/bin/pg_rewind/meson.build +++ b/src/bin/pg_rewind/meson.build @@ -19,11 +19,21 @@ if host_system == 'windows' '--FILEDESC', 'pg_rewind - synchronize a data directory with another one forked from']) endif +link_w = [] +include_dirs = [postgres_inc] + +if percona_ext == true + link_w = [pg_tde_frontend] + include_dirs = [postgres_inc, pg_tde_inc] +endif + pg_rewind = executable('pg_rewind', pg_rewind_sources, dependencies: [frontend_code, libpq, lz4, zstd], c_args: ['-DFRONTEND'], # needed for xlogreader et al kwargs: default_bin_args, + include_directories: include_dirs, + link_with: link_w, ) bin_targets += pg_rewind diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 242326c97a70e..fd37927e253d4 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -23,6 +23,10 @@ #include "fe_utils/archive.h" #include "filemap.h" #include "pg_rewind.h" +#ifdef PERCONA_EXT +#include "access/pg_tde_xlog_smgr.h" +#include "access/xlog_smgr.h" +#endif /* * RmgrNames is an array of the built-in resource manager names, to make error @@ -357,6 +361,11 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, Assert(xlogreadfd != -1); /* Read the requested page */ +#ifdef PERCONA_EXT + r = xlog_smgr->seg_read(xlogreadfd, readBuf, XLOG_BLCKSZ, (off_t) targetPageOff, + targetHistory[private->tliIndex].tli, + xlogreadsegno, WalSegSz); +#else if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0) { pg_log_error("could not seek in file \"%s\": %m", xlogfpath); @@ -365,6 +374,7 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, r = read(xlogreadfd, readBuf, XLOG_BLCKSZ); +#endif if (r != XLOG_BLCKSZ) { if (r < 0) diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 53eb49abdeaf6..f6726c1df485c 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -31,6 +31,12 @@ #include "pg_rewind.h" #include "rewind_source.h" #include "storage/bufpage.h" +#ifdef PERCONA_EXT +#include "pg_tde.h" +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" +#include "catalog/tde_global_space.h" +#endif static void usage(const char *progname); @@ -364,6 +370,15 @@ main(int argc, char **argv) target_tli = Max(ControlFile_target.minRecoveryPointTLI, ControlFile_target.checkPointCopy.ThisTimeLineID); +#ifdef PERCONA_EXT + { + /* TDOD: tde_path setup should be moved to the pg_tde side? */ + char tde_path[MAXPGPATH]; + snprintf(tde_path, sizeof(tde_path), "%s/%s", datadir_target, PG_TDE_DATA_DIR); + pg_tde_fe_init(tde_path); + TDEXLogSmgrInit(); + } +#endif /* * Find the common ancestor timeline between the clusters. * From 0c3afd71135911e4045fc9f7e47e6a5caf231beb Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Jul 2025 18:19:40 +0300 Subject: [PATCH 519/796] Add pg_rewind TAP tests These tests are a carbon copy of upstream's pg_rewind tests. Changes for WAL encryption will be added in the following commit. `standby_source` test is not copied as it fails because of changes needed in pg_basebackup. --- contrib/pg_tde/meson.build | 9 + contrib/pg_tde/t/RewindTest.pm | 386 ++++++++++++++++++ contrib/pg_tde/t/pg_rewind_basic.pl | 214 ++++++++++ contrib/pg_tde/t/pg_rewind_databases.pl | 77 ++++ contrib/pg_tde/t/pg_rewind_extrafiles.pl | 124 ++++++ contrib/pg_tde/t/pg_rewind_growing_files.pl | 77 ++++ .../pg_tde/t/pg_rewind_keep_recycled_wals.pl | 62 +++ .../pg_tde/t/pg_rewind_min_recovery_point.pl | 168 ++++++++ contrib/pg_tde/t/pg_rewind_options.pl | 45 ++ contrib/pg_tde/t/pg_rewind_pg_xlog_symlink.pl | 80 ++++ contrib/pg_tde/t/pg_rewind_same_timeline.pl | 24 ++ 11 files changed, 1266 insertions(+) create mode 100644 contrib/pg_tde/t/RewindTest.pm create mode 100644 contrib/pg_tde/t/pg_rewind_basic.pl create mode 100644 contrib/pg_tde/t/pg_rewind_databases.pl create mode 100644 contrib/pg_tde/t/pg_rewind_extrafiles.pl create mode 100644 contrib/pg_tde/t/pg_rewind_growing_files.pl create mode 100644 contrib/pg_tde/t/pg_rewind_keep_recycled_wals.pl create mode 100644 contrib/pg_tde/t/pg_rewind_min_recovery_point.pl create mode 100644 contrib/pg_tde/t/pg_rewind_options.pl create mode 100644 contrib/pg_tde/t/pg_rewind_pg_xlog_symlink.pl create mode 100644 contrib/pg_tde/t/pg_rewind_same_timeline.pl diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 7429c08420f93..42a6bbe6afff3 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -107,6 +107,15 @@ tap_tests = [ 't/key_rotate_tablespace.pl', 't/multiple_extensions.pl', 't/pg_tde_change_key_provider.pl', + 't/pg_rewind_basic.pl', + 't/pg_rewind_databases.pl', + 't/pg_rewind_extrafiles.pl', + 't/pg_rewind_growing_files.pl', + 't/pg_rewind_keep_recycled_wals.pl', + 't/pg_rewind_min_recovery_point.pl', + 't/pg_rewind_options.pl', + 't/pg_rewind_pg_xlog_symlink.pl', + 't/pg_rewind_same_timeline.pl', 't/pg_waldump_basic.pl', 't/pg_waldump_fullpage.pl', 't/replication.pl', diff --git a/contrib/pg_tde/t/RewindTest.pm b/contrib/pg_tde/t/RewindTest.pm new file mode 100644 index 0000000000000..0bf59db997359 --- /dev/null +++ b/contrib/pg_tde/t/RewindTest.pm @@ -0,0 +1,386 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +package RewindTest; + +# Test driver for pg_rewind. Each test consists of a cycle where a new cluster +# is first created with initdb, and a streaming replication standby is set up +# to follow the primary. Then the primary is shut down and the standby is +# promoted, and finally pg_rewind is used to rewind the old primary, using the +# standby as the source. +# +# To run a test, the test script (in t/ subdirectory) calls the functions +# in this module. These functions should be called in this sequence: +# +# 1. setup_cluster - creates a PostgreSQL cluster that runs as the primary +# +# 2. start_primary - starts the primary server +# +# 3. create_standby - runs pg_basebackup to initialize a standby server, and +# sets it up to follow the primary. +# +# 4. promote_standby - runs "pg_ctl promote" to promote the standby server. +# The old primary keeps running. +# +# 5. run_pg_rewind - stops the old primary (if it's still running) and runs +# pg_rewind to synchronize it with the now-promoted standby server. +# +# 6. clean_rewind_test - stops both servers used in the test, if they're +# still running. +# +# The test script can use the helper functions primary_psql and standby_psql +# to run psql against the primary and standby servers, respectively. + +use strict; +use warnings FATAL => 'all'; + +use Carp; +use Exporter 'import'; +use File::Copy; +use File::Path qw(rmtree); +use IPC::Run qw(run); +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::RecursiveCopy; +use PostgreSQL::Test::Utils; +use Test::More; + +our @EXPORT = qw( + $node_primary + $node_standby + + primary_psql + standby_psql + check_query + + setup_cluster + start_primary + create_standby + promote_standby + run_pg_rewind + clean_rewind_test +); + +# Our nodes. +our $node_primary; +our $node_standby; + +sub primary_psql +{ + my $cmd = shift; + my $dbname = shift || 'postgres'; + + system_or_bail 'psql', '-q', '--no-psqlrc', '-d', + $node_primary->connstr($dbname), '-c', "$cmd"; + return; +} + +sub standby_psql +{ + my $cmd = shift; + my $dbname = shift || 'postgres'; + + system_or_bail 'psql', '-q', '--no-psqlrc', '-d', + $node_standby->connstr($dbname), '-c', "$cmd"; + return; +} + +# Run a query against the primary, and check that the output matches what's +# expected +sub check_query +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($query, $expected_stdout, $test_name) = @_; + my ($stdout, $stderr); + + # we want just the output, no formatting + my $result = run [ + 'psql', '-q', '-A', '-t', '--no-psqlrc', '-d', + $node_primary->connstr('postgres'), + '-c', $query + ], + '>', \$stdout, '2>', \$stderr; + + is($result, 1, "$test_name: psql exit code"); + is($stderr, '', "$test_name: psql no stderr"); + is($stdout, $expected_stdout, "$test_name: query result matches"); + + return; +} + +sub setup_cluster +{ + my $extra_name = shift; # Used to differentiate clusters + my $extra = shift; # Extra params for initdb + + # Initialize primary, data checksums are mandatory + $node_primary = + PostgreSQL::Test::Cluster->new( + 'primary' . ($extra_name ? "_${extra_name}" : '')); + + # Set up pg_hba.conf and pg_ident.conf for the role running + # pg_rewind. This role is used for all the tests, and has + # minimal permissions enough to rewind from an online source. + $node_primary->init( + allows_streaming => 1, + extra => $extra, + auth_extra => [ '--create-role', 'rewind_user' ]); + + # Set wal_keep_size to prevent WAL segment recycling after enforced + # checkpoints in the tests. + $node_primary->append_conf( + 'postgresql.conf', qq( +wal_keep_size = 320MB +allow_in_place_tablespaces = on +)); + return; +} + +sub start_primary +{ + $node_primary->start; + + # Create custom role which is used to run pg_rewind, and adjust its + # permissions to the minimum necessary. + $node_primary->safe_psql( + 'postgres', " + CREATE ROLE rewind_user LOGIN; + GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean) + TO rewind_user; + GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) + TO rewind_user; + GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) + TO rewind_user; + GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean) + TO rewind_user;"); + + #### Now run the test-specific parts to initialize the primary before setting + # up standby + + return; +} + +sub create_standby +{ + my $extra_name = shift; + + $node_standby = + PostgreSQL::Test::Cluster->new( + 'standby' . ($extra_name ? "_${extra_name}" : '')); + $node_primary->backup('my_backup'); + $node_standby->init_from_backup($node_primary, 'my_backup'); + my $connstr_primary = $node_primary->connstr(); + + $node_standby->append_conf( + "postgresql.conf", qq( +primary_conninfo='$connstr_primary' +)); + + $node_standby->set_standby_mode(); + + # Start standby + $node_standby->start; + + # The standby may have WAL to apply before it matches the primary. That + # is fine, because no test examines the standby before promotion. + + return; +} + +sub promote_standby +{ + #### Now run the test-specific parts to run after standby has been started + # up standby + + # Wait for the standby to receive and write all WAL. + $node_primary->wait_for_catchup($node_standby, 'write'); + + # Now promote standby and insert some new data on primary, this will put + # the primary out-of-sync with the standby. + $node_standby->promote; + + return; +} + +sub run_pg_rewind +{ + my $test_mode = shift; + my $primary_pgdata = $node_primary->data_dir; + my $standby_pgdata = $node_standby->data_dir; + my $standby_connstr = $node_standby->connstr('postgres'); + my $tmp_folder = PostgreSQL::Test::Utils::tempdir; + + # Append the rewind-specific role to the connection string. + $standby_connstr = "$standby_connstr user=rewind_user"; + + if ($test_mode eq 'archive') + { + # pg_rewind is tested with --restore-target-wal by moving all + # WAL files to a secondary location. Note that this leads to + # a failure in ensureCleanShutdown(), forcing to the use of + # --no-ensure-shutdown in this mode as the initial set of WAL + # files needed to ensure a clean restart is gone. This could + # be improved by keeping around only a minimum set of WAL + # segments but that would just make the test more costly, + # without improving the coverage. Hence, instead, stop + # gracefully the primary here. + $node_primary->stop; + } + else + { + # Stop the primary and be ready to perform the rewind. The cluster + # needs recovery to finish once, and pg_rewind makes sure that it + # happens automatically. + $node_primary->stop('immediate'); + } + + # At this point, the rewind processing is ready to run. + # We now have a very simple scenario with a few diverged WAL record. + # The real testing begins really now with a bifurcation of the possible + # scenarios that pg_rewind supports. + + # Keep a temporary postgresql.conf for primary node or it would be + # overwritten during the rewind. + copy( + "$primary_pgdata/postgresql.conf", + "$tmp_folder/primary-postgresql.conf.tmp"); + + # Now run pg_rewind + if ($test_mode eq "local") + { + + # Do rewind using a local pgdata as source + # Stop the primary and be ready to perform the rewind + $node_standby->stop; + command_ok( + [ + 'pg_rewind', + "--debug", + "--source-pgdata=$standby_pgdata", + "--target-pgdata=$primary_pgdata", + "--no-sync", + "--config-file", + "$tmp_folder/primary-postgresql.conf.tmp" + ], + 'pg_rewind local'); + } + elsif ($test_mode eq "remote") + { + # Do rewind using a remote connection as source, generating + # recovery configuration automatically. + command_ok( + [ + 'pg_rewind', "--debug", + "--source-server", $standby_connstr, + "--target-pgdata=$primary_pgdata", "--no-sync", + "--write-recovery-conf", "--config-file", + "$tmp_folder/primary-postgresql.conf.tmp" + ], + 'pg_rewind remote'); + + # Check that standby.signal is here as recovery configuration + # was requested. + ok( -e "$primary_pgdata/standby.signal", + 'standby.signal created after pg_rewind'); + + # Now, when pg_rewind apparently succeeded with minimal permissions, + # add REPLICATION privilege. So we could test that new standby + # is able to connect to the new primary with generated config. + $node_standby->safe_psql('postgres', + "ALTER ROLE rewind_user WITH REPLICATION;"); + } + elsif ($test_mode eq "archive") + { + + # Do rewind using a local pgdata as source and specified + # directory with target WAL archive. The old primary has + # to be stopped at this point. + + # Remove the existing archive directory and move all WAL + # segments from the old primary to the archives. These + # will be used by pg_rewind. + rmtree($node_primary->archive_dir); + PostgreSQL::Test::RecursiveCopy::copypath( + $node_primary->data_dir . "/pg_wal", + $node_primary->archive_dir); + + # Fast way to remove entire directory content + rmtree($node_primary->data_dir . "/pg_wal"); + mkdir($node_primary->data_dir . "/pg_wal"); + + # Make sure that directories have the right umask as this is + # required by a follow-up check on permissions, and better + # safe than sorry. + chmod(0700, $node_primary->archive_dir) or die $!; + chmod(0700, $node_primary->data_dir . "/pg_wal") or die $!; + + # Add appropriate restore_command to the target cluster + $node_primary->enable_restoring($node_primary, 0); + + # Stop the new primary and be ready to perform the rewind. + $node_standby->stop; + + # Note the use of --no-ensure-shutdown here. WAL files are + # gone in this mode and the primary has been stopped + # gracefully already. --config-file reuses the original + # postgresql.conf as restore_command has been enabled above. + command_ok( + [ + 'pg_rewind', + "--debug", + "--source-pgdata=$standby_pgdata", + "--target-pgdata=$primary_pgdata", + "--no-sync", + "--no-ensure-shutdown", + "--restore-target-wal", + "--config-file", + "$primary_pgdata/postgresql.conf" + ], + 'pg_rewind archive'); + } + else + { + + # Cannot come here normally + croak("Incorrect test mode specified"); + } + + # Now move back postgresql.conf with old settings + move( + "$tmp_folder/primary-postgresql.conf.tmp", + "$primary_pgdata/postgresql.conf"); + + chmod( + $node_primary->group_access() ? 0640 : 0600, + "$primary_pgdata/postgresql.conf") + or BAIL_OUT( + "unable to set permissions for $primary_pgdata/postgresql.conf"); + + # Plug-in rewound node to the now-promoted standby node + if ($test_mode ne "remote") + { + my $port_standby = $node_standby->port; + $node_primary->append_conf( + 'postgresql.conf', qq( +primary_conninfo='port=$port_standby')); + + $node_primary->set_standby_mode(); + } + + # Restart the primary to check that rewind went correctly + $node_primary->start; + + #### Now run the test-specific parts to check the result + + return; +} + +# Clean up after the test. Stop both servers, if they're still running. +sub clean_rewind_test +{ + $node_primary->teardown_node if defined $node_primary; + $node_standby->teardown_node if defined $node_standby; + return; +} + +1; diff --git a/contrib/pg_tde/t/pg_rewind_basic.pl b/contrib/pg_tde/t/pg_rewind_basic.pl new file mode 100644 index 0000000000000..d2cc4d22f0ed4 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_basic.pl @@ -0,0 +1,214 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +sub run_test +{ + my $test_mode = shift; + + RewindTest::setup_cluster($test_mode); + RewindTest::start_primary(); + + # Create an in-place tablespace with some data on it. + primary_psql("CREATE TABLESPACE space_test LOCATION ''"); + primary_psql("CREATE TABLE space_tbl (d text) TABLESPACE space_test"); + primary_psql( + "INSERT INTO space_tbl VALUES ('in primary, before promotion')"); + + # Create a test table and insert a row in primary. + primary_psql("CREATE TABLE tbl1 (d text)"); + primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); + + # This test table will be used to test truncation, i.e. the table + # is extended in the old primary after promotion + primary_psql("CREATE TABLE trunc_tbl (d text)"); + primary_psql("INSERT INTO trunc_tbl VALUES ('in primary')"); + + # This test table will be used to test the "copy-tail" case, i.e. the + # table is truncated in the old primary after promotion + primary_psql("CREATE TABLE tail_tbl (id integer, d text)"); + primary_psql("INSERT INTO tail_tbl VALUES (0, 'in primary')"); + + # This test table is dropped in the old primary after promotion. + primary_psql("CREATE TABLE drop_tbl (d text)"); + primary_psql("INSERT INTO drop_tbl VALUES ('in primary')"); + + primary_psql("CHECKPOINT"); + + RewindTest::create_standby($test_mode); + + # Insert additional data on primary that will be replicated to standby + primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); + primary_psql( + "INSERT INTO trunc_tbl values ('in primary, before promotion')"); + primary_psql( + "INSERT INTO tail_tbl SELECT g, 'in primary, before promotion: ' || g FROM generate_series(1, 10000) g" + ); + + primary_psql('CHECKPOINT'); + + RewindTest::promote_standby(); + + # Insert a row in the old primary. This causes the primary and standby + # to have "diverged", it's no longer possible to just apply the + # standby's logs over primary directory - you need to rewind. + primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); + + # Also insert a new row in the standby, which won't be present in the + # old primary. + standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); + + # Insert enough rows to trunc_tbl to extend the file. pg_rewind should + # truncate it back to the old size. + primary_psql( + "INSERT INTO trunc_tbl SELECT 'in primary, after promotion: ' || g FROM generate_series(1, 10000) g" + ); + + # Truncate tail_tbl. pg_rewind should copy back the truncated part + # (We cannot use an actual TRUNCATE command here, as that creates a + # whole new relfilenode) + primary_psql("DELETE FROM tail_tbl WHERE id > 10"); + primary_psql("VACUUM tail_tbl"); + + # Drop drop_tbl. pg_rewind should copy it back. + primary_psql( + "insert into drop_tbl values ('in primary, after promotion')"); + primary_psql("DROP TABLE drop_tbl"); + + # Insert some data in the in-place tablespace for the old primary and + # the standby. + primary_psql( + "INSERT INTO space_tbl VALUES ('in primary, after promotion')"); + standby_psql( + "INSERT INTO space_tbl VALUES ('in standby, after promotion')"); + + # Before running pg_rewind, do a couple of extra tests with several + # option combinations. As the code paths taken by those tests + # do not change for the "local" and "remote" modes, just run them + # in "local" mode for simplicity's sake. + if ($test_mode eq 'local') + { + my $primary_pgdata = $node_primary->data_dir; + my $standby_pgdata = $node_standby->data_dir; + + # First check that pg_rewind fails if the target cluster is + # not stopped as it fails to start up for the forced recovery + # step. + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync' + ], + 'pg_rewind with running target'); + + # Again with --no-ensure-shutdown, which should equally fail. + # This time pg_rewind complains without attempting to perform + # recovery once. + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--no-ensure-shutdown' + ], + 'pg_rewind --no-ensure-shutdown with running target'); + + # Stop the target, and attempt to run with a local source + # still running. This fails as pg_rewind requires to have + # a source cleanly stopped. + $node_primary->stop; + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--no-ensure-shutdown' + ], + 'pg_rewind with unexpected running source'); + + # Stop the target cluster cleanly, and run again pg_rewind + # with --dry-run mode. If anything gets generated in the data + # folder, the follow-up run of pg_rewind will most likely fail, + # so keep this test as the last one of this subset. + $node_standby->stop; + command_ok( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--dry-run' + ], + 'pg_rewind --dry-run'); + + # Both clusters need to be alive moving forward. + $node_standby->start; + $node_primary->start; + } + + RewindTest::run_pg_rewind($test_mode); + + check_query( + 'SELECT * FROM space_tbl ORDER BY d', + qq(in primary, before promotion +in standby, after promotion +), + 'table content'); + + check_query( + 'SELECT * FROM tbl1', + qq(in primary +in primary, before promotion +in standby, after promotion +), + 'table content'); + + check_query( + 'SELECT * FROM trunc_tbl', + qq(in primary +in primary, before promotion +), + 'truncation'); + + check_query( + 'SELECT count(*) FROM tail_tbl', + qq(10001 +), + 'tail-copy'); + + check_query( + 'SELECT * FROM drop_tbl', + qq(in primary +), + 'drop'); + + # Permissions on PGDATA should be default + SKIP: + { + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os); + + ok(check_mode_recursive($node_primary->data_dir(), 0700, 0600), + 'check PGDATA permissions'); + } + + RewindTest::clean_rewind_test(); + return; +} + +# Run the test in both modes +run_test('local'); +run_test('remote'); +run_test('archive'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_databases.pl b/contrib/pg_tde/t/pg_rewind_databases.pl new file mode 100644 index 0000000000000..755ea80e332f9 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_databases.pl @@ -0,0 +1,77 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +sub run_test +{ + my $test_mode = shift; + + RewindTest::setup_cluster($test_mode, ['-g']); + RewindTest::start_primary(); + + # Create a database in primary with a table. + primary_psql('CREATE DATABASE inprimary'); + primary_psql('CREATE TABLE inprimary_tab (a int)', 'inprimary'); + + RewindTest::create_standby($test_mode); + + # Create another database with another table, the creation is + # replicated to the standby. + primary_psql('CREATE DATABASE beforepromotion'); + primary_psql('CREATE TABLE beforepromotion_tab (a int)', + 'beforepromotion'); + + RewindTest::promote_standby(); + + # Create databases in the old primary and the new promoted standby. + primary_psql('CREATE DATABASE primary_afterpromotion'); + primary_psql('CREATE TABLE primary_promotion_tab (a int)', + 'primary_afterpromotion'); + standby_psql('CREATE DATABASE standby_afterpromotion'); + standby_psql('CREATE TABLE standby_promotion_tab (a int)', + 'standby_afterpromotion'); + + # The clusters are now diverged. + + RewindTest::run_pg_rewind($test_mode); + + # Check that the correct databases are present after pg_rewind. + check_query( + 'SELECT datname FROM pg_database ORDER BY 1', + qq(beforepromotion +inprimary +postgres +standby_afterpromotion +template0 +template1 +), + 'database names'); + + # Permissions on PGDATA should have group permissions + SKIP: + { + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os || $Config::Config{osname} eq 'cygwin'); + + ok(check_mode_recursive($node_primary->data_dir(), 0750, 0640), + 'check PGDATA permissions'); + } + + RewindTest::clean_rewind_test(); + return; +} + +# Run the test in both modes. +run_test('local'); +run_test('remote'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_extrafiles.pl b/contrib/pg_tde/t/pg_rewind_extrafiles.pl new file mode 100644 index 0000000000000..097b0fde9d90a --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_extrafiles.pl @@ -0,0 +1,124 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# Test how pg_rewind reacts to extra files and directories in the data dirs. + +use strict; +use warnings FATAL => 'all'; +use Config; +use PostgreSQL::Test::Utils; +use Test::More; + +use File::Find; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + + +sub run_test +{ + my $test_mode = shift; + + RewindTest::setup_cluster($test_mode); + RewindTest::start_primary(); + + my $test_primary_datadir = $node_primary->data_dir; + + # Create a subdir and files that will be present in both + mkdir "$test_primary_datadir/tst_both_dir"; + append_to_file "$test_primary_datadir/tst_both_dir/both_file1", + "in both1"; + append_to_file "$test_primary_datadir/tst_both_dir/both_file2", + "in both2"; + mkdir "$test_primary_datadir/tst_both_dir/both_subdir/"; + append_to_file + "$test_primary_datadir/tst_both_dir/both_subdir/both_file3", + "in both3"; + + RewindTest::create_standby($test_mode); + + # Create different subdirs and files in primary and standby + my $test_standby_datadir = $node_standby->data_dir; + + mkdir "$test_standby_datadir/tst_standby_dir"; + append_to_file "$test_standby_datadir/tst_standby_dir/standby_file1", + "in standby1"; + append_to_file "$test_standby_datadir/tst_standby_dir/standby_file2", + "in standby2"; + append_to_file + "$test_standby_datadir/tst_standby_dir/standby_file3 with 'quotes'", + "in standby3"; + mkdir "$test_standby_datadir/tst_standby_dir/standby_subdir/"; + append_to_file + "$test_standby_datadir/tst_standby_dir/standby_subdir/standby_file4", + "in standby4"; + # Skip testing .DS_Store files on macOS to avoid risk of side effects + append_to_file "$test_standby_datadir/tst_standby_dir/.DS_Store", + "macOS system file" + unless ($Config{osname} eq 'darwin'); + + mkdir "$test_primary_datadir/tst_primary_dir"; + append_to_file "$test_primary_datadir/tst_primary_dir/primary_file1", + "in primary1"; + append_to_file "$test_primary_datadir/tst_primary_dir/primary_file2", + "in primary2"; + mkdir "$test_primary_datadir/tst_primary_dir/primary_subdir/"; + append_to_file + "$test_primary_datadir/tst_primary_dir/primary_subdir/primary_file3", + "in primary3"; + + RewindTest::promote_standby(); + RewindTest::run_pg_rewind($test_mode); + + # List files in the data directory after rewind. All the files that + # were present in the standby should be present after rewind, and + # all the files that were added on the primary should be removed. + my @paths; + find( + sub { + push @paths, $File::Find::name + if $File::Find::name =~ m/.*tst_.*/; + }, + $test_primary_datadir); + @paths = sort @paths; + + # File::Find converts backslashes to slashes in the newer Perl + # versions. To support all Perl versions, do the same conversion + # for Windows before comparing the paths. + if ($windows_os) + { + for my $filename (@paths) + { + $filename =~ s{\\}{/}g; + } + $test_primary_datadir =~ s{\\}{/}g; + } + + is_deeply( + \@paths, + [ + "$test_primary_datadir/tst_both_dir", + "$test_primary_datadir/tst_both_dir/both_file1", + "$test_primary_datadir/tst_both_dir/both_file2", + "$test_primary_datadir/tst_both_dir/both_subdir", + "$test_primary_datadir/tst_both_dir/both_subdir/both_file3", + "$test_primary_datadir/tst_standby_dir", + "$test_primary_datadir/tst_standby_dir/standby_file1", + "$test_primary_datadir/tst_standby_dir/standby_file2", + "$test_primary_datadir/tst_standby_dir/standby_file3 with 'quotes'", + "$test_primary_datadir/tst_standby_dir/standby_subdir", + "$test_primary_datadir/tst_standby_dir/standby_subdir/standby_file4" + ], + "file lists match"); + + RewindTest::clean_rewind_test(); + return; +} + +# Run the test in both modes. +run_test('local'); +run_test('remote'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_growing_files.pl b/contrib/pg_tde/t/pg_rewind_growing_files.pl new file mode 100644 index 0000000000000..8e59ad6996168 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_growing_files.pl @@ -0,0 +1,77 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +RewindTest::setup_cluster("local"); +RewindTest::start_primary(); + +# Create a test table and insert a row in primary. +primary_psql("CREATE TABLE tbl1 (d text)"); +primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); +primary_psql("CHECKPOINT"); + +RewindTest::create_standby("local"); + +# Insert additional data on primary that will be replicated to standby +primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); +primary_psql('CHECKPOINT'); + +RewindTest::promote_standby(); + +# Insert a row in the old primary. This causes the primary and standby to have +# "diverged", it's no longer possible to just apply the standby's logs over +# primary directory - you need to rewind. Also insert a new row in the +# standby, which won't be present in the old primary. +primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); +standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); + +# Stop the nodes before running pg_rewind +$node_standby->stop; +$node_primary->stop; + +my $primary_pgdata = $node_primary->data_dir; +my $standby_pgdata = $node_standby->data_dir; + +# Add an extra file that we can tamper with without interfering with the data +# directory data files. +mkdir "$standby_pgdata/tst_both_dir"; +append_to_file "$standby_pgdata/tst_both_dir/file1", 'a'; + +# Run pg_rewind and pipe the output from the run into the extra file we want +# to copy. This will ensure that the file is continuously growing during the +# copy operation and the result will be an error. +my $ret = run_log( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', + ], + '2>>', + "$standby_pgdata/tst_both_dir/file1"); +ok(!$ret, 'Error out on copying growing file'); + +# Ensure that the files are of different size, the final error message should +# only be in one of them making them guaranteed to be different +my $primary_size = -s "$primary_pgdata/tst_both_dir/file1"; +my $standby_size = -s "$standby_pgdata/tst_both_dir/file1"; +isnt($standby_size, $primary_size, "File sizes should differ"); + +# Extract the last line from the verbose output as that should have the error +# message for the unexpected file size +my $last; +open my $f, '<', "$standby_pgdata/tst_both_dir/file1" or die $!; +$last = $_ while (<$f>); +close $f; +like($last, qr/error: size of source file/, "Check error message"); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_keep_recycled_wals.pl b/contrib/pg_tde/t/pg_rewind_keep_recycled_wals.pl new file mode 100644 index 0000000000000..bf0084d3bc358 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_keep_recycled_wals.pl @@ -0,0 +1,62 @@ +# Copyright (c) 2021-2024, PostgreSQL Global Development Group +# +# Test situation where a target data directory contains +# WAL files that were already recycled by the new primary. +# + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; +use RewindTest; + +RewindTest::setup_cluster(); +$node_primary->enable_archiving(); +RewindTest::start_primary(); + +RewindTest::create_standby(); +$node_standby->enable_restoring($node_primary, 0); +$node_standby->reload(); + +RewindTest::primary_psql("CHECKPOINT"); # last common checkpoint + +# We use `perl -e "exit(1)"` as an alternative to "false", because the latter +# might not be available on Windows. +my $false = "$^X -e \"exit(1)\""; +$node_primary->append_conf( + 'postgresql.conf', qq( +archive_command = '$false' +)); +$node_primary->reload(); + +# advance WAL on primary; this WAL segment will never make it to the archive +RewindTest::primary_psql("CREATE TABLE t(a int)"); +RewindTest::primary_psql("INSERT INTO t VALUES(0)"); +RewindTest::primary_psql("SELECT pg_switch_wal()"); + +RewindTest::promote_standby; + +# new primary loses diverging WAL segment +RewindTest::standby_psql("INSERT INTO t values(0)"); +RewindTest::standby_psql("SELECT pg_switch_wal()"); + +$node_standby->stop(); +$node_primary->stop(); + +my ($stdout, $stderr) = run_command( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $node_standby->data_dir, + '--target-pgdata', $node_primary->data_dir, + '--no-sync', + ]); + +like( + $stderr, + qr/Not removing file .* because it is required for recovery/, + "some WAL files were skipped"); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_min_recovery_point.pl b/contrib/pg_tde/t/pg_rewind_min_recovery_point.pl new file mode 100644 index 0000000000000..65b66df6333e7 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_min_recovery_point.pl @@ -0,0 +1,168 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# +# Test situation where a target data directory contains +# WAL records beyond both the last checkpoint and the divergence +# point: +# +# Target WAL (TLI 2): +# +# backup ... Checkpoint A ... INSERT 'rewind this' +# (TLI 1 -> 2) +# +# ^ last common ^ minRecoveryPoint +# checkpoint +# +# Source WAL (TLI 3): +# +# backup ... Checkpoint A ... Checkpoint B ... INSERT 'keep this' +# (TLI 1 -> 2) (TLI 2 -> 3) +# +# +# The last common checkpoint is Checkpoint A. But there is WAL on TLI 2 +# after the last common checkpoint that needs to be rewound. We used to +# have a bug where minRecoveryPoint was ignored, and pg_rewind concluded +# that the target doesn't need rewinding in this scenario, because the +# last checkpoint on the target TLI was an ancestor of the source TLI. +# +# +# This test does not make use of RewindTest as it requires three +# nodes. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +use File::Copy; + +my $tmp_folder = PostgreSQL::Test::Utils::tempdir; + +my $node_1 = PostgreSQL::Test::Cluster->new('node_1'); +$node_1->init(allows_streaming => 1); +$node_1->append_conf( + 'postgresql.conf', qq( +wal_keep_size='100 MB' +)); + +$node_1->start; + +# Create a couple of test tables +$node_1->safe_psql('postgres', 'CREATE TABLE public.foo (t TEXT)'); +$node_1->safe_psql('postgres', 'CREATE TABLE public.bar (t TEXT)'); +$node_1->safe_psql('postgres', "INSERT INTO public.bar VALUES ('in both')"); + +# +# Create node_2 and node_3 as standbys following node_1 +# +my $backup_name = 'my_backup'; +$node_1->backup($backup_name); + +my $node_2 = PostgreSQL::Test::Cluster->new('node_2'); +$node_2->init_from_backup($node_1, $backup_name, has_streaming => 1); +$node_2->start; + +my $node_3 = PostgreSQL::Test::Cluster->new('node_3'); +$node_3->init_from_backup($node_1, $backup_name, has_streaming => 1); +$node_3->start; + +# Wait until node 3 has connected and caught up +$node_1->wait_for_catchup('node_3'); + +# +# Swap the roles of node_1 and node_3, so that node_1 follows node_3. +# +$node_1->stop('fast'); +$node_3->promote; + +# reconfigure node_1 as a standby following node_3 +my $node_3_connstr = $node_3->connstr; +$node_1->append_conf( + 'postgresql.conf', qq( +primary_conninfo='$node_3_connstr' +)); +$node_1->set_standby_mode(); +$node_1->start(); + +# also reconfigure node_2 to follow node_3 +$node_2->append_conf( + 'postgresql.conf', qq( +primary_conninfo='$node_3_connstr' +)); +$node_2->restart(); + +# +# Promote node_1, to create a split-brain scenario. +# + +# make sure node_1 is full caught up with node_3 first +$node_3->wait_for_catchup('node_1'); + +$node_1->promote; + +# +# We now have a split-brain with two primaries. Insert a row on both to +# demonstratively create a split brain. After the rewind, we should only +# see the insert on 1, as the insert on node 3 is rewound away. +# +$node_1->safe_psql('postgres', + "INSERT INTO public.foo (t) VALUES ('keep this')"); +# 'bar' is unmodified in node 1, so it won't be overwritten by replaying the +# WAL from node 1. +$node_3->safe_psql('postgres', + "INSERT INTO public.bar (t) VALUES ('rewind this')"); + +# Insert more rows in node 1, to bump up the XID counter. Otherwise, if +# rewind doesn't correctly rewind the changes made on the other node, +# we might fail to notice if the inserts are invisible because the XIDs +# are not marked as committed. +$node_1->safe_psql('postgres', + "INSERT INTO public.foo (t) VALUES ('and this')"); +$node_1->safe_psql('postgres', + "INSERT INTO public.foo (t) VALUES ('and this too')"); + +# Wait for node 2 to catch up +$node_2->poll_query_until('postgres', + q|SELECT COUNT(*) > 1 FROM public.bar|, 't'); + +# At this point node_2 will shut down without a shutdown checkpoint, +# but with WAL entries beyond the preceding shutdown checkpoint. +$node_2->stop('fast'); +$node_3->stop('fast'); + +my $node_2_pgdata = $node_2->data_dir; +my $node_1_connstr = $node_1->connstr; + +# Keep a temporary postgresql.conf or it would be overwritten during the rewind. +copy( + "$node_2_pgdata/postgresql.conf", + "$tmp_folder/node_2-postgresql.conf.tmp"); + +command_ok( + [ + 'pg_rewind', "--source-server=$node_1_connstr", + "--target-pgdata=$node_2_pgdata", "--debug" + ], + 'run pg_rewind'); + +# Now move back postgresql.conf with old settings +move( + "$tmp_folder/node_2-postgresql.conf.tmp", + "$node_2_pgdata/postgresql.conf"); + +$node_2->start; + +# Check contents of the test tables after rewind. The rows inserted in node 3 +# before rewind should've been overwritten with the data from node 1. +my $result; +$result = $node_2->safe_psql('postgres', 'SELECT * FROM public.foo'); +is( $result, qq(keep this +and this +and this too), 'table foo after rewind'); + +$result = $node_2->safe_psql('postgres', 'SELECT * FROM public.bar'); +is($result, qq(in both), 'table bar after rewind'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_options.pl b/contrib/pg_tde/t/pg_rewind_options.pl new file mode 100644 index 0000000000000..5917bb741d510 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_options.pl @@ -0,0 +1,45 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# +# Test checking options of pg_rewind. +# +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +program_help_ok('pg_rewind'); +program_version_ok('pg_rewind'); +program_options_handling_ok('pg_rewind'); + +my $primary_pgdata = PostgreSQL::Test::Utils::tempdir; +my $standby_pgdata = PostgreSQL::Test::Utils::tempdir; +command_fails( + [ + 'pg_rewind', '--debug', + '--target-pgdata', $primary_pgdata, + '--source-pgdata', $standby_pgdata, + 'extra_arg1' + ], + 'too many arguments'); +command_fails([ 'pg_rewind', '--target-pgdata', $primary_pgdata ], + 'no source specified'); +command_fails( + [ + 'pg_rewind', '--debug', + '--target-pgdata', $primary_pgdata, + '--source-pgdata', $standby_pgdata, + '--source-server', 'incorrect_source' + ], + 'both remote and local sources specified'); +command_fails( + [ + 'pg_rewind', '--debug', + '--target-pgdata', $primary_pgdata, + '--source-pgdata', $standby_pgdata, + '--write-recovery-conf' + ], + 'no local source with --write-recovery-conf'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_pg_xlog_symlink.pl b/contrib/pg_tde/t/pg_rewind_pg_xlog_symlink.pl new file mode 100644 index 0000000000000..8f63b3707ee07 --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_pg_xlog_symlink.pl @@ -0,0 +1,80 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# +# Test pg_rewind when the target's pg_wal directory is a symlink. +# +use strict; +use warnings FATAL => 'all'; +use File::Copy; +use File::Path qw(rmtree); +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +sub run_test +{ + my $test_mode = shift; + + my $primary_xlogdir = + "${PostgreSQL::Test::Utils::tmp_check}/xlog_primary"; + + rmtree($primary_xlogdir); + RewindTest::setup_cluster($test_mode); + + my $test_primary_datadir = $node_primary->data_dir; + + # turn pg_wal into a symlink + print("moving $test_primary_datadir/pg_wal to $primary_xlogdir\n"); + move("$test_primary_datadir/pg_wal", $primary_xlogdir) or die; + dir_symlink($primary_xlogdir, "$test_primary_datadir/pg_wal") or die; + + RewindTest::start_primary(); + + # Create a test table and insert a row in primary. + primary_psql("CREATE TABLE tbl1 (d text)"); + primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); + + primary_psql("CHECKPOINT"); + + RewindTest::create_standby($test_mode); + + # Insert additional data on primary that will be replicated to standby + primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); + + primary_psql('CHECKPOINT'); + + RewindTest::promote_standby(); + + # Insert a row in the old primary. This causes the primary and standby + # to have "diverged", it's no longer possible to just apply the + # standby's logs over primary directory - you need to rewind. + primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); + + # Also insert a new row in the standby, which won't be present in the + # old primary. + standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); + + RewindTest::run_pg_rewind($test_mode); + + check_query( + 'SELECT * FROM tbl1', + qq(in primary +in primary, before promotion +in standby, after promotion +), + 'table content'); + + RewindTest::clean_rewind_test(); + return; +} + +# Run the test in both modes +run_test('local'); +run_test('remote'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_rewind_same_timeline.pl b/contrib/pg_tde/t/pg_rewind_same_timeline.pl new file mode 100644 index 0000000000000..c8f5365ee7b0c --- /dev/null +++ b/contrib/pg_tde/t/pg_rewind_same_timeline.pl @@ -0,0 +1,24 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# +# Test that running pg_rewind with the source and target clusters +# on the same timeline runs successfully. +# +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +RewindTest::setup_cluster(); +RewindTest::start_primary(); +RewindTest::create_standby(); +RewindTest::run_pg_rewind('local'); +RewindTest::clean_rewind_test(); + +done_testing(); From 2c7a809eae3e74daf02044f6adcd5fa509b9b4f7 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 16 Jul 2025 18:27:31 +0300 Subject: [PATCH 520/796] Add WAL encryption to TDE pg_rewind tests Now the TDE version of pg_rewind tests runs with the WAL encryption ON --- contrib/pg_tde/t/RewindTest.pm | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/contrib/pg_tde/t/RewindTest.pm b/contrib/pg_tde/t/RewindTest.pm index 0bf59db997359..0fa74f2b3150f 100644 --- a/contrib/pg_tde/t/RewindTest.pm +++ b/contrib/pg_tde/t/RewindTest.pm @@ -36,6 +36,7 @@ use warnings FATAL => 'all'; use Carp; use Exporter 'import'; +use File::Basename; use File::Copy; use File::Path qw(rmtree); use IPC::Run qw(run); @@ -113,6 +114,13 @@ sub setup_cluster my $extra_name = shift; # Used to differentiate clusters my $extra = shift; # Extra params for initdb + my ($test_name) = basename($0) =~ /([^.]*)/; + my ($test_mode) = $extra_name //= 'default'; + my $tde_keyring_file = + "/tmp/pg_tde_rewind_test_${test_name}_${test_mode}.per"; + + unlink($tde_keyring_file); + # Initialize primary, data checksums are mandatory $node_primary = PostgreSQL::Test::Cluster->new( @@ -132,7 +140,31 @@ sub setup_cluster 'postgresql.conf', qq( wal_keep_size = 320MB allow_in_place_tablespaces = on + +shared_preload_libraries = 'pg_tde' )); + + $node_primary->start; + + $node_primary->safe_psql('postgres', + "CREATE EXTENSION IF NOT EXISTS pg_tde;"); + $node_primary->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal','${tde_keyring_file}');" + ); + $node_primary->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('global-db-principal-key', 'file-keyring-wal');" + ); + $node_primary->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('global-db-principal-key', 'file-keyring-wal');" + ); + + $node_primary->append_conf( + 'postgresql.conf', q{ +pg_tde.wal_encrypt = on +}); + + $node_primary->stop; + return; } From ec7528b87a6e88cdb9add2656379364aab095750 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 14 May 2025 18:34:44 +0300 Subject: [PATCH 521/796] Re-encrypt WAL segments when copying data to the new timeline We use TLI to calculate encryption IV, therefore, data in segments has to be re-encrypted when copied to the new timeline. For PG-1412 --- src/backend/access/transam/xlog.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 631deea344da7..c1d7988066ff6 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -3423,6 +3423,7 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno, int srcfd; int fd; int nbytes; + off_t offset = 0; /* * Open the source file @@ -3471,7 +3472,8 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno, if (nread > sizeof(buffer)) nread = sizeof(buffer); pgstat_report_wait_start(WAIT_EVENT_WAL_COPY_READ); - r = read(srcfd, buffer.data, nread); + r = xlog_smgr->seg_read(srcfd, buffer.data, nread, offset, + srcTLI, srcsegno, wal_segment_size); if (r != nread) { if (r < 0) @@ -3489,7 +3491,7 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno, } errno = 0; pgstat_report_wait_start(WAIT_EVENT_WAL_COPY_WRITE); - if ((int) write(fd, buffer.data, sizeof(buffer)) != (int) sizeof(buffer)) + if ((int) xlog_smgr->seg_write(fd, buffer.data, sizeof(buffer), offset, destTLI, destsegno) != (int) sizeof(buffer)) { int save_errno = errno; @@ -3505,6 +3507,7 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno, errmsg("could not write to file \"%s\": %m", tmppath))); } pgstat_report_wait_end(); + offset += sizeof(buffer); } pgstat_report_wait_start(WAIT_EVENT_WAL_COPY_SYNC); From 8f555b27c4b1b8d55c364001261e7d2dc77c44e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Fri, 18 Jul 2025 10:16:01 +0200 Subject: [PATCH 522/796] Fix hint when principal server key not configured The errhint is supposed to be a full sentence, and pg_tde_set_server_key no longer creates any principal keys. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 07ad317dec06d..f285f2e24a0a8 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -123,7 +123,7 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat { ereport(ERROR, errmsg("principal key not configured"), - errhint("create one using pg_tde_set_server_key before using encrypted WAL")); + errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); } /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ From 3cb397447e8a4025a3c6725ae07a7f985f81b426 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 23 Jul 2025 12:32:13 +0300 Subject: [PATCH 523/796] Update release notes structure, TOC, and improve rn index (#480) - add small icons to links --- .../docs/release-notes/release-notes-v1.0.md | 46 +++++++++---------- .../docs/release-notes/release-notes.md | 29 ++++++++---- contrib/pg_tde/documentation/mkdocs.yml | 17 ++++--- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md index 96a7ce3b1ea2a..e5035a8a1d66e 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md @@ -1,6 +1,6 @@ # pg_tde 1.0 ({{date.GA10}}) -The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get Started](../install.md){.md-button} @@ -8,7 +8,7 @@ The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE)]( * **`pg_tde` 1.0 is now GA (Generally Available)** -And **stable** for encrypting relational data in PostgreSQL using [Transparent Data Encryption (TDE)](../index/index.md). This milestone brings production-level data protection to PostgreSQL workloads. +And **stable** for encrypting relational data in PostgreSQL using [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/index.md). This milestone brings production-level data protection to PostgreSQL workloads. * **WAL encryption is still in Beta** @@ -16,7 +16,7 @@ The WAL encryption feature is currently still in beta and is not effective unles ## Upgrade considerations -`pg_tde` {{tdeversion}} is **not** backward compatible with previous `pg_tde` versions, like Release Candidate 2, due to significant changes in code. This means you **cannot** directly upgrade from one version to another. You must do **a clean installation** of `pg_tde`. +`pg_tde` ({{tdeversion}}) is **not** backward compatible with previous `pg_tde` versions, like Release Candidate 2, due to significant changes in code. This means you **cannot** directly upgrade from one version to another. You must do **a clean installation** of `pg_tde`. ## Known issues @@ -33,29 +33,29 @@ Adjust the limits with caution since it affects other processes running in your ### New Features -- [PG-1257](https://perconadev.atlassian.net/browse/PG-1257) – Added SQL function to remove the current principal key +- [PG-1257 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1257) – Added SQL function to remove the current principal key ### Improvements -- [PG-1617](https://perconadev.atlassian.net/browse/PG-1617) – Removed relation key cache -- [PG-1635](https://perconadev.atlassian.net/browse/PG-1635) – User-facing TDE functions now return void -- [PG-1605](https://perconadev.atlassian.net/browse/PG-1605) – Removed undeclared dependencies for `pg_tde_grant_database_key_management_to_role()` +- [PG-1617 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1617) – Removed relation key cache +- [PG-1635 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1635) – User-facing TDE functions now return void +- [PG-1605 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1605) – Removed undeclared dependencies for `pg_tde_grant_database_key_management_to_role()` ### Bugs Fixed -- [PG-1581](https://perconadev.atlassian.net/browse/PG-1581) – Fixed PostgreSQL crashes on table access when KMIP key is unavailable after restart -- [PG-1583](https://perconadev.atlassian.net/browse/PG-1583) – Fixed a crash when dropping the `pg_tde` extension with CASCADE after changing the key provider file -- [PG-1585](https://perconadev.atlassian.net/browse/PG-1585) – Fixed the vault provider re-addition that failed after server restart with a new token -- [PG-1592](https://perconadev.atlassian.net/browse/PG-1592) – Improve error logs when Server Key Info is requested without being created -- [PG-1593](https://perconadev.atlassian.net/browse/PG-1593) – Fixed runtime failures when invalid Vault tokens are allowed during key provider creation -- [PG-1600](https://perconadev.atlassian.net/browse/PG-1600) – Fixed Postmaster error when dropping a table with an unavailable key provider -- [PG-1606](https://perconadev.atlassian.net/browse/PG-1606) – Fixed missing superuser check in role grant function leads to misleading errors -- [PG-1607](https://perconadev.atlassian.net/browse/PG-1607) – Improved CA parameter order and surrounding documentation for clearer interpretation -- [PG-1608](https://perconadev.atlassian.net/browse/PG-1608) – Updated and fixed global key configuration parameters in documentation -- [PG-1613](https://perconadev.atlassian.net/browse/PG-1613) – Tested and improved the `pg_tde_change_key_provider` CLI utility -- [PG-1637](https://perconadev.atlassian.net/browse/PG-1637) – Fixed unused keys in key files which caused issues after OID wraparound -- [PG-1651](https://perconadev.atlassian.net/browse/PG-1651) – Fixed the CLI tool when working with Vault key export/import -- [PG-1652](https://perconadev.atlassian.net/browse/PG-1652) – Fixed when the server fails to find encryption keys after CLI-based provider change -- [PG-1662](https://perconadev.atlassian.net/browse/PG-1662) – Fixed the creation of inconsistent encryption status when altering partitioned tables -- [PG-1663](https://perconadev.atlassian.net/browse/PG-1663) – Fixed the indexes on partitioned tables which were not encrypted -- [PG-1700](https://perconadev.atlassian.net/browse/PG-1700) – Fixed the error hint when the principal key is missing +- [PG-1581 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1581) – Fixed PostgreSQL crashes on table access when KMIP key is unavailable after restart +- [PG-1583 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1583) – Fixed a crash when dropping the `pg_tde` extension with CASCADE after changing the key provider file +- [PG-1585 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1585) – Fixed the vault provider re-addition that failed after server restart with a new token +- [PG-1592 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1592) – Improve error logs when Server Key Info is requested without being created +- [PG-1593 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1593) – Fixed runtime failures when invalid Vault tokens are allowed during key provider creation +- [PG-1600 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1600) – Fixed Postmaster error when dropping a table with an unavailable key provider +- [PG-1606 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1606) – Fixed missing superuser check in role grant function leads to misleading errors +- [PG-1607 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1607) – Improved CA parameter order and surrounding documentation for clearer interpretation +- [PG-1608 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1608) – Updated and fixed global key configuration parameters in documentation +- [PG-1613 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1613) – Tested and improved the `pg_tde_change_key_provider` CLI utility +- [PG-1637 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1637) – Fixed unused keys in key files which caused issues after OID wraparound +- [PG-1651 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1651) – Fixed the CLI tool when working with Vault key export/import +- [PG-1652 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1652) – Fixed when the server fails to find encryption keys after CLI-based provider change +- [PG-1662 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1662) – Fixed the creation of inconsistent encryption status when altering partitioned tables +- [PG-1663 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1663) – Fixed the indexes on partitioned tables which were not encrypted +- [PG-1700 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1700) – Fixed the error hint when the principal key is missing diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index d1e7b8e1424d8..38d8536e2ba4f 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -1,10 +1,19 @@ -# Release notes index - -* [Percona Transparent Database Encryption for PostgreSQL 1.0](release-notes-v1.0.md) ({{date.GA10}}) -* [pg_tde Release Candidate 2 (RC2)](rc2.md) ({{date.RC2}}) -* [pg_tde Release Candidate 1 (RC1)](rc2.md) ({{date.RC}}) -* [pg_tde Release Candidate](rc.md) ({{date.RC}}) -* [pg_tde Beta2](beta2.md) (2024-12-16) -* [pg_tde Beta](beta.md) (2024-06-30) -* [pg_tde Alpha1](alpha1.md) (2024-03-28) -* [pg_tde MVP](mvp.md) (2023-12-12) +# Percona Transparent Data Encryption for PostgreSQL release notes index + +This page lists all release notes for `pg_tde`, organized by year and version. Use it to track new features, fixes, and updates across major and minor versions. + +## 2025 + +* [1.0](release-notes-v1.0.md) ({{date.GA10}}) +* [Release Candidate 2 (RC2)](rc2.md) ({{date.RC2}}) +* [Release Candidate 1 (RC1)](rc.md) ({{date.RC}}) + +## 2024 + +* [Beta 2](beta2.md) (2024-12-16) +* [Beta 1](beta.md) (2024-06-30) +* [Alpha 1](alpha1.md) (2024-03-28) + +## 2023 + +* [MVP](mvp.md) (2023-12-12) diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index aa490d2dd0ada..810425f44bcfa 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -206,11 +206,14 @@ nav: - faq.md - "Release notes": - "Release notes index": release-notes/release-notes.md - - release-notes/release-notes-v1.0.md - - release-notes/rc2.md - - release-notes/rc.md - - release-notes/beta2.md - - release-notes/beta.md - - release-notes/alpha1.md - - release-notes/mvp.md + - "2025": + - "1.0": release-notes/release-notes-v1.0.md + - "Release Candidate 2": release-notes/rc2.md + - "Release Candidate 1": release-notes/rc.md + - "2024 (Alpha 1 - Beta 2)": + - "Beta 2": release-notes/beta2.md + - "Beta": release-notes/beta.md + - "Alpha 1": release-notes/alpha1.md + - "2023 (MVP)": + - "MVP": release-notes/mvp.md - contribute.md From 11dcde6722fe0052db79336b62b55ce49e5152da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 23 Jul 2025 10:20:13 +0200 Subject: [PATCH 524/796] Rename helper function This function counts the number of encryption keys in the key file associated with the given OID. Name it accordingly. Also remove comment about only user which is no longer true. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 12 +++++------- contrib/pg_tde/src/catalog/tde_principal_key.c | 4 ++-- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/src/pg_tde_event_capture.c | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index f285f2e24a0a8..6c1f146e306d3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -529,7 +529,7 @@ pg_tde_delete_principal_key(Oid dbOid) char path[MAXPGPATH]; Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - Assert(pg_tde_count_relations(dbOid) == 0); + Assert(pg_tde_count_encryption_keys(dbOid) == 0); pg_tde_set_db_file_path(dbOid, path); @@ -672,17 +672,15 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, } /* - * Counts number of encrypted objects in a database. + * Counts number of encryption keys in a key file. * * Does not check if objects actually exist but just that they have keys in - * the map file. For the only current caller, checking if we can use - * FILE_COPY, this is good enough but for other workloads where a false - * positive is more harmful this might not be. + * the key file. * - * Works even if the database has no map file. + * Works even if the database has no key file. */ int -pg_tde_count_relations(Oid dbOid) +pg_tde_count_encryption_keys(Oid dbOid) { char db_map_path[MAXPGPATH]; File map_fd; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 38f962b4794f2..4e030c364298e 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -700,7 +700,7 @@ pg_tde_delete_key(PG_FUNCTION_ARGS) * If database has something encryted, we can try to fallback to the * default principal key */ - if (pg_tde_count_relations(MyDatabaseId) != 0) + if (pg_tde_count_encryption_keys(MyDatabaseId) != 0) { default_principal_key = GetPrincipalKeyNoDefault(DEFAULT_DATA_TDE_OID, LW_EXCLUSIVE); if (default_principal_key == NULL) @@ -785,7 +785,7 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) * delete default principal key if there are encrypted tables in * the database. */ - if (pg_tde_count_relations(dbOid) != 0) + if (pg_tde_count_encryption_keys(dbOid) != 0) { ereport(ERROR, errmsg("cannot delete default principal key"), diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index f3177544cf551..e6d6d982404ec 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -92,7 +92,7 @@ extern bool pg_tde_has_smgr_key(RelFileLocator rel); extern InternalKey *pg_tde_get_smgr_key(RelFileLocator rel); extern void pg_tde_free_key_map_entry(RelFileLocator rel); -extern int pg_tde_count_relations(Oid dbOid); +extern int pg_tde_count_encryption_keys(Oid dbOid); extern void pg_tde_delete_tde_files(Oid dbOid); diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index 9f089f5c36eee..3dcb787c7dd87 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -643,7 +643,7 @@ pg_tde_proccess_utility(PlannedStmt *pstmt, int count; LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); - count = pg_tde_count_relations(dbOid); + count = pg_tde_count_encryption_keys(dbOid); LWLockRelease(tde_lwlock_enc_keys()); if (count > 0) From 7491b0a753271a0ace4c3fa2693e75a3b4d26e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 23 Jul 2025 10:29:37 +0200 Subject: [PATCH 525/796] PG-1658 Remove server key when removing default key Previously this was left behind even if the default key was deleted. Check if any WAL encryption keys exist and allow removal if there are none. --- .../pg_tde/expected/delete_principal_key.out | 40 +++++++++++++++++++ contrib/pg_tde/sql/delete_principal_key.sql | 11 +++++ .../pg_tde/src/catalog/tde_principal_key.c | 18 ++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 550bcc217b2dd..92b8299c2b725 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -134,6 +134,46 @@ SELECT pg_tde_delete_key(); (1 row) +-- Delete default key even if it's configured for a database or server key, as +-- long as it's unused. Regardless how the key was set, we unset it if it's the +-- same key as is used as a default key. This is probably a bug. +SELECT pg_tde_set_default_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_default_key_using_global_key_provider +-------------------------------------------------- + +(1 row) + +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); + pg_tde_set_key_using_global_key_provider +------------------------------------------ + +(1 row) + +SELECT pg_tde_set_server_key_using_global_key_provider('test-db-key','file-provider'); +WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! + pg_tde_set_server_key_using_global_key_provider +------------------------------------------------- + +(1 row) + +SELECT pg_tde_delete_default_key(); + pg_tde_delete_default_key +--------------------------- + +(1 row) + +SELECT pg_tde_key_info(); -- No key configured + pg_tde_key_info +----------------- + (,,,) +(1 row) + +SELECT pg_tde_server_key_info(); -- No key configured + pg_tde_server_key_info +------------------------ + (,,,) +(1 row) + SELECT pg_tde_delete_global_key_provider('file-provider'); pg_tde_delete_global_key_provider ----------------------------------- diff --git a/contrib/pg_tde/sql/delete_principal_key.sql b/contrib/pg_tde/sql/delete_principal_key.sql index abc4c574b5616..142eedde0a56d 100644 --- a/contrib/pg_tde/sql/delete_principal_key.sql +++ b/contrib/pg_tde/sql/delete_principal_key.sql @@ -53,5 +53,16 @@ SELECT pg_tde_delete_default_key(); DROP TABLE test_table; SELECT pg_tde_delete_key(); + +-- Delete default key even if it's configured for a database or server key, as +-- long as it's unused. Regardless how the key was set, we unset it if it's the +-- same key as is used as a default key. This is probably a bug. +SELECT pg_tde_set_default_key_using_global_key_provider('test-db-key','file-provider'); +SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); +SELECT pg_tde_set_server_key_using_global_key_provider('test-db-key','file-provider'); +SELECT pg_tde_delete_default_key(); +SELECT pg_tde_key_info(); -- No key configured +SELECT pg_tde_server_key_info(); -- No key configured + SELECT pg_tde_delete_global_key_provider('file-provider'); DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 4e030c364298e..d795eb014ee61 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -798,8 +798,22 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) } /* - * Remove empty key map files for databases that has no encrypted tables - * as we cannot leave reference to the default principal key. + * The default key may have been used as server key, check if there are + * any WAL encryption keys that uses it. + */ + principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); + if (pg_tde_is_same_principal_key(default_principal_key, principal_key)) + { + if (pg_tde_count_encryption_keys(GLOBAL_DATA_TDE_OID) != 0) + ereport(ERROR, + errmsg("cannot delete default principal key"), + errhint("There are WAL encryption keys.")); + dbs = lappend_oid(dbs, GLOBAL_DATA_TDE_OID); + } + + /* + * Remove empty key files for OIDs that have no encryption keys as we + * cannot leave references to the default principal key. */ foreach_oid(dbOid, dbs) { From 9a493eb80cd0ec9d9098b3b0d14c97a299f76a28 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 23 Jul 2025 15:00:07 +0300 Subject: [PATCH 526/796] Add links from features.md to their specific topics in docs (#466) - improve button text for clarity in features.md --- contrib/pg_tde/documentation/docs/features.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index ce9cd10826a81..8df33533bfa4d 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -5,7 +5,7 @@ The Percona Server for PostgreSQL provides an extended Storage Manager API that The following features are available for the extension: -* Table encryption, including: +* [Table encryption](test.md#encrypt-data-in-a-new-table), including: * Data tables * Index data for encrypted tables * TOAST tables @@ -14,9 +14,9 @@ The following features are available for the extension: !!! note Metadata of those tables is not encrypted. -* Single-tenancy support via a global keyring provider -* Multi-tenancy support +* Single-tenancy support via a [global keyring provider](global-key-provider-configuration/set-principal-key.md) +* [Multi-tenancy support](how-to/multi-tenant-setup.md) * Table-level granularity for encryption and access control -* Multiple Key management options +* Multiple [Key management options](global-key-provider-configuration/index.md) -[Overview](index/index.md){.md-button} [Get Started](install.md){.md-button} +[What is Transparent Data Encryption (TDE)?](index/index.md){.md-button} [Install pg_tde to get started](install.md){.md-button} From 4a9e09101b923c026d7e6bbc4193aaea4cc87a75 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 21 Jul 2025 21:22:25 +0200 Subject: [PATCH 527/796] Remove an invalid optimization when reading WAL It is a bit unclear what this optimization was supposed to do, presumably it was to break from the loop once the whole buffer was decrypted, but the logic was very confused since if we want to do a similar optimization it should be something like if (dec_off + read_sz == offset + readsz). --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index bd08072aefdcc..fe20911325888 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -342,11 +342,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, #endif pg_tde_stream_crypt(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, &curr_key->key, &curr_key->crypt_ctx); - - if (dec_off + dec_sz == offset) - { - break; - } } } } From c7cc1d6ec486eb5d5b0d9ab3fdc7dd0ea960964e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 21 Jul 2025 21:30:33 +0200 Subject: [PATCH 528/796] Remove a pointless layer of indirection in the WAL encryption buffer The performance improvement should be tiny but this also makes the code easier to follow. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index fe20911325888..b8ba5d0237459 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -55,12 +55,12 @@ static ssize_t TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, typedef struct EncryptionStateData { - char *segBuf; char db_map_path[MAXPGPATH]; pg_atomic_uint64 enc_key_lsn; /* to sync with readers */ } EncryptionStateData; static EncryptionStateData *EncryptionState = NULL; +static char *EncryptionBuf; /* TODO: can be swapped out to the disk */ static InternalKey EncryptionKey = @@ -126,7 +126,6 @@ void TDEXLogShmemInit(void) { bool foundBuf; - char *allocptr; EncryptionState = (EncryptionStateData *) ShmemInitStruct("TDE XLog Encryption State", @@ -137,11 +136,9 @@ TDEXLogShmemInit(void) if (EncryptXLog) { - allocptr = ((char *) EncryptionState) + sizeof(EncryptionStateData); - allocptr = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, allocptr); - EncryptionState->segBuf = allocptr; + EncryptionBuf = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, ((char *) EncryptionState) + sizeof(EncryptionStateData)); - Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionState->segBuf + TDEXLogEncryptBuffSize()); + Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionBuf + TDEXLogEncryptBuffSize()); } pg_atomic_init_u64(&EncryptionState->enc_key_lsn, 0); @@ -158,7 +155,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, { char iv_prefix[16]; InternalKey *key = &EncryptionKey; - char *enc_buff = EncryptionState->segBuf; + char *enc_buff = EncryptionBuf; Assert(count <= TDEXLogEncryptBuffSize()); From d9403cbb492975b11d278752f3625dbfebed7b08 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Jul 2025 07:48:43 +0200 Subject: [PATCH 529/796] Split WAL SMGR init to make it more frontend-firendly Since some frontend tools will need to write WAL while others will not it makes sense to split the initalization so only some frontend tools and the backend needs to initialize the WAL write related stuff. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 16 ++++++++++------ .../pg_tde/src/include/access/pg_tde_xlog_smgr.h | 1 + contrib/pg_tde/src/pg_tde.c | 1 + src/bin/pg_rewind/pg_rewind.c | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index b8ba5d0237459..86723fdf4e5f6 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -19,11 +19,11 @@ #include "encryption/enc_tde.h" #include "pg_tde.h" #include "pg_tde_defines.h" -#include "pg_tde_guc.h" #ifdef FRONTEND #include "pg_tde_fe.h" #else +#include "pg_tde_guc.h" #include "port/atomics.h" #endif @@ -175,10 +175,15 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #endif /* !FRONTEND */ void -TDEXLogSmgrInit(void) +TDEXLogSmgrInit() +{ + SetXLogSmgr(&tde_xlog_smgr); +} + +void +TDEXLogSmgrInitWrite(bool encrypt_xlog) { #ifndef FRONTEND - /* TODO: move to the separate func, it's not an SMGR init */ InternalKey *key = pg_tde_read_last_wal_key(); /* @@ -186,7 +191,7 @@ TDEXLogSmgrInit(void) * attacks on CTR ciphers based on comparing the WAL generated by two * divergent copies of the same cluster. */ - if (EncryptXLog) + if (encrypt_xlog) { pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), TDE_KEY_TYPE_WAL_ENCRYPTED); @@ -208,7 +213,6 @@ TDEXLogSmgrInit(void) pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); #endif - SetXLogSmgr(&tde_xlog_smgr); } static ssize_t @@ -234,7 +238,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, pg_atomic_write_u64(&EncryptionState->enc_key_lsn, lsn); } - if (EncryptXLog) + if (EncryptionKey.type == TDE_KEY_TYPE_WAL_ENCRYPTED) return TDEXLogWriteEncryptedPages(fd, buf, count, offset, tli, segno); else #endif diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index cb714ed34438a..5956c4b9d12fd 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -10,5 +10,6 @@ extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); +extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); #endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 626d5493b8fb3..977ba0ccd35c8 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -68,6 +68,7 @@ tde_shmem_startup(void) PrincipalKeyShmemInit(); TDEXLogShmemInit(); TDEXLogSmgrInit(); + TDEXLogSmgrInitWrite(EncryptXLog); } void diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index f6726c1df485c..931dda9250150 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -376,7 +376,7 @@ main(int argc, char **argv) char tde_path[MAXPGPATH]; snprintf(tde_path, sizeof(tde_path), "%s/%s", datadir_target, PG_TDE_DATA_DIR); pg_tde_fe_init(tde_path); - TDEXLogSmgrInit(); + TDEXLogSmgrInit(); } #endif /* From 3d43051138101d963614629e3bcf24cb35d47a6d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Jul 2025 08:36:26 +0200 Subject: [PATCH 530/796] Make both WAL SMGR function arguments symmetric Only the read took the segment size while both writing and reading actually needs it. Either both or neither should take it as an argument. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 6 +++--- src/backend/access/transam/xlog.c | 4 ++-- src/backend/replication/walreceiver.c | 2 +- src/include/access/xlog_smgr.h | 4 ++-- src/include/pg_config_manual.h | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 86723fdf4e5f6..e351b218cf822 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -32,7 +32,7 @@ static ssize_t tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offs TimeLineID tli, XLogSegNo segno, int segSize); static ssize_t tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, - XLogSegNo segno); + XLogSegNo segno, int segSize); static const XLogSmgr tde_xlog_smgr = { .seg_read = tdeheap_xlog_seg_read, @@ -217,7 +217,7 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) static ssize_t tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, - TimeLineID tli, XLogSegNo segno) + TimeLineID tli, XLogSegNo segno, int segSize) { #ifndef FRONTEND @@ -231,7 +231,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, { XLogRecPtr lsn; - XLogSegNoOffsetToRecPtr(segno, offset, wal_segment_size, lsn); + XLogSegNoOffsetToRecPtr(segno, offset, segSize, lsn); pg_tde_wal_last_key_set_lsn(lsn, EncryptionState->db_map_path); EncryptionKey.start_lsn = lsn; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index c1d7988066ff6..e0e8d2daf0071 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -2446,7 +2446,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible) INSTR_TIME_SET_ZERO(start); pgstat_report_wait_start(WAIT_EVENT_WAL_WRITE); - written = xlog_smgr->seg_write(openLogFile, from, nleft, startoffset, tli, openLogSegNo); + written = xlog_smgr->seg_write(openLogFile, from, nleft, startoffset, tli, openLogSegNo, wal_segment_size); pgstat_report_wait_end(); /* @@ -3491,7 +3491,7 @@ XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno, } errno = 0; pgstat_report_wait_start(WAIT_EVENT_WAL_COPY_WRITE); - if ((int) xlog_smgr->seg_write(fd, buffer.data, sizeof(buffer), offset, destTLI, destsegno) != (int) sizeof(buffer)) + if ((int) xlog_smgr->seg_write(fd, buffer.data, sizeof(buffer), offset, destTLI, destsegno, wal_segment_size) != (int) sizeof(buffer)) { int save_errno = errno; diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index 973ef75f138d1..7cdfe1a648731 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -944,7 +944,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli) byteswritten = xlog_smgr->seg_write(recvFile, buf, segbytes, (off_t) startoff, recvFileTLI, - recvSegNo); + recvSegNo, wal_segment_size); if (byteswritten <= 0) { char xlogfname[MAXFNAMELEN]; diff --git a/src/include/access/xlog_smgr.h b/src/include/access/xlog_smgr.h index 808a07f502fc1..b1f7c4c425f48 100644 --- a/src/include/access/xlog_smgr.h +++ b/src/include/access/xlog_smgr.h @@ -12,12 +12,12 @@ typedef struct XLogSmgr TimeLineID tli, XLogSegNo segno, int segSize); ssize_t (*seg_write) (int fd, const void *buf, size_t count, off_t offset, - TimeLineID tli, XLogSegNo segno); + TimeLineID tli, XLogSegNo segno, int segSize); } XLogSmgr; static inline ssize_t default_seg_write(int fd, const void *buf, size_t count, off_t offset, - TimeLineID tli, XLogSegNo segno) + TimeLineID tli, XLogSegNo segno, int segSize) { return pg_pwrite(fd, buf, count, offset); } diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index 78353b03d70cb..588d6e987cd30 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -384,4 +384,4 @@ */ /* #define TRACE_SYNCSCAN */ -#define PERCONA_API_VERSION 1 +#define PERCONA_API_VERSION 2 From a329aeb6c509bc1f52bb8d9f14e004c3471f9ab2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 22 Jul 2025 11:44:33 +0200 Subject: [PATCH 531/796] Refactor WAL SMGR to make it easier to add WAL write support to fronend tools With ifdefs all over the place it was hard to expose the write functions to frontend tools so we reduce the number of ifdefs by having one clear set of data structures fror backend and one for frontend. Additionally we give access to WAL key generation and setting the start_lsn of a key to the frontend code. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 429 ++++++++++--------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 120 ++++-- contrib/pg_tde/src/include/pg_tde_fe.h | 2 + 3 files changed, 295 insertions(+), 256 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 6c1f146e306d3..e67d7169a51e0 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -54,23 +54,24 @@ typedef struct TDEFileHeader static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; +static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); +static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); +static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); +static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); +static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset); static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); -static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); #ifndef FRONTEND -static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); -static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) @@ -103,37 +104,6 @@ tde_sprint_key(InternalKey *k) return buf; } -/* - * Generates a new internal key for WAL and adds it to the key file. - * - * We have a special function for WAL as it is being called during recovery - * start so there should be no XLog records and aquired locks. The key is - * always created with start_lsn = InvalidXLogRecPtr. Which will be updated - * with the actual lsn by the first WAL write. - */ -void -pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type) -{ - TDEPrincipalKey *principal_key; - - LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); - if (principal_key == NULL) - { - ereport(ERROR, - errmsg("principal key not configured"), - errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); - } - - /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ - pg_tde_generate_internal_key(rel_key_data, entry_type); - - pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); - - LWLockRelease(tde_lwlock_enc_keys()); -} - /* * Deletes the key file for a given database. */ @@ -202,155 +172,6 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) CloseTransientFile(map_fd); } -/* - * Write TDE file header to a TDE file. - */ -static int -pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written) -{ - TDEFileHeader fheader; - - Assert(signed_key_info); - - fheader.file_version = PG_TDE_FILEMAGIC; - fheader.signed_key_info = *signed_key_info; - *bytes_written = pg_pwrite(fd, &fheader, TDE_FILE_HEADER_SIZE, 0); - - if (*bytes_written != TDE_FILE_HEADER_SIZE) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not write tde file \"%s\": %m", tde_filename)); - } - - if (pg_fsync(fd) != 0) - { - ereport(data_sync_elevel(ERROR), - errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", tde_filename)); - } - - ereport(DEBUG2, errmsg("Wrote the header to %s", tde_filename)); - - return fd; -} - -static void -pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) -{ - signed_key_info->data = principal_key->keyInfo; - - if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - - AesGcmEncrypt(principal_key->keyData, - signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), - NULL, 0, - NULL, - signed_key_info->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); -} - -static void -pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data) -{ - map_entry->spcOid = rlocator->spcOid; - map_entry->relNumber = rlocator->relNumber; - map_entry->type = rel_key_data->type; - map_entry->enc_key = *rel_key_data; - - if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); - - AesGcmEncrypt(principal_key->keyData, - map_entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), - rel_key_data->key, INTERNAL_KEY_LEN, - map_entry->enc_key.key, - map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); -} - -static void -pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path) -{ - int bytes_written = 0; - - bytes_written = pg_pwrite(fd, map_entry, MAP_ENTRY_SIZE, *offset); - - if (bytes_written != MAP_ENTRY_SIZE) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not write tde map file \"%s\": %m", db_map_path)); - } - if (pg_fsync(fd) != 0) - { - ereport(data_sync_elevel(ERROR), - errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", db_map_path)); - } - - *offset += bytes_written; -} - -/* - * The caller must hold an exclusive lock on the key file to avoid - * concurrent in place updates leading to data conflicts. - */ -void -pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key) -{ - char db_map_path[MAXPGPATH]; - int map_fd; - off_t curr_pos = 0; - TDEMapEntry write_map_entry; - TDESignedPrincipalKeyInfo signed_key_Info; - - Assert(rlocator); - - pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); - - pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); - - /* Open and validate file for basic correctness. */ - map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, false, &curr_pos); - - /* - * Read until we find an empty slot. Otherwise, read until end. This seems - * to be less frequent than vacuum. So let's keep this function here - * rather than overloading the vacuum process. - */ - while (1) - { - TDEMapEntry read_map_entry; - off_t prev_pos = curr_pos; - - if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) - { - curr_pos = prev_pos; - break; - } - - if (read_map_entry.type == MAP_ENTRY_EMPTY) - { - curr_pos = prev_pos; - break; - } - } - - /* Initialize map entry and encrypt key */ - pg_tde_initialize_map_entry(&write_map_entry, principal_key, rlocator, rel_key_data); - - /* Write the given entry at curr_pos; i.e. the free entry. */ - pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); - - CloseTransientFile(map_fd); -} - /* * Mark relation map entry as free and overwrite the key * @@ -541,6 +362,8 @@ pg_tde_delete_principal_key(Oid dbOid) durable_unlink(path, ERROR); } +#endif /* !FRONTEND */ + /* * It's called by seg_write inside crit section so no pallocs, hence * needs keyfile_path @@ -609,37 +432,152 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) CloseTransientFile(fd); } +static void +pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) +{ + signed_key_info->data = principal_key->keyInfo; + + if (!RAND_bytes(signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); + + AesGcmEncrypt(principal_key->keyData, + signed_key_info->sign_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) &signed_key_info->data, sizeof(signed_key_info->data), + NULL, 0, + NULL, + signed_key_info->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); +} + +static void +pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data) +{ + map_entry->spcOid = rlocator->spcOid; + map_entry->relNumber = rlocator->relNumber; + map_entry->type = rel_key_data->type; + map_entry->enc_key = *rel_key_data; + + if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for key map: %s", ERR_error_string(ERR_get_error(), NULL))); + + AesGcmEncrypt(principal_key->keyData, + map_entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), + rel_key_data->key, INTERNAL_KEY_LEN, + map_entry->enc_key.key, + map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); +} + +static void +pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path) +{ + int bytes_written = 0; + + bytes_written = pg_pwrite(fd, map_entry, MAP_ENTRY_SIZE, *offset); + + if (bytes_written != MAP_ENTRY_SIZE) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write tde map file \"%s\": %m", db_map_path)); + } + if (pg_fsync(fd) != 0) + { + ereport(data_sync_elevel(ERROR), + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", db_map_path)); + } + + *offset += bytes_written; +} + /* - * Open for write and Validate File Header: - * header: {Format Version, Principal Key Name} + * Generates a new internal key for WAL and adds it to the key file. * - * Returns the file descriptor in case of a success. Otherwise, error - * is raised. + * We have a special function for WAL as it is being called during recovery + * start so there should be no XLog records and aquired locks. The key is + * always created with start_lsn = InvalidXLogRecPtr. Which will be updated + * with the actual lsn by the first WAL write. */ -static int -pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos) +void +pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type) { - int fd; - TDEFileHeader fheader; - off_t bytes_read = 0; - off_t bytes_written = 0; - int file_flags = O_RDWR | O_CREAT | PG_BINARY | (truncate ? O_TRUNC : 0); + TDEPrincipalKey *principal_key; - Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - fd = pg_tde_open_file_basic(tde_filename, file_flags, false); + principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); + if (principal_key == NULL) + { + ereport(ERROR, + errmsg("principal key not configured"), + errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); + } - pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read); + /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ + pg_tde_generate_internal_key(rel_key_data, entry_type); - /* In case it's a new file, let's add the header now. */ - if (bytes_read == 0 && signed_key_info) - pg_tde_file_header_write(tde_filename, fd, signed_key_info, &bytes_written); + pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); - *curr_pos = bytes_read + bytes_written; - return fd; + LWLockRelease(tde_lwlock_enc_keys()); } -#endif /* !FRONTEND */ +/* + * The caller must hold an exclusive lock on the key file to avoid + * concurrent in place updates leading to data conflicts. + */ +void +pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key) +{ + char db_map_path[MAXPGPATH]; + int map_fd; + off_t curr_pos = 0; + TDEMapEntry write_map_entry; + TDESignedPrincipalKeyInfo signed_key_Info; + + Assert(rlocator); + + pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); + + pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + + /* Open and validate file for basic correctness. */ + map_fd = pg_tde_open_file_write(db_map_path, &signed_key_Info, false, &curr_pos); + + /* + * Read until we find an empty slot. Otherwise, read until end. This seems + * to be less frequent than vacuum. So let's keep this function here + * rather than overloading the vacuum process. + */ + while (1) + { + TDEMapEntry read_map_entry; + off_t prev_pos = curr_pos; + + if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) + { + curr_pos = prev_pos; + break; + } + + if (read_map_entry.type == MAP_ENTRY_EMPTY) + { + curr_pos = prev_pos; + break; + } + } + + /* Initialize map entry and encrypt key */ + pg_tde_initialize_map_entry(&write_map_entry, principal_key, rlocator, rel_key_data); + + /* Write the given entry at curr_pos; i.e. the free entry. */ + pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); + + CloseTransientFile(map_fd); +} /* * Returns true if we find a valid match; e.g. type is not set to @@ -739,6 +677,28 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) return rel_key_data; } +/* + * Open a TDE file: + * + * Returns the file descriptor in case of a success. Otherwise, error + * is raised except when ignore_missing is true and the file does not exit. + */ +static int +pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing) +{ + int fd; + + fd = OpenTransientFile(tde_filename, fileFlags); + if (fd < 0 && !(errno == ENOENT && ignore_missing == true)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not open tde file \"%s\": %m", tde_filename)); + } + + return fd; +} + /* * Open for read and Validate File Header: * header: {Format Version, Principal Key Name} @@ -766,24 +726,32 @@ pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr } /* - * Open a TDE file: + * Open for write and Validate File Header: + * header: {Format Version, Principal Key Name} * * Returns the file descriptor in case of a success. Otherwise, error - * is raised except when ignore_missing is true and the file does not exit. + * is raised. */ static int -pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing) +pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos) { int fd; + TDEFileHeader fheader; + off_t bytes_read = 0; + off_t bytes_written = 0; + int file_flags = O_RDWR | O_CREAT | PG_BINARY | (truncate ? O_TRUNC : 0); - fd = OpenTransientFile(tde_filename, fileFlags); - if (fd < 0 && !(errno == ENOENT && ignore_missing == true)) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not open tde file \"%s\": %m", tde_filename)); - } + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + fd = pg_tde_open_file_basic(tde_filename, file_flags, false); + + pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read); + + /* In case it's a new file, let's add the header now. */ + if (bytes_read == 0 && signed_key_info) + pg_tde_file_header_write(tde_filename, fd, signed_key_info, &bytes_written); + + *curr_pos = bytes_read + bytes_written; return fd; } @@ -810,6 +778,39 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader } } +/* + * Write TDE file header to a TDE file. + */ +static int +pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written) +{ + TDEFileHeader fheader; + + Assert(signed_key_info); + + fheader.file_version = PG_TDE_FILEMAGIC; + fheader.signed_key_info = *signed_key_info; + *bytes_written = pg_pwrite(fd, &fheader, TDE_FILE_HEADER_SIZE, 0); + + if (*bytes_written != TDE_FILE_HEADER_SIZE) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write tde file \"%s\": %m", tde_filename)); + } + + if (pg_fsync(fd) != 0) + { + ereport(data_sync_elevel(ERROR), + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", tde_filename)); + } + + ereport(DEBUG2, errmsg("Wrote the header to %s", tde_filename)); + + return fd; +} + /* * Returns true if a map entry if found or false if we have reached the end of * the file. diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index e351b218cf822..3edef134b790d 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -39,8 +39,14 @@ static const XLogSmgr tde_xlog_smgr = { .seg_write = tdeheap_xlog_seg_write, }; -#ifndef FRONTEND -static Size TDEXLogEncryptBuffSize(void); +static void *EncryptionCryptCtx = NULL; + +/* TODO: can be swapped out to the disk */ +static InternalKey EncryptionKey = +{ + .type = MAP_ENTRY_EMPTY, + .start_lsn = InvalidXLogRecPtr, +}; /* * Must be the same as in replication/walsender.c @@ -49,9 +55,13 @@ static Size TDEXLogEncryptBuffSize(void); */ #define MAX_SEND_SIZE (XLOG_BLCKSZ * 16) -static ssize_t TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, - off_t offset, TimeLineID tli, - XLogSegNo segno); +/* + * Since the backend code needs to use atomics and shared memory while the + * frotnend code cannot do that we provide two separate implementations of some + * data structures and the functions which operate one them. + */ + +#ifndef FRONTEND typedef struct EncryptionStateData { @@ -60,15 +70,22 @@ typedef struct EncryptionStateData } EncryptionStateData; static EncryptionStateData *EncryptionState = NULL; + static char *EncryptionBuf; -/* TODO: can be swapped out to the disk */ -static InternalKey EncryptionKey = +static XLogRecPtr +TDEXLogGetEncKeyLsn() { - .type = MAP_ENTRY_EMPTY, - .start_lsn = InvalidXLogRecPtr, -}; -static void *EncryptionCryptCtx = NULL; + return (XLogRecPtr) pg_atomic_read_u64(&EncryptionState->enc_key_lsn); +} + +static void +TDEXLogSetEncKeyLsn(XLogRecPtr start_lsn) +{ + pg_atomic_write_u64(&EncryptionState->enc_key_lsn, start_lsn); +} + +static Size TDEXLogEncryptBuffSize(void); static int XLOGChooseNumBuffers(void); @@ -146,33 +163,33 @@ TDEXLogShmemInit(void) elog(DEBUG1, "pg_tde: initialized encryption buffer %lu bytes", TDEXLogEncryptStateSize()); } -/* - * Encrypt XLog page(s) from the buf and write to the segment file. - */ -static ssize_t -TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, - TimeLineID tli, XLogSegNo segno) +#else /* !FRONTEND */ + +typedef struct EncryptionStateData { - char iv_prefix[16]; - InternalKey *key = &EncryptionKey; - char *enc_buff = EncryptionBuf; + char db_map_path[MAXPGPATH]; + XLogRecPtr enc_key_lsn; /* to sync with reader */ +} EncryptionStateData; - Assert(count <= TDEXLogEncryptBuffSize()); +static EncryptionStateData EncryptionStateD = {0}; -#ifdef TDE_XLOG_DEBUG - elog(DEBUG1, "write encrypted WAL, size: %lu, offset: %ld [%lX], seg: %X/%X, key_start_lsn: %X/%X", - count, offset, offset, LSN_FORMAT_ARGS(segno), LSN_FORMAT_ARGS(key->start_lsn)); -#endif +static EncryptionStateData *EncryptionState = &EncryptionStateD; - CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); - pg_tde_stream_crypt(iv_prefix, offset, - (char *) buf, count, - enc_buff, key, &EncryptionCryptCtx); +static char EncryptionBuf[MAX_SEND_SIZE]; - return pg_pwrite(fd, enc_buff, count, offset); +static XLogRecPtr +TDEXLogGetEncKeyLsn() +{ + return (XLogRecPtr) EncryptionState->enc_key_lsn; +} + +static void +TDEXLogSetEncKeyLsn(XLogRecPtr start_lsn) +{ + EncryptionState->enc_key_lsn = EncryptionKey.start_lsn; } -#endif /* !FRONTEND */ +#endif /* FRONTEND */ void TDEXLogSmgrInit() @@ -183,7 +200,6 @@ TDEXLogSmgrInit() void TDEXLogSmgrInitWrite(bool encrypt_xlog) { -#ifndef FRONTEND InternalKey *key = pg_tde_read_last_wal_key(); /* @@ -204,30 +220,53 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) else if (key) { EncryptionKey = *key; - pg_atomic_write_u64(&EncryptionState->enc_key_lsn, EncryptionKey.start_lsn); + TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn); } if (key) pfree(key); pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); +} + +/* + * Encrypt XLog page(s) from the buf and write to the segment file. + */ +static ssize_t +TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, + TimeLineID tli, XLogSegNo segno) +{ + char iv_prefix[16]; + InternalKey *key = &EncryptionKey; + char *enc_buff = EncryptionBuf; +#ifndef FRONTEND + Assert(count <= TDEXLogEncryptBuffSize()); #endif + +#ifdef TDE_XLOG_DEBUG + elog(DEBUG1, "write encrypted WAL, size: %lu, offset: %ld [%lX], seg: %X/%X, key_start_lsn: %X/%X", + count, offset, offset, LSN_FORMAT_ARGS(segno), LSN_FORMAT_ARGS(key->start_lsn)); +#endif + + CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); + pg_tde_stream_crypt(iv_prefix, offset, + (char *) buf, count, + enc_buff, key, &EncryptionCryptCtx); + + return pg_pwrite(fd, enc_buff, count, offset); } static ssize_t tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize) { -#ifndef FRONTEND - /* * Set the last (most recent) key's start LSN if not set. * * This func called with WALWriteLock held, so no need in any extra sync. */ - if (EncryptionKey.type != MAP_ENTRY_EMPTY && - pg_atomic_read_u64(&EncryptionState->enc_key_lsn) == 0) + if (EncryptionKey.type != MAP_ENTRY_EMPTY && TDEXLogGetEncKeyLsn() == 0) { XLogRecPtr lsn; @@ -235,13 +274,12 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, pg_tde_wal_last_key_set_lsn(lsn, EncryptionState->db_map_path); EncryptionKey.start_lsn = lsn; - pg_atomic_write_u64(&EncryptionState->enc_key_lsn, lsn); + TDEXLogSetEncKeyLsn(lsn); } if (EncryptionKey.type == TDE_KEY_TYPE_WAL_ENCRYPTED) return TDEXLogWriteEncryptedPages(fd, buf, count, offset, tli, segno); else -#endif return pg_pwrite(fd, buf, count, offset); } @@ -274,8 +312,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, keys = pg_tde_fetch_wal_keys(InvalidXLogRecPtr); } -#ifndef FRONTEND - write_key_lsn = pg_atomic_read_u64(&EncryptionState->enc_key_lsn); + write_key_lsn = TDEXLogGetEncKeyLsn(); if (!XLogRecPtrIsInvalid(write_key_lsn)) { @@ -292,7 +329,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, keys = pg_tde_get_wal_cache_keys(); } } -#endif XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start); XLogSegNoOffsetToRecPtr(segno, offset + readsz, segSize, data_end); diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index 6ef811b2b98ec..86d4f2377998d 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -67,6 +67,8 @@ static int tde_fe_error_level = 0; +#define data_sync_elevel(elevel) (elevel) + /* * ------------- */ From d0a0bba3d243ab389ee4cf40116828918452da0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 23 Jul 2025 10:23:44 +0200 Subject: [PATCH 532/796] Fix error message when deleting key providers Error messages are not supposed to be proper sentences, and according to the error style guide "cannot" is preferable over "can't". Also add an appropriate errcode. --- contrib/pg_tde/expected/default_principal_key.out | 2 +- contrib/pg_tde/expected/key_provider.out | 4 ++-- contrib/pg_tde/src/catalog/tde_keyring.c | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/expected/default_principal_key.out b/contrib/pg_tde/expected/default_principal_key.out index 72ec579b6de3b..21319d568ea60 100644 --- a/contrib/pg_tde/expected/default_principal_key.out +++ b/contrib/pg_tde/expected/default_principal_key.out @@ -45,7 +45,7 @@ SELECT provider_id, provider_name, key_name -- fails SELECT pg_tde_delete_global_key_provider('file-provider'); -ERROR: Can't delete a provider which is currently in use +ERROR: cannot delete provider which is currently in use SELECT id, name FROM pg_tde_list_all_global_key_providers(); id | name ----+--------------- diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 5563df5aa4d9c..7be4f59044fd2 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -91,7 +91,7 @@ SELECT id, name FROM pg_tde_list_all_global_key_providers(); -- fails SELECT pg_tde_delete_database_key_provider('file-provider'); -ERROR: Can't delete a provider which is currently in use +ERROR: cannot delete provider which is currently in use SELECT id, name FROM pg_tde_list_all_database_key_providers(); id | name ----+---------------- @@ -127,7 +127,7 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key', 'file-keyring'); -- fails SELECT pg_tde_delete_global_key_provider('file-keyring'); -ERROR: Can't delete a provider which is currently in use +ERROR: cannot delete provider which is currently in use SELECT id, name FROM pg_tde_list_all_global_key_providers(); id | name ----+--------------- diff --git a/contrib/pg_tde/src/catalog/tde_keyring.c b/contrib/pg_tde/src/catalog/tde_keyring.c index 0f7b53164c1e2..4ca489885058c 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring.c +++ b/contrib/pg_tde/src/catalog/tde_keyring.c @@ -309,7 +309,8 @@ pg_tde_delete_key_provider_internal(PG_FUNCTION_ARGS, Oid db_oid) if (provider_used) { ereport(ERROR, - errmsg("Can't delete a provider which is currently in use")); + errcode(ERRCODE_OBJECT_IN_USE), + errmsg("cannot delete provider which is currently in use")); } delete_key_provider_info(provider_name, db_oid); From 96e4944ee3ab9a3f478061a99c0da8fef6595ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 24 Jul 2025 10:10:01 +0200 Subject: [PATCH 533/796] Add errcode when failing to unset principal key ERRCODE_OBJECT_IN_USE seems appropriate for this as it's also used for things like replication slots and not only database objects. --- contrib/pg_tde/src/catalog/tde_principal_key.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index d795eb014ee61..178461905a93c 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -706,6 +706,7 @@ pg_tde_delete_key(PG_FUNCTION_ARGS) if (default_principal_key == NULL) { ereport(ERROR, + errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete principal key"), errdetail("There are encrypted tables in the database."), errhint("Set default principal key as fallback option or decrypt all tables before deleting principal key.")); @@ -718,6 +719,7 @@ pg_tde_delete_key(PG_FUNCTION_ARGS) if (pg_tde_is_same_principal_key(principal_key, default_principal_key)) { ereport(ERROR, + errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete principal key"), errdetail("There are encrypted tables in the database.")); } @@ -788,6 +790,7 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) if (pg_tde_count_encryption_keys(dbOid) != 0) { ereport(ERROR, + errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete default principal key"), errhint("There are encrypted tables in the database with id: %u.", dbOid)); } @@ -806,6 +809,7 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) { if (pg_tde_count_encryption_keys(GLOBAL_DATA_TDE_OID) != 0) ereport(ERROR, + errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete default principal key"), errhint("There are WAL encryption keys.")); dbs = lappend_oid(dbs, GLOBAL_DATA_TDE_OID); From 516129122e79fa617ff7ba99af739fcdbcb68a85 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 25 Jul 2025 18:01:47 +0300 Subject: [PATCH 534/796] Reorganize toc / overview chapter / update topic headings (#468) Reorganized how the ToC website interaction is done by renaming index.md from CLI, architecture, advanced topics, KMS and Overview (index folder). This meant updating links in multiple other files as index files needed to be renamed for the structure to work. Reorganized how Overview is displayed, removed RC2 mention in Limitations, removed important note as it is no longer needed since it was an RC2 mention. Reworded the button texts for better interaction and user expectation in the Overview chapter topics. Added a short intro for Benefits of pg_tde topic, rewrote admonition. Updated KMS titles to reflect Percona Style Guide. --- .../{index.md => tech-reference.md} | 4 +- .../{index.md => architecture.md} | 2 +- .../{index.md => cli-tools.md} | 2 +- contrib/pg_tde/documentation/docs/faq.md | 2 +- contrib/pg_tde/documentation/docs/features.md | 2 +- .../keyring.md | 2 +- .../kmip-fortanix.md | 2 +- .../kmip-openbao.md | 2 +- .../kmip-server.md | 2 +- .../kmip-thales.md | 2 +- .../{index.md => overview.md} | 6 ++- .../set-principal-key.md | 2 +- .../vault.md | 2 +- .../docs/how-to/multi-tenant-setup.md | 10 ++--- contrib/pg_tde/documentation/docs/index.md | 6 +-- .../documentation/docs/index/about-tde.md | 12 ++++++ .../docs/index/how-does-tde-work.md | 6 +-- .../documentation/docs/index/how-tde-helps.md | 10 +++-- .../pg_tde/documentation/docs/index/index.md | 7 ---- .../docs/index/supported-versions.md | 4 +- .../docs/index/table-access-method.md | 6 +-- .../documentation/docs/index/tde-encrypts.md | 6 +-- .../docs/index/tde-limitations.md | 9 ++-- contrib/pg_tde/documentation/docs/install.md | 2 +- .../docs/release-notes/alpha1.md | 2 +- .../documentation/docs/release-notes/beta.md | 2 +- .../documentation/docs/release-notes/beta2.md | 2 +- .../documentation/docs/release-notes/rc.md | 2 +- .../documentation/docs/release-notes/rc2.md | 2 +- contrib/pg_tde/documentation/docs/setup.md | 2 +- .../documentation/docs/wal-encryption.md | 2 +- contrib/pg_tde/documentation/mkdocs.yml | 42 +++++++++---------- .../snippets/kms-considerations.md | 2 +- 33 files changed, 86 insertions(+), 82 deletions(-) rename contrib/pg_tde/documentation/docs/advanced-topics/{index.md => tech-reference.md} (56%) rename contrib/pg_tde/documentation/docs/architecture/{index.md => architecture.md} (99%) rename contrib/pg_tde/documentation/docs/command-line-tools/{index.md => cli-tools.md} (93%) rename contrib/pg_tde/documentation/docs/global-key-provider-configuration/{index.md => overview.md} (79%) create mode 100644 contrib/pg_tde/documentation/docs/index/about-tde.md delete mode 100644 contrib/pg_tde/documentation/docs/index/index.md diff --git a/contrib/pg_tde/documentation/docs/advanced-topics/index.md b/contrib/pg_tde/documentation/docs/advanced-topics/tech-reference.md similarity index 56% rename from contrib/pg_tde/documentation/docs/advanced-topics/index.md rename to contrib/pg_tde/documentation/docs/advanced-topics/tech-reference.md index 9df169821bcd3..309a0b1bf73aa 100644 --- a/contrib/pg_tde/documentation/docs/advanced-topics/index.md +++ b/contrib/pg_tde/documentation/docs/advanced-topics/tech-reference.md @@ -1,7 +1,7 @@ -# Technical Reference +# Overview of technical capabilities This section covers the internal components and tools that power `pg_tde`. Use it to understand how encryption is implemented, fine-tune a configuration, leverage advanced CLI tools and functions for diagnostics and customization. -[Architecture](../architecture/index.md){.md-button} [GUC Variables](../variables.md){.md-button} [Functions](../functions.md){.md-button} +[Architecture](../architecture/architecture.md){.md-button} [GUC Variables](../variables.md){.md-button} [Functions](../functions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/architecture/index.md b/contrib/pg_tde/documentation/docs/architecture/architecture.md similarity index 99% rename from contrib/pg_tde/documentation/docs/architecture/index.md rename to contrib/pg_tde/documentation/docs/architecture/architecture.md index 76bc66e3c9ae4..bc1df2f25df6e 100644 --- a/contrib/pg_tde/documentation/docs/architecture/index.md +++ b/contrib/pg_tde/documentation/docs/architecture/architecture.md @@ -167,7 +167,7 @@ Key provider configuration or location may change. For example, a service is mov In certain cases you can't use SQL functions to manage key providers. For example, if the key provider changed while the server wasn't running and is therefore unaware of these changes. The startup can fail if it needs to access the encryption keys. -For such situations, `pg_tde` also provides [command line tools](../command-line-tools/index.md) to recover the database. +For such situations, `pg_tde` also provides [command line tools](../command-line-tools/cli-tools.md) to recover the database. ### Sensitive key provider information diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/index.md b/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md similarity index 93% rename from contrib/pg_tde/documentation/docs/command-line-tools/index.md rename to contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md index b854eab1b304a..e0edeb4b5de32 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/index.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md @@ -1,4 +1,4 @@ -# pg_tde CLI Tools +# Overview of pg_tde CLI tools The `pg_tde` extension introduces new command-line utilities and extends some existing PostgreSQL tools to support encrypted WAL and tables. These include: diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index 36430b722a1c4..b244818371144 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -142,7 +142,7 @@ You must restart the database in the following cases to apply the changes: * after you enabled the `pg_tde` extension * when enabling WAL encryption, which is currently in beta. **Do not enable this feature in production environments**. -After that, no database restart is required. When you create or alter the table using the `tde_heap` access method, the files are marked as those that require encryption. The encryption happens at the storage manager level, before a transaction is written to disk. Read more about [how tde_heap works](index/table-access-method.md#how-tde_heap-works). +After that, no database restart is required. When you create or alter the table using the `tde_heap` access method, the files are marked as those that require encryption. The encryption happens at the storage manager level, before a transaction is written to disk. Read more about [how tde_heap works](index/table-access-method.md#how-tde_heap-works-with-pg_tde). ## What happens to my data if I lose a principal key? diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index 8df33533bfa4d..aa8703fc42bca 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -19,4 +19,4 @@ The following features are available for the extension: * Table-level granularity for encryption and access control * Multiple [Key management options](global-key-provider-configuration/index.md) -[What is Transparent Data Encryption (TDE)?](index/index.md){.md-button} [Install pg_tde to get started](install.md){.md-button} +[Learn more about TDE and pg_tde :material-arrow-right:](index/about-tde.md){.md-button} [Get started with installation :material-arrow-right:](install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md index 82829aefd1d12..fd60e4fcf220c 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/keyring.md @@ -1,4 +1,4 @@ -# Keyring File Configuration +# Keyring file configuration This setup is intended for development and stores the keys unencrypted in the specified data file. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md index edc09daf51af8..db08752133248 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-fortanix.md @@ -1,4 +1,4 @@ -# Fortanix KMIP Server Configuration +# Fortanix KMIP server configuration `pg_tde` is compatible with Fortanix Data Security Manager (DSM) via the KMIP protocol. For a full setup guide, see [the Fortanix KMIP documentation here](https://support.fortanix.com/docs/users-guide-account-client-configurations?highlight=KMIP#23-kmip-clients). diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md index 7b11a694a3d26..91bc1cefd3b7d 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-openbao.md @@ -1,4 +1,4 @@ -# Using OpenBao as a Key Provider +# Using OpenBao as a key provider You can configure `pg_tde` to use OpenBao as a global key provider for managing encryption keys securely. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md index 46d93e7f31a63..488ca0007749b 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-server.md @@ -1,4 +1,4 @@ -# KMIP Configuration +# KMIP configuration To use a Key Management Interoperability Protocol (KMIP) server with `pg_tde`, you must configure it as a global key provider. This setup enables `pg_tde` to securely fetch and manage encryption keys from a centralized key management appliance. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md index daf6bd5e16039..d31ba22e9fdd2 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/kmip-thales.md @@ -1,4 +1,4 @@ -# Thales KMIP Server Configuration +# Thales KMIP server configuration `pg_tde` is compatible with the Thales CipherTrust Manager via the KMIP protocol. For a full setup guide, see [the Thales documentation](https://thalesdocs.com/ctp/cm/2.19/reference/kmip-ref/index.html?). diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md similarity index 79% rename from contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md rename to contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md index f8fbf49d5ab29..800f505925715 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/index.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md @@ -1,4 +1,4 @@ -# Configure Key Management (KMS) +# Key management overview In production environments, storing encryption keys locally on the PostgreSQL server can introduce security risks. To enhance security, `pg_tde` supports integration with external Key Management Systems (KMS) through a Global Key Provider interface. @@ -13,4 +13,6 @@ To use an external KMS with `pg_tde`, follow these two steps: Select your prefered configuration from the links below: -[KMIP Configuration :material-arrow-right:](kmip-server.md){.md-button} [Vault Configuration :material-arrow-right:](vault.md){.md-button} [Keyring File Configuration (not recommended) :material-arrow-right:](keyring.md){.md-button} +[KMIP Configuration :material-arrow-right:](kmip-server.md){.md-button} +[Vault Configuration :material-arrow-right:](vault.md){.md-button} +[Keyring File Configuration (not recommended) :material-arrow-right:](keyring.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index c069f44e98209..8bee0e15ca0f5 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -1,4 +1,4 @@ -# Global Principal Key Configuration +# Global Principal Key configuration You can configure a default principal key using a global key provider. This key will be used by all databases that do not have their own encryption keys configured. The function **both** sets the principal key and rotates internal keys as needed. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index d60024a62e7c3..00bac8074d457 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -1,4 +1,4 @@ -# Vault Configuration +# Vault configuration You can configure `pg_tde` to use HashiCorp Vault as a global key provider for managing encryption keys securely. diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index c8eaac1afcfe8..c20a8f640731c 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -2,9 +2,9 @@ The steps below describe how to set up multi-tenancy with `pg_tde`. Multi-tenancy allows you to encrypt different databases with different keys. This provides granular control over data and enables you to introduce different security policies and access controls for each database so that only authorized users of specific databases have access to the data. -If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Configure pg_tde](../setup.md) section. +If you don't need multi-tenancy, use the global key provider. See the configuration steps from the [Configure pg_tde :octicons-link-external-16:](../setup.md) section. -For how to enable WAL encryption, refer to the [Configure WAL Encryption](../wal-encryption.md) section. +For how to enable WAL encryption, refer to the [Configure WAL Encryption :octicons-link-external-16:](../wal-encryption.md) section. --8<-- "kms-considerations.md" @@ -32,7 +32,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo sudo systemctl restart postgresql-17 ``` -3. Create the extension using the [CREATE EXTENSION](https://www.postgresql.org/docs/current/sql-createextension.html) command. You must have the privileges of a superuser or a database owner to use this command. Connect to `psql` as a superuser for a database and run the following command: +3. Create the extension using the [CREATE EXTENSION :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-createextension.html) command. You must have the privileges of a superuser or a database owner to use this command. Connect to `psql` as a superuser for a database and run the following command: ``` CREATE EXTENSION pg_tde; @@ -50,7 +50,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo ## Key provider configuration -You must do these steps for every database where you have created the extension. For more information on configurations, please see the [Configure Key Management (KMS)](../global-key-provider-configuration/index.md) topic. +You must do these steps for every database where you have created the extension. For more information on configurations, please see the [Configure Key Management (KMS) :octicons-link-external-16:](../global-key-provider-configuration/overview.md) topic. 1. Set up a key provider. @@ -58,7 +58,7 @@ You must do these steps for every database where you have created the extension. The KMIP server setup is out of scope of this document. - Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). + Make sure you have obtained the root certificate for the KMIP server and the keypair for the client. The client key needs permissions to create / read keys on the server. Find the [configuration guidelines for the HashiCorp Vault Enterprise KMIP Secrets Engine :octicons-link-external-16:](https://developer.hashicorp.com/vault/tutorials/enterprise/kmip-engine). For testing purposes, you can use the PyKMIP server which enables you to set up required certificates. To use a real KMIP server, make sure to obtain the valid certificates issued by the key management appliance. diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 5f2a091a250ff..21e9058e3736a 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -6,6 +6,6 @@ Percona Transparent Data Encryption for PostgreSQL (`pg_tde`) is an open source, There is no safe upgrade path from the previous versions, such as Release Candidate 2, to the General Availability (GA) version of `pg_tde`. We recommend starting with a **clean installation** for GA deployments. Avoid using RC environments in production. -[Overview](index/index.md){.md-button} -[Get Started](install.md){.md-button} -[What's new in pg_tde {{release}}](release-notes/release-notes.md){.md-button} +[Learn more about TDE and pg_tde :material-arrow-right:](index/about-tde.md){.md-button} +[Get started with installation :material-arrow-right:](install.md){.md-button} +[Check out what's new in pg_tde {{release}} :material-arrow-right:](release-notes/release-notes.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/about-tde.md b/contrib/pg_tde/documentation/docs/index/about-tde.md new file mode 100644 index 0000000000000..e2952ece5b8d2 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/index/about-tde.md @@ -0,0 +1,12 @@ +# About Transparent Data Encryption + +Transparent Data Encryption (TDE) protects your data at rest by ensuring that even if the underlying storage is compromised, the data remains unreadable without the correct decryption keys. This is especially critical for environments handling sensitive, regulated, or high-value information. + +This process runs transparently in the background, with minimal impact on database operations. + +Use the links below to explore how `pg_tde` helps secure your PostgreSQL deployments: + +[Discover the benefits of pg_tde :material-arrow-right:](how-tde-helps.md){.md-button} +[Understand pg_tde limitations :material-arrow-right:](tde-limitations.md){.md-button} +[View the full feature list :material-arrow-right:](../features.md){.md-button} +[Get started with installation :material-arrow-right:](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md index e21bdffbccfe2..8bce6c474c017 100644 --- a/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md +++ b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md @@ -1,4 +1,4 @@ -# How TDE Works +# How pg_tde works To encrypt the data, two types of keys are used: @@ -7,7 +7,7 @@ To encrypt the data, two types of keys are used: !!! note - For more information on managing and storing principal keys externally, see [Configure Global Key Provider](../global-key-provider-configuration/index.md). + For more information on managing and storing principal keys externally, see [Configure Global Key Provider](../global-key-provider-configuration/overview.md). You have the following options to store and manage principal keys externally: @@ -24,4 +24,4 @@ The internal key itself is encrypted using the principal key. The principal key Similarly when the user queries the encrypted table, the principal key is retrieved from the key store to decrypt the internal key. Then the same unique internal key for that table is used to decrypt the data, and unencrypted data gets returned to the user. So, effectively, every TDE table has a unique key, and each table key is encrypted using the principal key. -[Encrypted Data Scope](tde-encrypts.md){.md-button} +[Understand the encrypted data scope :material-arrow-right:](tde-encrypts.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/how-tde-helps.md b/contrib/pg_tde/documentation/docs/index/how-tde-helps.md index d4397dc330043..1f213c20b2e0b 100644 --- a/contrib/pg_tde/documentation/docs/index/how-tde-helps.md +++ b/contrib/pg_tde/documentation/docs/index/how-tde-helps.md @@ -1,4 +1,6 @@ -# TDE Benefits +# Benefits of pg_tde + +The benefits of using `pg_tde` are outlined below for different users and organizations. ## Benefits for organizations @@ -11,10 +13,10 @@ * **Operational simplicity:** Works transparently without requiring major application changes. * **Defense in depth:** Adds another layer of protection to existing controls like TLS (encryption in transit), access control, and role-based permissions. -When combined with the external Key Management Systems (KMS), TDE enables centralized control, auditing, and rotation of encryption keys—critical for secure production environments. +When combined with the external Key Management Systems (KMS), `pg_tde` enables centralized control, auditing, and rotation of encryption keys—critical for secure production environments. !!! admonition "See also" - Percona Blog: [Transparent Data Encryption (TDE)](https://www.percona.com/blog/transparent-data-encryption-tde/) + You can find more information on Transparent Data Encryption (TDE) in [this article](https://www.percona.com/blog/transparent-data-encryption-tde/). -[How TDE works](how-does-tde-work.md){.md-button} +[Learn how pg_tde works :material-arrow-right:](how-does-tde-work.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/index.md b/contrib/pg_tde/documentation/docs/index/index.md deleted file mode 100644 index 8c988a00128d4..0000000000000 --- a/contrib/pg_tde/documentation/docs/index/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# What is Transparent Data Encryption (TDE)? - -Transparent Data Encryption (TDE) protects your data at rest by ensuring that even if the underlying storage is compromised, the data remains unreadable without the correct encryption keys. This is especially critical for environments handling sensitive, regulated, or high-value information. - -Encryption happens transparently in the background, with minimal impact on database operations. - -[TDE Benefits](how-tde-helps.md){.md-button} [Check the full feature list](../features.md){.md-button} [Get Started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/supported-versions.md b/contrib/pg_tde/documentation/docs/index/supported-versions.md index d943451450d58..7d31c7d45664f 100644 --- a/contrib/pg_tde/documentation/docs/index/supported-versions.md +++ b/contrib/pg_tde/documentation/docs/index/supported-versions.md @@ -1,4 +1,4 @@ -# Versions and Supported PostgreSQL Deployments +# Versions and Supported PostgreSQL deployments The `pg_tde` extension is available for [Percona Server for PostgreSQL 17.x](https://docs.percona.com/postgresql/17/postgresql-server.html), an open source, drop-in replacement for PostgreSQL Community. This version provides the `tde_heap` access method and offers [full encryption capabilities](../features.md), including encryption of tables, indexes and WAL data. @@ -18,4 +18,4 @@ By using our PostgreSQL distribution, you get: Still unsure which deployment fits your needs? [Contact our experts](https://www.percona.com/about/contact) to find the best solution for your environment. -[Get Started: Install pg_tde :material-arrow-right:](../install.md){.md-button} +[Get started with installation :material-arrow-right:](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/table-access-method.md b/contrib/pg_tde/documentation/docs/index/table-access-method.md index b308cc1aedbcb..90957facded97 100644 --- a/contrib/pg_tde/documentation/docs/index/table-access-method.md +++ b/contrib/pg_tde/documentation/docs/index/table-access-method.md @@ -1,4 +1,4 @@ -# Table Access Methods and TDE +# Table Access Methods and pg_tde A table access method is the way how PostgreSQL stores the data in a table. The default table access method is `heap`. PostgreSQL organizes data in a heap structure, meaning there is no particular order to the rows in the table. Each row is stored independently and identified by its unique row identifier (TID). @@ -28,7 +28,7 @@ CREATE TABLE table_name ( ) USING tde_heap; ``` -### How tde_heap works +### How tde_heap works with pg_tde The `tde_heap` access method works on top of the default `heap` access method and is a marker to point which tables require encryption. It uses the custom storage manager TDE SMGR, which becomes active only after you installed the `pg_tde` extension. @@ -88,4 +88,4 @@ Here is how you can set the new default table access method: SELECT pg_reload_conf(); ``` -[Limitations of TDE](tde-limitations.md){.md-button} +[Understand pg_tde's limitations :material-arrow-right:](tde-limitations.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md index 1bc2c70cef3e4..19e9357057c50 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-encrypts.md +++ b/contrib/pg_tde/documentation/docs/index/tde-encrypts.md @@ -1,4 +1,4 @@ -# Encrypted Data Scope +# Encrypted data scope `pg_tde` encrypts the following components: @@ -7,6 +7,4 @@ * **Write-Ahead Log (WAL) data** for the entire database cluster. This includes WAL data in encrypted and non-encrypted tables. * **Indexes** on encrypted tables. -!!! - -[Table Access Methods and TDE](table-access-method.md){.md-button} +[Check out the table access methods :material-arrow-right:](table-access-method.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index 271665a0f17be..8e3376671aea7 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -1,11 +1,8 @@ # Limitations of pg_tde -* Keys in the local keyfile are stored **unencrypted**. For better security we recommend using the [Key management storage](../global-key-provider-configuration/index.md). +The following are current limitations of `pg_tde`: + * System tables, which include statistics data and database statistics, are currently **not encrypted**. * The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. -* `pg_tde` RC 2 is incompatible with `pg_tde` Beta2 due to significant changes in code. There is no direct upgrade flow from one version to another. You must [uninstall](../how-to/uninstall.md) `pg_tde` Beta2 first and then [install](../install.md) and configure the new Release Candidate version. - -!!! important - This is the {{release}} version of the extension and **it is not meant for production use yet**. We encourage you to use it in testing environments and [provide your feedback](https://forums.percona.com/c/postgresql/pg-tde-transparent-data-encryption-tde/82). -[Versions and Supported PostgreSQL Deployments](supported-versions.md){.md-button} +[View the versions and supported deployments :material-arrow-right:](supported-versions.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/install.md b/contrib/pg_tde/documentation/docs/install.md index 273bb899cd29c..542c876c2726b 100644 --- a/contrib/pg_tde/documentation/docs/install.md +++ b/contrib/pg_tde/documentation/docs/install.md @@ -39,4 +39,4 @@ Follow the configuration steps below to continue: If you’ve already completed these steps, feel free to skip ahead to a later section: - [Configure Key Management (KMS)](global-key-provider-configuration/index.md){.md-button} [Validate Encryption with pg_tde](test.md){.md-button} [Configure WAL encryption](wal-encryption.md){.md-button} + [Configure Key Management (KMS)](global-key-provider-configuration/overview.md){.md-button} [Validate Encryption with pg_tde](test.md){.md-button} [Configure WAL encryption](wal-encryption.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/alpha1.md b/contrib/pg_tde/documentation/docs/release-notes/alpha1.md index 766c9e28b136e..401819f529ba1 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/alpha1.md +++ b/contrib/pg_tde/documentation/docs/release-notes/alpha1.md @@ -1,6 +1,6 @@ # pg_tde Alpha 1 (2024-03-28) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/beta.md b/contrib/pg_tde/documentation/docs/release-notes/beta.md index 11985962c0b20..f01faac5fc92e 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/beta.md +++ b/contrib/pg_tde/documentation/docs/release-notes/beta.md @@ -1,6 +1,6 @@ # pg_tde Beta (2024-06-30) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/beta2.md b/contrib/pg_tde/documentation/docs/release-notes/beta2.md index f134f6874b772..1cd220d7d4785 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/beta2.md +++ b/contrib/pg_tde/documentation/docs/release-notes/beta2.md @@ -1,6 +1,6 @@ # pg_tde Beta 2 (2024-12-16) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc.md b/contrib/pg_tde/documentation/docs/release-notes/rc.md index d3ff780ff9187..416801e1eaa07 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc.md @@ -1,6 +1,6 @@ # pg_tde Release Candidate 1 ({{date.RC}}) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/release-notes/rc2.md b/contrib/pg_tde/documentation/docs/release-notes/rc2.md index c2d174f7e1f55..0d25d1d49b591 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/rc2.md +++ b/contrib/pg_tde/documentation/docs/release-notes/rc2.md @@ -1,6 +1,6 @@ # pg_tde Release Candidate 2 ({{date.RC2}}) -`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +`pg_tde` extension brings in [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get Started](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/setup.md b/contrib/pg_tde/documentation/docs/setup.md index 73cafba216c50..8b0d1f24136c3 100644 --- a/contrib/pg_tde/documentation/docs/setup.md +++ b/contrib/pg_tde/documentation/docs/setup.md @@ -65,4 +65,4 @@ psql -d template1 -c 'CREATE EXTENSION pg_tde;' ## Next steps -[Configure Key Management (KMS) :material-arrow-right:](global-key-provider-configuration/index.md){.md-button} +[Configure Key Management (KMS) :material-arrow-right:](global-key-provider-configuration/overview.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 68cfa9c65e864..33547d0e359c4 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -117,4 +117,4 @@ Before enabling WAL encryption, follow the steps below to create a principal key Now WAL files start to be encrypted for both encrypted and unencrypted tables. For more technical references related to architecture, variables or functions, see: -[Technical Reference](advanced-topics/index.md){.md-button} +[Technical Reference](advanced-topics/tech-reference.md){.md-button} diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 810425f44bcfa..43725bb5de5e6 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -162,13 +162,13 @@ nav: - get-help.md - "Features": features.md - "Overview": - - "What is Transparent Data Encryption (TDE)?": - - "TDE overview": index/index.md - - "TDE benefits": index/how-tde-helps.md - - "How TDE works": index/how-does-tde-work.md + - "About Transparent Data Encryption": index/about-tde.md + - "About pg_tde": + - "Benefits of pg_tde": index/how-tde-helps.md + - "How pg_tde works": index/how-does-tde-work.md - "Encrypted data scope": index/tde-encrypts.md - - "Table access methods and TDE": index/table-access-method.md - - "Limitations of TDE": index/tde-limitations.md + - "Table access methods and pg_tde": index/table-access-method.md + - "Limitations of pg_tde": index/tde-limitations.md - "Versions and supported PostgreSQL deployments": index/supported-versions.md - "Get started": - "1. Install pg_tde": install.md @@ -176,25 +176,25 @@ nav: - "1.2 Via yum": yum.md - "2. Configure pg_tde": setup.md - "2.1 Configure Key Management (KMS)": - - "2.1 Configure Key Management (KMS)": global-key-provider-configuration/index.md - - "KMIP Configuration": global-key-provider-configuration/kmip-server.md - - "Fortanix Configuration": global-key-provider-configuration/kmip-fortanix.md - - "Vault Configuration": global-key-provider-configuration/vault.md - - "Thales Configuration": global-key-provider-configuration/kmip-thales.md - - "Using OpenBao as a Key Provider": global-key-provider-configuration/kmip-openbao.md - - "Keyring File Configuration": global-key-provider-configuration/keyring.md - - "2.2 Global Principal Key Configuration": global-key-provider-configuration/set-principal-key.md - - "3. Validate Encryption with pg_tde": test.md - - "4. Configure WAL Encryption (tech preview)": wal-encryption.md - - "Technical Reference": - - "Overview": advanced-topics/index.md - - "Architecture": architecture/index.md + - "Key management overview": global-key-provider-configuration/overview.md + - "KMIP configuration": global-key-provider-configuration/kmip-server.md + - "Fortanix configuration": global-key-provider-configuration/kmip-fortanix.md + - "Vault configuration": global-key-provider-configuration/vault.md + - "Thales configuration": global-key-provider-configuration/kmip-thales.md + - "Using OpenBao as a key provider": global-key-provider-configuration/kmip-openbao.md + - "Keyring file configuration": global-key-provider-configuration/keyring.md + - "2.2 Global Principal Key configuration": global-key-provider-configuration/set-principal-key.md + - "3. Validate encryption with pg_tde": test.md + - "4. Configure WAL encryption (tech preview)": wal-encryption.md + - "Technical reference": + - "Overview": advanced-topics/tech-reference.md + - "Architecture": architecture/architecture.md - "GUC variables": variables.md - "Functions": functions.md - "Streaming Replication with tde_heap": replication.md - - "TDE Operations": + - "TDE operations": - "pg_tde CLI Tools": - - "CLI Overview": command-line-tools/index.md + - "Overview": command-line-tools/cli-tools.md - "pg_tde_change_key_provider": command-line-tools/pg-tde-change-key-provider.md - "pg_waldump": command-line-tools/pg-waldump.md - "pg_checksums": command-line-tools/pg-tde-checksums.md diff --git a/contrib/pg_tde/documentation/snippets/kms-considerations.md b/contrib/pg_tde/documentation/snippets/kms-considerations.md index c0e6bfdadfb91..5ef51732fc76a 100644 --- a/contrib/pg_tde/documentation/snippets/kms-considerations.md +++ b/contrib/pg_tde/documentation/snippets/kms-considerations.md @@ -1,3 +1,3 @@ ## Considerations -You can use external key providers to manage encryption keys. The recommended approach is to use the Key Management Store (KMS). For more information, see [Configure Key Management (KMS)](../global-key-provider-configuration/index.md). +You can use external key providers to manage encryption keys. The recommended approach is to use the Key Management Store (KMS). For more information, see [Configure Key Management (KMS) :octicons-link-external-16:](../global-key-provider-configuration/overview.md). From 0ee3f3a341ebe769b52d6d51fdafb7ba12fcc6f2 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 25 Jul 2025 18:06:41 +0300 Subject: [PATCH 535/796] PG-1656 Add sample outputs for Global Principal Key Configuration commands (#474) --- .../pg_tde/documentation/docs/functions.md | 3 ++ .../set-principal-key.md | 46 ++++++++++++------- .../docs/how-to/multi-tenant-setup.md | 3 ++ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index b5c51ec07cdf0..73f2da208faa0 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -2,6 +2,9 @@ The `pg_tde` extension provides functions for managing different aspects of its operation: +!!! note + If no error is reported when running the commands below, the operation completed successfully. + ## Key provider management A key provider is a system or service responsible for managing encryption keys. `pg_tde` supports the following key providers: diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 8bee0e15ca0f5..1d4e55788d5df 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -4,6 +4,9 @@ You can configure a default principal key using a global key provider. This key ## Create a default principal key +!!! note + The sample output below is for demonstration purposes only. Be sure to replace the key name and provider with your actual values. + To create a global principal key, run: ```sql @@ -13,6 +16,18 @@ SELECT pg_tde_create_key_using_global_key_provider( ); ``` +??? example "Sample output" + ```sql + postgres=# SELECT pg_tde_create_key_using_global_key_provider( + 'keytest1', + 'file-keyring' + ); + pg_tde_create_key_using_global_key_provider + --------------------------------------------- + + (1 row) + ``` + ## Configure a default principal key To configure a global principal key, run: @@ -24,11 +39,26 @@ SELECT pg_tde_set_default_key_using_global_key_provider( ); ``` +??? example "Sample output" + ```sql + postgres=# SELECT pg_tde_set_default_key_using_global_key_provider( + 'keytest1', + 'file-keyring' + ); + pg_tde_set_default_key_using_global_key_provider + -------------------------------------------------- + + (1 row) + ``` + ## Parameter description * `key-name` is the name under which the principal key is stored in the provider. * `global_vault_provider` is the name of the global key provider you previously configured. +!!! note + If no error is reported, the action completed successfully. + ## How key generation works The key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. @@ -36,22 +66,6 @@ The key material (actual cryptographic key) is auto-generated by `pg_tde` and st !!! note This process sets the **default principal key for the entire server**. Any database without a key explicitly configured will fall back to this key. -## Example - -This example is for testing purposes only. Replace the key name and provider name with your values: - -```sql -SELECT pg_tde_create_key_using_global_key_provider( - 'test-db-master-key', - 'file-vault' -); - -SELECT pg_tde_set_key_using_global_key_provider( - 'test-db-master-key', - 'file-vault' -); -``` - ## Next steps [Validate Encryption with pg_tde :material-arrow-right:](../test.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index c20a8f640731c..92bc892285a39 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -8,6 +8,9 @@ For how to enable WAL encryption, refer to the [Configure WAL Encryption :octico --8<-- "kms-considerations.md" +!!! note + If no error is reported when running the commands below, the operation completed successfully. + ## Enable extension Load the `pg_tde` at startup time. The extension requires additional shared memory; therefore, add the `pg_tde` value for the `shared_preload_libraries` parameter and restart the `postgresql` cluster. From f939a24a08d400407278a9b783503b31ea73e912 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 28 Jul 2025 11:38:23 +0200 Subject: [PATCH 536/796] Increase the test timeout from 180 to 300 seconds We get what looks a lot like timeouts when we try to run certain pg_tde TAP tests with code coverage enabled. --- .github/workflows/coverage.yml | 4 +++- .github/workflows/sanitizers.yml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 70d227e18bea3..f22e97afc0b8a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,7 +10,9 @@ on: - contrib/pg_tde/documentation/** env: - PGCTLTIMEOUT: 120 # Avoid failures on slow recovery + # Avoid failures on slow recovery + PGCTLTIMEOUT: 120 + PG_TEST_TIMEOUT_DEFAULT: 300 jobs: collect: diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index b1196d41f4fbe..462de6fa0fb67 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -17,7 +17,9 @@ env: LSAN_OPTIONS: log_path=${{ github.workspace }}/sanitize.log print_suppressions=0 suppressions=${{ github.workspace }}/ci_scripts/suppressions/lsan.supp ASAN_SYMBOLIZER_PATH: /usr/bin/llvm-symbolizer-14 EXTRA_REGRESS_OPTS: "--temp-config=${{ github.workspace }}/test_postgresql.conf" + # Avoid failures on slow recovery PGCTLTIMEOUT: 120 + PG_TEST_TIMEOUT_DEFAULT: 300 jobs: run: From 77678e83ae793046d37b328e3c94f3effbc7e12a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Jul 2025 00:21:48 +0200 Subject: [PATCH 537/796] PG-1710 Move binaries into src/bin Now that we will soon be adding more bianries having them at the top level only makes things confusing for developers. --- contrib/pg_tde/.gitignore | 2 +- contrib/pg_tde/Makefile | 6 +++--- contrib/pg_tde/meson.build | 2 +- contrib/pg_tde/src/{ => bin}/pg_tde_change_key_provider.c | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename contrib/pg_tde/src/{ => bin}/pg_tde_change_key_provider.c (100%) diff --git a/contrib/pg_tde/.gitignore b/contrib/pg_tde/.gitignore index 044af8f3c5cae..8dd13aa295cb6 100644 --- a/contrib/pg_tde/.gitignore +++ b/contrib/pg_tde/.gitignore @@ -11,7 +11,7 @@ __pycache__ /configure~ /log /results -/src/pg_tde_change_key_provider +/src/bin/pg_tde_change_key_provider /t/results /tmp_check diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index dd9f5541c6d19..c78599fe478a1 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -51,10 +51,10 @@ src/libkmip/libkmip/src/kmip_bio.o \ src/libkmip/libkmip/src/kmip_locate.o \ src/libkmip/libkmip/src/kmip_memset.o -SCRIPTS_built = src/pg_tde_change_key_provider +SCRIPTS_built = src/bin/pg_tde_change_key_provider EXTRA_INSTALL += contrib/pg_buffercache contrib/test_decoding -EXTRA_CLEAN += src/pg_tde_change_key_provider.o +EXTRA_CLEAN += src/bin/pg_tde_change_key_provider.o ifdef USE_PGXS PG_CONFIG = pg_config @@ -71,7 +71,7 @@ endif override SHLIB_LINK += -lcurl -lcrypto -lssl -src/pg_tde_change_key_provider: src/pg_tde_change_key_provider.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtde.a +src/bin/pg_tde_change_key_provider: src/bin/pg_tde_change_key_provider.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtde.a $(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) # Fetches typedefs list for PostgreSQL core and merges it with typedefs defined in this project. diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 42a6bbe6afff3..df05c36dffd7b 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -151,7 +151,7 @@ pg_tde_frontend = static_library('pg_tde_frontend', ) pg_tde_change_key_provider_sources = files( - 'src/pg_tde_change_key_provider.c', + 'src/bin/pg_tde_change_key_provider.c', ) pg_tde_change_key_provider = executable('pg_tde_change_key_provider', diff --git a/contrib/pg_tde/src/pg_tde_change_key_provider.c b/contrib/pg_tde/src/bin/pg_tde_change_key_provider.c similarity index 100% rename from contrib/pg_tde/src/pg_tde_change_key_provider.c rename to contrib/pg_tde/src/bin/pg_tde_change_key_provider.c From 80d515b322e9a0c7177cf67f148f296600df1e62 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 15 Jul 2025 21:21:59 +0200 Subject: [PATCH 538/796] Make comment in TAP test more clear --- contrib/pg_tde/t/wal_encrypt.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/t/wal_encrypt.pl b/contrib/pg_tde/t/wal_encrypt.pl index 7db5cd28dbf2a..2c23d75808a52 100644 --- a/contrib/pg_tde/t/wal_encrypt.pl +++ b/contrib/pg_tde/t/wal_encrypt.pl @@ -15,8 +15,8 @@ $node->init; $node->append_conf('postgresql.conf', "shared_preload_libraries = 'pg_tde'"); $node->append_conf('postgresql.conf', "wal_level = 'logical'"); -# NOT testing that it can't start: the test framework doesn't have an easy way to do this -#$node->append_conf('postgresql.conf', "pg_tde.wal_encrypt = 1"}); +# We don't test that it can't start: the test framework doesn't have an easy way to do this +#$node->append_conf('postgresql.conf', "pg_tde.wal_encrypt = 1"); $node->start; PGTDE::psql($node, 'postgres', "CREATE EXTENSION pg_tde;"); From f6f1ae33b1f8738fd90cc99b93ac032f2e443965 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 14 Jul 2025 22:57:33 +0200 Subject: [PATCH 539/796] PG-1710 Create helpers for decrypting/encrypting archived WAL To support some common WAL archiving tools, e.g. PgBackRest, we implement an archive_command and a restore_command which can wrap any command and use pipe() to create fake file to either read from or write to. The restore command makes sure to write encrypted files if WAL encryption is enabled. It uses the fresh WAL key generated by the server on the current start which works fine because we then just let the first invocation of the restore command set the start LSN of the key. For e.g. PgBackRest you would have the following commands: archive_command = 'pg_tde_archive_decrypt %p pgbackrest --stanza=demo archive-push %p' restore_command = 'pg_tde_restore_encrypt %f %p pgbackrest --stanza=demo archive-get %f "%p"' --- contrib/pg_tde/.gitignore | 2 + contrib/pg_tde/Makefile | 22 +- contrib/pg_tde/meson.build | 29 +++ contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 15 ++ .../pg_tde/src/bin/pg_tde_archive_decrypt.c | 228 ++++++++++++++++++ .../pg_tde/src/bin/pg_tde_restore_encrypt.c | 220 +++++++++++++++++ .../src/include/access/pg_tde_xlog_smgr.h | 1 + contrib/pg_tde/t/wal_archiving.pl | 101 ++++++++ 8 files changed, 616 insertions(+), 2 deletions(-) create mode 100644 contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c create mode 100644 contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c create mode 100644 contrib/pg_tde/t/wal_archiving.pl diff --git a/contrib/pg_tde/.gitignore b/contrib/pg_tde/.gitignore index 8dd13aa295cb6..47f98d48e68f3 100644 --- a/contrib/pg_tde/.gitignore +++ b/contrib/pg_tde/.gitignore @@ -11,7 +11,9 @@ __pycache__ /configure~ /log /results +/src/bin/pg_tde_archive_decrypt /src/bin/pg_tde_change_key_provider +/src/bin/pg_tde_restore_encrypt /t/results /tmp_check diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index c78599fe478a1..3eba916acc935 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -51,10 +51,16 @@ src/libkmip/libkmip/src/kmip_bio.o \ src/libkmip/libkmip/src/kmip_locate.o \ src/libkmip/libkmip/src/kmip_memset.o -SCRIPTS_built = src/bin/pg_tde_change_key_provider +SCRIPTS_built = src/bin/pg_tde_archive_decrypt \ +src/bin/pg_tde_change_key_provider \ +src/bin/pg_tde_restore_encrypt EXTRA_INSTALL += contrib/pg_buffercache contrib/test_decoding -EXTRA_CLEAN += src/bin/pg_tde_change_key_provider.o +EXTRA_CLEAN += src/bin/pg_tde_archive_decrypt.o \ +src/bin/pg_tde_change_key_provider.o \ +src/bin/pg_tde_restore_encrypt.o \ +xlogreader.c \ +xlogreader.o ifdef USE_PGXS PG_CONFIG = pg_config @@ -74,6 +80,18 @@ override SHLIB_LINK += -lcurl -lcrypto -lssl src/bin/pg_tde_change_key_provider: src/bin/pg_tde_change_key_provider.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtde.a $(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) +src/bin/pg_tde_archive_decrypt: src/bin/pg_tde_archive_decrypt.o xlogreader.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtdexlog.a $(top_builddir)/src/libtde/libtde.a + $(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +src/bin/pg_tde_restore_encrypt: src/bin/pg_tde_restore_encrypt.o xlogreader.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtdexlog.a $(top_builddir)/src/libtde/libtde.a + $(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/% + rm -f $@ && $(LN_S) $< . + +xlogreader.o: xlogreader.c + $(CC) $(CPPFLAGS) -DFRONTEND -c $< -o $@ + # Fetches typedefs list for PostgreSQL core and merges it with typedefs defined in this project. # https://wiki.postgresql.org/wiki/Running_pgindent_on_non-core_code_or_development_code update-typedefs: diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index df05c36dffd7b..5785f3148a25c 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -122,6 +122,7 @@ tap_tests = [ 't/rotate_key.pl', 't/tde_heap.pl', 't/unlogged_tables.pl', + 't/wal_archiving.pl', 't/wal_encrypt.pl', ] @@ -163,3 +164,31 @@ pg_tde_change_key_provider = executable('pg_tde_change_key_provider', link_with: [pg_tde_frontend] ) contrib_targets += pg_tde_change_key_provider + +pg_tde_archive_decrypt_sources = files( + 'src/bin/pg_tde_archive_decrypt.c', +) + xlogreader_sources + +pg_tde_archive_decrypt = executable('pg_tde_archive_decrypt', + pg_tde_archive_decrypt_sources, + dependencies: [frontend_code], + c_args: ['-DFRONTEND'], # needed for xlogreader et al + kwargs: default_bin_args, + include_directories: [postgres_inc, pg_tde_inc], + link_with: [pg_tde_frontend] +) +contrib_targets += pg_tde_archive_decrypt + +pg_tde_restore_encrypt_sources = files( + 'src/bin/pg_tde_restore_encrypt.c', +) + xlogreader_sources + +pg_tde_restore_encrypt = executable('pg_tde_restore_encrypt', + pg_tde_restore_encrypt_sources, + dependencies: [frontend_code], + c_args: ['-DFRONTEND'], # needed for xlogreader et al + kwargs: default_bin_args, + include_directories: [postgres_inc, pg_tde_inc], + link_with: [pg_tde_frontend] +) +contrib_targets += pg_tde_restore_encrypt diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 3edef134b790d..5578167a9b471 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -229,6 +229,21 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); } +void +TDEXLogSmgrInitWriteReuseKey() +{ + InternalKey *key = pg_tde_read_last_wal_key(); + + if (key) + { + EncryptionKey = *key; + TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn); + pfree(key); + } + + pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); +} + /* * Encrypt XLog page(s) from the buf and write to the segment file. */ diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c new file mode 100644 index 0000000000000..81ac79cfecada --- /dev/null +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -0,0 +1,228 @@ +#include "postgres_fe.h" + +#include +#include +#include + +#include "access/xlog_internal.h" +#include "access/xlog_smgr.h" +#include "common/logging.h" + +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" + +static bool +is_segment(const char *filename) +{ + return strspn(filename, "0123456789ABCDEF") == 24 && filename[24] == '\0'; +} + +static void +write_decrypted_segment(const char *segpath, const char *segname, int pipewr) +{ + int fd; + off_t fsize; + int r; + int w; + TimeLineID tli; + XLogSegNo segno; + PGAlignedXLogBlock buf; + off_t pos = 0; + + fd = open(segpath, O_RDONLY | PG_BINARY, 0); + if (fd < 0) + pg_fatal("could not open file \"%s\": %m", segname); + + /* + * WalSegSz extracted from the first page header but it might be + * encrypted. But we need to know the segment seize to decrypt it (it's + * required for encryption offset calculations). So we get the segment + * size from the file's actual size. XLogLongPageHeaderData->xlp_seg_size + * there is "just as a cross-check" anyway. + */ + fsize = lseek(fd, 0, SEEK_END); + XLogFromFileName(segname, &tli, &segno, fsize); + + r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); + + if (r == XLOG_BLCKSZ) + { + XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data; + int walsegsz = longhdr->xlp_seg_size; + + if (walsegsz != fsize) + pg_fatal("mismatch of segment size in WAL file \"%s\" (header: %d bytes, file size: %ld bytes)", + segname, walsegsz, fsize); + + if (!IsValidWalSegSize(walsegsz)) + { + pg_log_error(ngettext("invalid WAL segment size in WAL file \"%s\" (%d byte)", + "invalid WAL segment size in WAL file \"%s\" (%d bytes)", + walsegsz), + segname, walsegsz); + pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB."); + exit(1); + } + } + else if (r < 0) + pg_fatal("could not read file \"%s\": %m", + segpath); + else + pg_fatal("could not read file \"%s\": read %d of %d", + segpath, r, XLOG_BLCKSZ); + + pos += r; + + w = write(pipewr, buf.data, XLOG_BLCKSZ); + + if (w < 0) + pg_fatal("could not write to pipe: %m"); + else if (w != r) + pg_fatal("could not write to pipe: wrote %d of %d", w, r); + + while (1) + { + r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); + + if (r == 0) + break; + else if (r < 0) + pg_fatal("could not read file \"%s\": %m", segpath); + + pos += r; + + w = write(pipewr, buf.data, r); + + if (w < 0) + pg_fatal("could not write to pipe: %m"); + else if (w != r) + pg_fatal("could not write to pipe: wrote %d of %d", w, r); + } + + close(fd); +} + +static void +usage(const char *progname) +{ + printf(_("%s wraps an archive command to make it archive unencrypted WAL.\n\n"), progname); + printf(_("Usage:\n %s %%p \n\n"), progname); + printf(_("Options:\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); +} + +int +main(int argc, char *argv[]) +{ + const char *progname; + char *sourcepath; + char *sep; + char *sourcename; + char stdindir[MAXPGPATH] = "/tmp/pg_tde_archiveXXXXXX"; + char stdinpath[MAXPGPATH]; + bool issegment; + int pipefd[2]; + pid_t child; + int status; + int r; + + pg_logging_init(argv[0]); + progname = get_progname(argv[0]); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(progname); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("pg_tde_archive_deceypt (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + if (argc < 3) + { + pg_log_error("too few arguments"); + pg_log_error_detail("Try \"%s --help\" for more information.", progname); + exit(1); + } + + sourcepath = argv[1]; + + pg_tde_fe_init("pg_tde"); + TDEXLogSmgrInit(); + + sep = strrchr(sourcepath, '/'); + + if (sep != NULL) + sourcename = sep + 1; + else + sourcename = sourcepath; + + issegment = is_segment(sourcename); + + if (issegment) + { + char *s; + + if (mkdtemp(stdindir) == NULL) + pg_fatal("could not create temporary directory \"%s\": %m", stdindir); + + s = stpcpy(stdinpath, stdindir); + s = stpcpy(s, "/"); + stpcpy(s, sourcename); + + if (pipe(pipefd) < 0) + pg_fatal("could not create pipe: %m"); + + if (symlink("/dev/stdin", stdinpath) < 0) + pg_fatal("could not create symlink \"%s\": %m", stdinpath); + + for (int i = 2; i < argc; i++) + if (strcmp(sourcepath, argv[i]) == 0) + argv[i] = stdinpath; + } + + child = fork(); + if (child == 0) + { + if (issegment) + { + close(0); + dup2(pipefd[0], 0); + close(pipefd[0]); + close(pipefd[1]); + } + + if (execvp(argv[2], argv + 2) < 0) + pg_fatal("exec failed: %m"); + } + else if (child < 0) + pg_fatal("could not create background process: %m"); + + if (issegment) + { + close(pipefd[0]); + write_decrypted_segment(sourcepath, sourcename, pipefd[1]); + close(pipefd[1]); + } + + r = waitpid(child, &status, 0); + if (r == (pid_t) -1) + pg_fatal("could not wait for child process: %m"); + if (r != child) + pg_fatal("child %d died, expected %d", (int) r, (int) child); + if (status != 0) + pg_fatal("%s", wait_result_to_str(status)); + + if (issegment && unlink(stdinpath) < 0) + pg_log_warning("could not remove symlink \"%s\": %m", stdinpath); + if (issegment && rmdir(stdindir) < 0) + pg_log_warning("could not remove directory \"%s\": %m", stdindir); + + return 0; +} diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c new file mode 100644 index 0000000000000..ff26c72aa1a73 --- /dev/null +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -0,0 +1,220 @@ +#include "postgres_fe.h" + +#include +#include +#include + +#include "access/xlog_internal.h" +#include "access/xlog_smgr.h" +#include "common/logging.h" + +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" + +static bool +is_segment(const char *filename) +{ + return strspn(filename, "0123456789ABCDEF") == 24 && filename[24] == '\0'; +} + +static void +write_encrypted_segment(const char *segpath, const char *segname, int piperd) +{ + int fd; + PGAlignedXLogBlock buf; + int r; + int w; + int pos = 0; + XLogLongPageHeader longhdr; + int walsegsz; + TimeLineID tli; + XLogSegNo segno; + + fd = open(segpath, O_CREAT | O_WRONLY | PG_BINARY, 0666); + if (fd < 0) + pg_fatal("could not open file \"%s\": %m", segpath); + + r = read(piperd, buf.data, XLOG_BLCKSZ); + + if (r < 0) + pg_fatal("could not read from pipe: %m"); + else if (r != XLOG_BLCKSZ) + pg_fatal("could not read from pipe: read %d of %d", + r, XLOG_BLCKSZ); + + longhdr = (XLogLongPageHeader) buf.data; + walsegsz = longhdr->xlp_seg_size; + + if (!IsValidWalSegSize(walsegsz)) + { + pg_log_error(ngettext("invalid WAL segment size in WAL file \"%s\" (%d byte)", + "invalid WAL segment size in WAL file \"%s\" (%d bytes)", + walsegsz), + segname, walsegsz); + pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB."); + exit(1); + } + + XLogFromFileName(segname, &tli, &segno, walsegsz); + + TDEXLogSmgrInitWriteReuseKey(); + + w = xlog_smgr->seg_write(fd, buf.data, r, pos, tli, segno, walsegsz); + + if (w < 0) + pg_fatal("could not write file \"%s\": %m", segpath); + else if (w != r) + pg_fatal("could not write file \"%s\": wrote %d of %d", + segpath, w, r); + + pos += w; + + while (1) + { + r = read(piperd, buf.data, XLOG_BLCKSZ); + + if (r == 0) + break; + else if (r < 0) + pg_fatal("could not read from pipe: %m"); + + w = xlog_smgr->seg_write(fd, buf.data, r, pos, tli, segno, walsegsz); + + if (w < 0) + pg_fatal("could not write file \"%s\": %m", segpath); + else if (w != r) + pg_fatal("could not write file \"%s\": wrote %d of %d", + segpath, w, r); + + pos += w; + } + + close(fd); +} + +static void +usage(const char *progname) +{ + printf(_("%s wraps a restore command to make it write encrypted WAL to pg_wal.\n\n"), progname); + printf(_("Usage:\n %s %%f %%p \n\n"), progname); + printf(_("Options:\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); +} + +int +main(int argc, char *argv[]) +{ + const char *progname; + char *sourcename; + char *targetpath; + char *sep; + char *targetname; + char stdoutdir[MAXPGPATH] = "/tmp/pg_tde_restoreXXXXXX"; + char stdoutpath[MAXPGPATH]; + bool issegment; + int pipefd[2]; + pid_t child; + int status; + int r; + + pg_logging_init(argv[0]); + progname = get_progname(argv[0]); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(progname); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("pg_tde_restore_encrypt (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + if (argc < 4) + { + pg_log_error("too few arguments"); + pg_log_error_detail("Try \"%s --help\" for more information.", progname); + exit(1); + } + + sourcename = argv[1]; + targetpath = argv[2]; + + pg_tde_fe_init("pg_tde"); + TDEXLogSmgrInit(); + + sep = strrchr(targetpath, '/'); + + if (sep != NULL) + targetname = sep + 1; + else + targetname = targetpath; + + issegment = is_segment(sourcename); + + if (issegment) + { + char *s; + + if (mkdtemp(stdoutdir) == NULL) + pg_fatal("could not create temporary directory \"%s\": %m", stdoutdir); + + s = stpcpy(stdoutpath, stdoutdir); + s = stpcpy(s, "/"); + stpcpy(s, targetname); + + if (pipe(pipefd) < 0) + pg_fatal("could not create pipe: %m"); + + if (symlink("/dev/stdout", stdoutpath) < 0) + pg_fatal("could not create symlink \"%s\": %m", stdoutpath); + + for (int i = 2; i < argc; i++) + if (strcmp(targetpath, argv[i]) == 0) + argv[i] = stdoutpath; + } + + child = fork(); + if (child == 0) + { + if (issegment) + { + close(1); + dup2(pipefd[1], 1); + close(pipefd[0]); + close(pipefd[1]); + } + + if (execvp(argv[3], argv + 3) < 0) + pg_fatal("exec failed: %m"); + } + else if (child < 0) + pg_fatal("could not create background process: %m"); + + if (issegment) + { + close(pipefd[1]); + write_encrypted_segment(targetpath, sourcename, pipefd[0]); + close(pipefd[0]); + } + + r = waitpid(child, &status, 0); + if (r == (pid_t) -1) + pg_fatal("could not wait for child process: %m"); + if (r != child) + pg_fatal("child %d died, expected %d", (int) r, (int) child); + if (status != 0) + pg_fatal("%s", wait_result_to_str(status)); + + if (issegment && unlink(stdoutpath) < 0) + pg_log_warning("could not remove symlink \"%s\": %m", stdoutpath); + if (issegment && rmdir(stdoutdir) < 0) + pg_log_warning("could not remove directory \"%s\": %m", stdoutdir); + + return 0; +} diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 5956c4b9d12fd..6b434de3d3c93 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -11,5 +11,6 @@ extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); +extern void TDEXLogSmgrInitWriteReuseKey(void); #endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/t/wal_archiving.pl b/contrib/pg_tde/t/wal_archiving.pl new file mode 100644 index 0000000000000..9bd37904a0792 --- /dev/null +++ b/contrib/pg_tde/t/wal_archiving.pl @@ -0,0 +1,101 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use Test::More; +use lib 't'; +use pgtde; + +unlink('/tmp/wal_archiving.per'); + +# Test archive_command + +my $primary = PostgreSQL::Test::Cluster->new('primary'); +my $archive_dir = $primary->archive_dir; +$primary->init(allows_streaming => 1); +$primary->append_conf('postgresql.conf', + "shared_preload_libraries = 'pg_tde'"); +$primary->append_conf('postgresql.conf', "wal_level = 'replica'"); +$primary->append_conf('postgresql.conf', "autovacuum = off"); +$primary->append_conf('postgresql.conf', "checkpoint_timeout = 1h"); +$primary->append_conf('postgresql.conf', "archive_mode = on"); +$primary->append_conf('postgresql.conf', + "archive_command = 'pg_tde_archive_decrypt %p cp %p $archive_dir/%f'"); +$primary->start; + +$primary->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); + +$primary->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('keyring', '/tmp/wal_archiving.per');" +); +$primary->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'keyring');" +); +$primary->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'keyring');" +); + +$primary->append_conf('postgresql.conf', "pg_tde.wal_encrypt = on"); + +$primary->backup('backup', backup_options => [ '-X', 'none' ]); + +$primary->safe_psql('postgres', + "CREATE TABLE t1 AS SELECT 'foobar_plain' AS x"); + +$primary->restart; + +$primary->safe_psql('postgres', + "CREATE TABLE t2 AS SELECT 'foobar_enc' AS x"); + +my $data_dir = $primary->data_dir; + +like( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_plain/, + 'should find foobar_plain in WAL'); +unlike( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_enc/, + 'should not find foobar_enc in WAL'); + +$primary->stop; + +like( + `strings $archive_dir/0000000100000000000000??`, + qr/foobar_plain/, + 'should find foobar_plain in archive'); +like( + `strings $archive_dir/0000000100000000000000??`, + qr/foobar_enc/, + 'should find foobar_enc in archive'); + +# Test restore_command + +my $replica = PostgreSQL::Test::Cluster->new('replica'); +$replica->init_from_backup($primary, 'backup'); +$replica->append_conf('postgresql.conf', + "restore_command = 'pg_tde_restore_encrypt %f %p cp $archive_dir/%f %p'"); +$replica->append_conf('postgresql.conf', "recovery_target_action = promote"); +$replica->set_recovery_mode; +$replica->start; + +$data_dir = $replica->data_dir; + +unlike( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_plain/, + 'should not find foobar_plain in WAL since it is encrypted'); +unlike( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_enc/, + 'should not find foobar_enc in WAL since it is encrypted'); + +my $result = $replica->safe_psql('postgres', + 'SELECT * FROM t1 UNION ALL SELECT * FROM t2'); + +is($result, "foobar_plain\nfoobar_enc", 'b'); + +$replica->stop; + +done_testing(); From a54fb06cba16ea0a863872611cc5576876cca6ec Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Jul 2025 17:13:44 +0200 Subject: [PATCH 540/796] Fix our poll_start() helper by including usleep() The helper crashed if it failed on the first attempt due to a missing dependency. --- contrib/pg_tde/t/pgtde.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index eb5c02b24ec17..8f02dce151555 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -6,6 +6,7 @@ use PostgreSQL::Test::Utils; use File::Basename; use File::Compare; use Test::More; +use Time::HiRes qw(usleep); # Expected .out filename of TAP testcase being executed. These are already part of repo under t/expected/*. our $expected_filename_with_path; From 01a88bf14a4b3e4774614852bb43a4a6a09e7859 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Jul 2025 17:19:08 +0200 Subject: [PATCH 541/796] Update code owners to not assign Andrew and Zsolt as reviewers We have changed our process to not assign any reviewer autoamtically but to only do so manually if that is desired. --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3bdd7c5f42812..63a3ca4eeb531 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,5 @@ # https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners # Order is important; the last matching pattern takes the most precedence. -* @dutow @dAdAbird /contrib/pg_tde/documentation/ @nastena1606 @Andriciuc /.github/ @artemgavrilov From 9dc6c26cdfb957a9b90e485202f8ec0e39b9ddac Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Jul 2025 15:45:25 +0200 Subject: [PATCH 542/796] PG-1710 Improve error message by using full path The error message did already include the path in the restore command so this was an oversight. --- contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 81ac79cfecada..d4b43bf2fcd15 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -31,7 +31,7 @@ write_decrypted_segment(const char *segpath, const char *segname, int pipewr) fd = open(segpath, O_RDONLY | PG_BINARY, 0); if (fd < 0) - pg_fatal("could not open file \"%s\": %m", segname); + pg_fatal("could not open file \"%s\": %m", segpath); /* * WalSegSz extracted from the first page header but it might be From fd6685348e71d09a955c29cb5538b328e3424073 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Jul 2025 15:45:37 +0200 Subject: [PATCH 543/796] PG-1710 Use temporary files instead of pipes for archive and restore commands For some to me unknown reason pgbackrest archive-push did not like reading from a pipe so we create a temporary file instead. The location for the temporary file is for now hardcoded to be /dev/shm since that is guaranteed to be on a tmpfs mount on all common Linux distributions. We might in the future want to make this configurable since it is a Linux specific thing. I tested this with PgBackRest 2.55.0. --- .../pg_tde/src/bin/pg_tde_archive_decrypt.c | 85 +++++++++---------- .../pg_tde/src/bin/pg_tde_restore_encrypt.c | 81 ++++++++---------- 2 files changed, 75 insertions(+), 91 deletions(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index d4b43bf2fcd15..f3377e26446b9 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -11,6 +11,8 @@ #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" +#define TMPFS_DIRECTORY "/dev/shm" + static bool is_segment(const char *filename) { @@ -18,9 +20,10 @@ is_segment(const char *filename) } static void -write_decrypted_segment(const char *segpath, const char *segname, int pipewr) +write_decrypted_segment(const char *segpath, const char *segname, const char *tmppath) { - int fd; + int segfd; + int tmpfd; off_t fsize; int r; int w; @@ -29,10 +32,14 @@ write_decrypted_segment(const char *segpath, const char *segname, int pipewr) PGAlignedXLogBlock buf; off_t pos = 0; - fd = open(segpath, O_RDONLY | PG_BINARY, 0); - if (fd < 0) + segfd = open(segpath, O_RDONLY | PG_BINARY, 0); + if (segfd < 0) pg_fatal("could not open file \"%s\": %m", segpath); + tmpfd = open(tmppath, O_CREAT | O_WRONLY | PG_BINARY, 0666); + if (tmpfd < 0) + pg_fatal("could not open file \"%s\": %m", tmppath); + /* * WalSegSz extracted from the first page header but it might be * encrypted. But we need to know the segment seize to decrypt it (it's @@ -40,10 +47,10 @@ write_decrypted_segment(const char *segpath, const char *segname, int pipewr) * size from the file's actual size. XLogLongPageHeaderData->xlp_seg_size * there is "just as a cross-check" anyway. */ - fsize = lseek(fd, 0, SEEK_END); + fsize = lseek(segfd, 0, SEEK_END); XLogFromFileName(segname, &tli, &segno, fsize); - r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); + r = xlog_smgr->seg_read(segfd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); if (r == XLOG_BLCKSZ) { @@ -73,16 +80,17 @@ write_decrypted_segment(const char *segpath, const char *segname, int pipewr) pos += r; - w = write(pipewr, buf.data, XLOG_BLCKSZ); + w = write(tmpfd, buf.data, XLOG_BLCKSZ); if (w < 0) - pg_fatal("could not write to pipe: %m"); + pg_fatal("could not write file \"%s\": %m", tmppath); else if (w != r) - pg_fatal("could not write to pipe: wrote %d of %d", w, r); + pg_fatal("could not write file \"%s\": wrote %d of %d", + tmppath, w, r); while (1) { - r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); + r = xlog_smgr->seg_read(segfd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize); if (r == 0) break; @@ -91,15 +99,17 @@ write_decrypted_segment(const char *segpath, const char *segname, int pipewr) pos += r; - w = write(pipewr, buf.data, r); + w = write(tmpfd, buf.data, r); if (w < 0) - pg_fatal("could not write to pipe: %m"); + pg_fatal("could not write file \"%s\": %m", tmppath); else if (w != r) - pg_fatal("could not write to pipe: wrote %d of %d", w, r); + pg_fatal("could not write file \"%s\": wrote %d of %d", + tmppath, w, r); } - close(fd); + close(tmpfd); + close(segfd); } static void @@ -119,10 +129,9 @@ main(int argc, char *argv[]) char *sourcepath; char *sep; char *sourcename; - char stdindir[MAXPGPATH] = "/tmp/pg_tde_archiveXXXXXX"; - char stdinpath[MAXPGPATH]; + char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_archiveXXXXXX"; + char tmppath[MAXPGPATH]; bool issegment; - int pipefd[2]; pid_t child; int status; int r; @@ -169,48 +178,29 @@ main(int argc, char *argv[]) { char *s; - if (mkdtemp(stdindir) == NULL) - pg_fatal("could not create temporary directory \"%s\": %m", stdindir); + if (mkdtemp(tmpdir) == NULL) + pg_fatal("could not create temporary directory \"%s\": %m", tmpdir); - s = stpcpy(stdinpath, stdindir); + s = stpcpy(tmppath, tmpdir); s = stpcpy(s, "/"); stpcpy(s, sourcename); - if (pipe(pipefd) < 0) - pg_fatal("could not create pipe: %m"); - - if (symlink("/dev/stdin", stdinpath) < 0) - pg_fatal("could not create symlink \"%s\": %m", stdinpath); - for (int i = 2; i < argc; i++) if (strcmp(sourcepath, argv[i]) == 0) - argv[i] = stdinpath; + argv[i] = tmppath; + + write_decrypted_segment(sourcepath, sourcename, tmppath); } child = fork(); if (child == 0) { - if (issegment) - { - close(0); - dup2(pipefd[0], 0); - close(pipefd[0]); - close(pipefd[1]); - } - if (execvp(argv[2], argv + 2) < 0) pg_fatal("exec failed: %m"); } else if (child < 0) pg_fatal("could not create background process: %m"); - if (issegment) - { - close(pipefd[0]); - write_decrypted_segment(sourcepath, sourcename, pipefd[1]); - close(pipefd[1]); - } - r = waitpid(child, &status, 0); if (r == (pid_t) -1) pg_fatal("could not wait for child process: %m"); @@ -219,10 +209,13 @@ main(int argc, char *argv[]) if (status != 0) pg_fatal("%s", wait_result_to_str(status)); - if (issegment && unlink(stdinpath) < 0) - pg_log_warning("could not remove symlink \"%s\": %m", stdinpath); - if (issegment && rmdir(stdindir) < 0) - pg_log_warning("could not remove directory \"%s\": %m", stdindir); + if (issegment) + { + if (unlink(tmppath) < 0) + pg_log_warning("could not remove file \"%s\": %m", tmppath); + if (rmdir(tmpdir) < 0) + pg_log_warning("could not remove directory \"%s\": %m", tmpdir); + } return 0; } diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index ff26c72aa1a73..8054770f33a3a 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -11,6 +11,8 @@ #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" +#define TMPFS_DIRECTORY "/dev/shm" + static bool is_segment(const char *filename) { @@ -18,9 +20,10 @@ is_segment(const char *filename) } static void -write_encrypted_segment(const char *segpath, const char *segname, int piperd) +write_encrypted_segment(const char *segpath, const char *segname, const char *tmppath) { - int fd; + int tmpfd; + int segfd; PGAlignedXLogBlock buf; int r; int w; @@ -30,17 +33,21 @@ write_encrypted_segment(const char *segpath, const char *segname, int piperd) TimeLineID tli; XLogSegNo segno; - fd = open(segpath, O_CREAT | O_WRONLY | PG_BINARY, 0666); - if (fd < 0) + tmpfd = open(tmppath, O_RDONLY | PG_BINARY, 0); + if (tmpfd < 0) + pg_fatal("could not open file \"%s\": %m", tmppath); + + segfd = open(segpath, O_CREAT | O_WRONLY | PG_BINARY, 0666); + if (segfd < 0) pg_fatal("could not open file \"%s\": %m", segpath); - r = read(piperd, buf.data, XLOG_BLCKSZ); + r = read(tmpfd, buf.data, XLOG_BLCKSZ); if (r < 0) - pg_fatal("could not read from pipe: %m"); + pg_fatal("could not read file \"%s\": %m", tmppath); else if (r != XLOG_BLCKSZ) - pg_fatal("could not read from pipe: read %d of %d", - r, XLOG_BLCKSZ); + pg_fatal("could not read file \"%s\": read %d of %d", + tmppath, r, XLOG_BLCKSZ); longhdr = (XLogLongPageHeader) buf.data; walsegsz = longhdr->xlp_seg_size; @@ -59,7 +66,7 @@ write_encrypted_segment(const char *segpath, const char *segname, int piperd) TDEXLogSmgrInitWriteReuseKey(); - w = xlog_smgr->seg_write(fd, buf.data, r, pos, tli, segno, walsegsz); + w = xlog_smgr->seg_write(segfd, buf.data, r, pos, tli, segno, walsegsz); if (w < 0) pg_fatal("could not write file \"%s\": %m", segpath); @@ -71,14 +78,14 @@ write_encrypted_segment(const char *segpath, const char *segname, int piperd) while (1) { - r = read(piperd, buf.data, XLOG_BLCKSZ); + r = read(tmpfd, buf.data, XLOG_BLCKSZ); if (r == 0) break; else if (r < 0) - pg_fatal("could not read from pipe: %m"); + pg_fatal("could not read file \"%s\": %m", tmppath); - w = xlog_smgr->seg_write(fd, buf.data, r, pos, tli, segno, walsegsz); + w = xlog_smgr->seg_write(segfd, buf.data, r, pos, tli, segno, walsegsz); if (w < 0) pg_fatal("could not write file \"%s\": %m", segpath); @@ -89,7 +96,8 @@ write_encrypted_segment(const char *segpath, const char *segname, int piperd) pos += w; } - close(fd); + close(segfd); + close(tmpfd); } static void @@ -110,10 +118,9 @@ main(int argc, char *argv[]) char *targetpath; char *sep; char *targetname; - char stdoutdir[MAXPGPATH] = "/tmp/pg_tde_restoreXXXXXX"; - char stdoutpath[MAXPGPATH]; + char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_restoreXXXXXX"; + char tmppath[MAXPGPATH]; bool issegment; - int pipefd[2]; pid_t child; int status; int r; @@ -161,48 +168,27 @@ main(int argc, char *argv[]) { char *s; - if (mkdtemp(stdoutdir) == NULL) - pg_fatal("could not create temporary directory \"%s\": %m", stdoutdir); + if (mkdtemp(tmpdir) == NULL) + pg_fatal("could not create temporary directory \"%s\": %m", tmpdir); - s = stpcpy(stdoutpath, stdoutdir); + s = stpcpy(tmppath, tmpdir); s = stpcpy(s, "/"); stpcpy(s, targetname); - if (pipe(pipefd) < 0) - pg_fatal("could not create pipe: %m"); - - if (symlink("/dev/stdout", stdoutpath) < 0) - pg_fatal("could not create symlink \"%s\": %m", stdoutpath); - for (int i = 2; i < argc; i++) if (strcmp(targetpath, argv[i]) == 0) - argv[i] = stdoutpath; + argv[i] = tmppath; } child = fork(); if (child == 0) { - if (issegment) - { - close(1); - dup2(pipefd[1], 1); - close(pipefd[0]); - close(pipefd[1]); - } - if (execvp(argv[3], argv + 3) < 0) pg_fatal("exec failed: %m"); } else if (child < 0) pg_fatal("could not create background process: %m"); - if (issegment) - { - close(pipefd[1]); - write_encrypted_segment(targetpath, sourcename, pipefd[0]); - close(pipefd[0]); - } - r = waitpid(child, &status, 0); if (r == (pid_t) -1) pg_fatal("could not wait for child process: %m"); @@ -211,10 +197,15 @@ main(int argc, char *argv[]) if (status != 0) pg_fatal("%s", wait_result_to_str(status)); - if (issegment && unlink(stdoutpath) < 0) - pg_log_warning("could not remove symlink \"%s\": %m", stdoutpath); - if (issegment && rmdir(stdoutdir) < 0) - pg_log_warning("could not remove directory \"%s\": %m", stdoutdir); + if (issegment) + { + write_encrypted_segment(targetpath, sourcename, tmppath); + + if (unlink(tmppath) < 0) + pg_log_warning("could not remove file \"%s\": %m", tmppath); + if (rmdir(tmpdir) < 0) + pg_log_warning("could not remove directory \"%s\": %m", tmpdir); + } return 0; } From f63175326a38676413d1f3eb338a24abf698bbd9 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 29 Jul 2025 20:33:16 +0200 Subject: [PATCH 544/796] PG-1710 Do not hardcode length of WAL filename There is a cosntant used by PostgreSQL itself. --- contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 2 +- contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index f3377e26446b9..40d4a4ffab8b9 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -16,7 +16,7 @@ static bool is_segment(const char *filename) { - return strspn(filename, "0123456789ABCDEF") == 24 && filename[24] == '\0'; + return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && filename[XLOG_FNAME_LEN] == '\0'; } static void diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 8054770f33a3a..154946990ec7d 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -16,7 +16,7 @@ static bool is_segment(const char *filename) { - return strspn(filename, "0123456789ABCDEF") == 24 && filename[24] == '\0'; + return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && filename[XLOG_FNAME_LEN] == '\0'; } static void From 3a4db712992fcc615b2a5c8dbd103ff00c08ec7d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 30 Jul 2025 15:16:25 +0200 Subject: [PATCH 545/796] PG-1710 Decrypt partial WAL segments when archvining them While the restore_command will never be called for partial WAL segments we should sitll make sure to decrypt them when archiving so a sysadmin manually could use them. As the comemnt explains we also add the same logic to the restore command for symmetry and if someone ever would call it manually or PostgreSQL would add support for restoring partial WAL segments. If you want to learn more about partial WAL segments read the comment for CleanupAfterArchiveRecovery(). --- contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 3 ++- contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 40d4a4ffab8b9..48c05aea601a0 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -16,7 +16,8 @@ static bool is_segment(const char *filename) { - return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && filename[XLOG_FNAME_LEN] == '\0'; + return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && + (filename[XLOG_FNAME_LEN] == '\0' || strcmp(filename + XLOG_FNAME_LEN, ".partial") == 0); } static void diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 154946990ec7d..2217eb80d3217 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -13,10 +13,17 @@ #define TMPFS_DIRECTORY "/dev/shm" +/* + * Partial WAL segments are archived but never automatically fetched from the + * archive by the restore_command. We support them here for symmetry though + * since if someone would want to fetch a partial segment from the archive and + * write it to pg_wal then they would want it encrypted. + */ static bool is_segment(const char *filename) { - return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && filename[XLOG_FNAME_LEN] == '\0'; + return strspn(filename, "0123456789ABCDEF") == XLOG_FNAME_LEN && + (filename[XLOG_FNAME_LEN] == '\0' || strcmp(filename + XLOG_FNAME_LEN, ".partial") == 0); } static void From 11fa1e393e2c8d462a293e2e49d3ceb30d836f55 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 31 Jul 2025 18:23:31 +0300 Subject: [PATCH 546/796] Fix sanitizer issues in pg_rewind Fixed memory leak in the JSON parser and added suppressions for pg_rewind --- ci_scripts/suppressions/lsan.supp | 4 ++++ contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c | 1 + 2 files changed, 5 insertions(+) diff --git a/ci_scripts/suppressions/lsan.supp b/ci_scripts/suppressions/lsan.supp index 087f3164fc72c..c72849ba6cc24 100644 --- a/ci_scripts/suppressions/lsan.supp +++ b/ci_scripts/suppressions/lsan.supp @@ -51,3 +51,7 @@ leak:bin/pg_waldump/pg_waldump.c #pg_dump leak:getSchemaData leak:dumpDumpableObject + +#pg_rewind +leak:decide_file_actions +leak:GenerateRecoveryConfig diff --git a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c index e8714cb02692b..1297e16e9e924 100644 --- a/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c +++ b/contrib/pg_tde/src/catalog/tde_keyring_parse_opts.c @@ -264,6 +264,7 @@ json_kring_object_field_start(void *state, char *fname, bool isnull) break; } + pfree(fname); return JSON_SUCCESS; } From dc7c67e6b43991c8fcfa365760c259fee3ac85f2 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 31 Jul 2025 20:56:56 +0300 Subject: [PATCH 547/796] Fix sanitizer issues in wal_archiving --- ci_scripts/suppressions/lsan.supp | 4 ++++ contrib/pg_tde/src/access/pg_tde_tdemap.c | 6 ++++++ contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 8 +++++++- contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c | 8 +++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ci_scripts/suppressions/lsan.supp b/ci_scripts/suppressions/lsan.supp index c72849ba6cc24..3b7d93af62dda 100644 --- a/ci_scripts/suppressions/lsan.supp +++ b/ci_scripts/suppressions/lsan.supp @@ -55,3 +55,7 @@ leak:dumpDumpableObject #pg_rewind leak:decide_file_actions leak:GenerateRecoveryConfig + +# Returns strdup'd string which never gets freed in frontend tools. Trying to +# free it leads to comiler complains that it discards 'const' qualifier. +leak:get_progname diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index e67d7169a51e0..feec326eaf09b 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -1018,6 +1018,9 @@ pg_tde_read_last_wal_key(void) /* No keys */ if (fsize == TDE_FILE_HEADER_SIZE) { +#ifdef FRONTEND + pfree(principal_key); +#endif LWLockRelease(lock_pk); CloseTransientFile(fd); return NULL; @@ -1027,6 +1030,9 @@ pg_tde_read_last_wal_key(void) pg_tde_read_one_map_entry2(fd, file_idx, &map_entry, rlocator.dbOid); rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); +#ifdef FRONTEND + pfree(principal_key); +#endif LWLockRelease(lock_pk); CloseTransientFile(fd); diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 48c05aea601a0..f3d52d81cf0a2 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -208,7 +208,13 @@ main(int argc, char *argv[]) if (r != child) pg_fatal("child %d died, expected %d", (int) r, (int) child); if (status != 0) - pg_fatal("%s", wait_result_to_str(status)); + { + char *reason = wait_result_to_str(status); + + pg_fatal("%s", reason); + /* keep lsan happy */ + free(reason); + } if (issegment) { diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 2217eb80d3217..de808b4ef735e 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -202,7 +202,13 @@ main(int argc, char *argv[]) if (r != child) pg_fatal("child %d died, expected %d", (int) r, (int) child); if (status != 0) - pg_fatal("%s", wait_result_to_str(status)); + { + char *reason = wait_result_to_str(status); + + pg_fatal("%s", reason); + /* keep lsan happy */ + free(reason); + } if (issegment) { From ad39bf9d895d65e6b0f6f6f2cc3f99e8567f9d0e Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 24 Jul 2025 15:29:42 +0200 Subject: [PATCH 548/796] PG-1411 Make pg_resetwal work with TDE As pg_resetwal removes old WAL segments and creates new one with empty record we can do that write in unencrypted mode. However that requires new WAL key creation in case if encryption was enabled before. --- src/bin/pg_resetwal/.gitignore | 3 +++ src/bin/pg_resetwal/Makefile | 17 +++++++++++- src/bin/pg_resetwal/meson.build | 12 +++++++++ src/bin/pg_resetwal/pg_resetwal.c | 43 +++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore index 56bade5ea4401..ddb4f6fb35745 100644 --- a/src/bin/pg_resetwal/.gitignore +++ b/src/bin/pg_resetwal/.gitignore @@ -1,2 +1,5 @@ /pg_resetwal /tmp_check/ + +# Source files copied from src/backend/access/transam/ +/xlogreader.c diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile index 4228a5a772a9f..f39e8378417ce 100644 --- a/src/bin/pg_resetwal/Makefile +++ b/src/bin/pg_resetwal/Makefile @@ -21,8 +21,23 @@ OBJS = \ $(WIN32RES) \ pg_resetwal.o +ifeq ($(enable_percona_ext),yes) +override CPPFLAGS := -DFRONTEND $(CPPFLAGS) + +OBJS += \ + $(top_srcdir)/src/fe_utils/simple_list.o \ + $(top_builddir)/src/libtde/libtdexlog.a \ + $(top_builddir)/src/libtde/libtde.a \ + xlogreader.o + +override CPPFLAGS := -I$(top_srcdir)/contrib/pg_tde/src/include -I$(top_srcdir)/contrib/pg_tde/src/libkmip/libkmip/include $(CPPFLAGS) +endif + all: pg_resetwal +xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/% + rm -f $@ && $(LN_S) $< . + pg_resetwal: $(OBJS) | submake-libpgport $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) @@ -36,7 +51,7 @@ uninstall: rm -f '$(DESTDIR)$(bindir)/pg_resetwal$(X)' clean distclean: - rm -f pg_resetwal$(X) $(OBJS) + rm -f pg_resetwal$(X) $(OBJS) xlogreader.c rm -rf tmp_check check: diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build index c1239528db27f..ea7a3094452d5 100644 --- a/src/bin/pg_resetwal/meson.build +++ b/src/bin/pg_resetwal/meson.build @@ -10,10 +10,22 @@ if host_system == 'windows' '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log']) endif +link_w = [] +include_dirs = [postgres_inc] + +if percona_ext == true + link_w = [pg_tde_frontend] + include_dirs = [postgres_inc, pg_tde_inc] + pg_resetwal_sources += xlogreader_sources +endif + pg_resetwal = executable('pg_resetwal', pg_resetwal_sources, dependencies: [frontend_code], + c_args: ['-DFRONTEND'], # needed for xlogreader et al kwargs: default_bin_args, + include_directories: include_dirs, + link_with: link_w ) bin_targets += pg_resetwal diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c index e9dcb5a6d89d1..9ce4ac43d3c68 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -59,6 +59,13 @@ #include "pg_getopt.h" #include "storage/large_object.h" +#ifdef PERCONA_EXT +#include "pg_tde.h" +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" +#include "access/xlog_smgr.h" +#endif + static ControlFileData ControlFile; /* pg_control values */ static XLogSegNo newXlogSegNo; /* new XLOG segment # */ static bool guessed = false; /* T if we had to guess at any values */ @@ -344,6 +351,15 @@ main(int argc, char *argv[]) } #endif +#ifdef PERCONA_EXT + { + char tde_path[MAXPGPATH]; + snprintf(tde_path, sizeof(tde_path), "%s/%s", DataDir, PG_TDE_DATA_DIR); + pg_tde_fe_init(tde_path); + TDEXLogSmgrInit(); + } +#endif + get_restricted_token(); /* Set mask based on PGDATA permissions */ @@ -488,6 +504,22 @@ main(int argc, char *argv[]) exit(1); } +#ifdef PERCONA_EXT + /* + * The only thing that WAL will contain after reset is a checkpoint, so we can + * write it in unencrypted form. There is no sensitive data in it. But we still + * need to use TDE smgr because WAL key change may be needed. If the last WAL key + * had type "encrypted" new key will be created with type "unencrypted" to mark + * the beginning of a new unencrypted WAL record. If the last WAL key had type + * "unencrypted" it will be reused. On the startup server may create a new key + * with appropriate type according to encryption settings. + * + * We are doing a write initialization only here and not at the startup because we + * want to be sure that everything is checked and ready for writing at this point. + */ + TDEXLogSmgrInitWrite(false); +#endif + /* * Else, do the dirty deed. */ @@ -1134,7 +1166,13 @@ WriteEmptyXLOG(void) pg_fatal("could not open file \"%s\": %m", path); errno = 0; +#ifdef PERCONA_EXT + if (xlog_smgr->seg_write(fd, buffer.data, XLOG_BLCKSZ, 0, + ControlFile.checkPointCopy.ThisTimeLineID, + newXlogSegNo, WalSegSz) != XLOG_BLCKSZ) +#else if (write(fd, buffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ) +#endif { /* if write didn't set errno, assume problem is no disk space */ if (errno == 0) @@ -1142,6 +1180,11 @@ WriteEmptyXLOG(void) pg_fatal("could not write file \"%s\": %m", path); } +#ifdef PERCONA_EXT + /* If we used xlog smgr, we need to update the file offset */ + lseek(fd, XLOG_BLCKSZ, SEEK_CUR); +#endif + /* Fill the rest of the file with zeroes */ memset(buffer.data, 0, XLOG_BLCKSZ); for (nbytes = XLOG_BLCKSZ; nbytes < WalSegSz; nbytes += XLOG_BLCKSZ) From 342122cecf95c743c067821e97ee46f512285012 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Thu, 24 Jul 2025 17:37:12 +0200 Subject: [PATCH 549/796] PG-1411 Add pg_resetwal tap tests for TDE setup These tests are copy of original pg_resetwal tests with enalbed WAL encryption and removed flags validation as we interested here only in proper enrypted WAL handling. --- contrib/pg_tde/t/pg_resetwal_basic.pl | 150 ++++++++++++++++++++++ contrib/pg_tde/t/pg_resetwal_corrupted.pl | 92 +++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 contrib/pg_tde/t/pg_resetwal_basic.pl create mode 100644 contrib/pg_tde/t/pg_resetwal_corrupted.pl diff --git a/contrib/pg_tde/t/pg_resetwal_basic.pl b/contrib/pg_tde/t/pg_resetwal_basic.pl new file mode 100644 index 0000000000000..78890f633cf83 --- /dev/null +++ b/contrib/pg_tde/t/pg_resetwal_basic.pl @@ -0,0 +1,150 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +unlink('/tmp/pg_resetwal_basic.per'); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', q{ +track_commit_timestamp = on + +# WAL Encryption +shared_preload_libraries = 'pg_tde' +}); + +$node->start; +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_resetwal_basic.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); + +$node->append_conf( + 'postgresql.conf', q{ +pg_tde.wal_encrypt = on +}); +$node->stop; + + +command_like([ 'pg_resetwal', '-n', $node->data_dir ], + qr/checkpoint/, 'pg_resetwal -n produces output'); + + +# Permissions on PGDATA should be default +SKIP: +{ + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os); + + ok(check_mode_recursive($node->data_dir, 0700, 0600), + 'check PGDATA permissions'); +} + +command_ok([ 'pg_resetwal', '-D', $node->data_dir ], 'pg_resetwal runs'); +$node->start; +is($node->safe_psql("postgres", "SELECT 1;"), + 1, 'server running and working after reset'); + +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/lock file .* exists/, + 'fails if server running'); + +$node->stop('immediate'); +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/database server was not shut down cleanly/, + 'does not run after immediate shutdown'); +command_ok( + [ 'pg_resetwal', '-f', $node->data_dir ], + 'runs after immediate shutdown with force'); +$node->start; +is($node->safe_psql("postgres", "SELECT 1;"), + 1, 'server running and working after forced reset'); + +$node->stop; + +# check various command-line handling + +# Note: This test intends to check that a nonexistent data directory +# gives a reasonable error message. Because of the way the code is +# currently structured, you get an error about readings permissions, +# which is perhaps suboptimal, so feel free to update this test if +# this gets improved. + + +# run with control override options + +my $out = (run_command([ 'pg_resetwal', '-n', $node->data_dir ]))[0]; +$out =~ /^Database block size: *(\d+)$/m or die; +my $blcksz = $1; + +my @cmd = ('pg_resetwal', '-D', $node->data_dir); + +# some not-so-critical hardcoded values +push @cmd, '-e', 1; +push @cmd, '-l', '00000001000000320000004B'; +push @cmd, '-o', 100_000; +push @cmd, '--wal-segsize', 1; + +# these use the guidance from the documentation + +sub get_slru_files +{ + opendir(my $dh, $node->data_dir . '/' . $_[0]) or die $!; + my @files = sort grep { /[0-9A-F]+/ } readdir $dh; + closedir $dh; + return @files; +} + +my (@files, $mult); + +@files = get_slru_files('pg_commit_ts'); +# XXX: Should there be a multiplier, similar to the other options? +# -c argument is "old,new" +push @cmd, + '-c', + sprintf("%d,%d", hex($files[0]) == 0 ? 3 : hex($files[0]), hex($files[-1])); + +@files = get_slru_files('pg_multixact/offsets'); +$mult = 32 * $blcksz / 4; +# -m argument is "new,old" +push @cmd, '-m', + sprintf("%d,%d", + (hex($files[-1]) + 1) * $mult, + hex($files[0]) == 0 ? 1 : hex($files[0] * $mult)); + +@files = get_slru_files('pg_multixact/members'); +$mult = 32 * int($blcksz / 20) * 4; +push @cmd, '-O', (hex($files[-1]) + 1) * $mult; + +@files = get_slru_files('pg_xact'); +$mult = 32 * $blcksz * 4; +push @cmd, + '-u', (hex($files[0]) == 0 ? 3 : hex($files[0]) * $mult), + '-x', ((hex($files[-1]) + 1) * $mult); + +command_ok([ @cmd, '-n' ], 'runs with control override options, dry run'); +command_ok(\@cmd, 'runs with control override options'); +command_like( + [ 'pg_resetwal', '-n', $node->data_dir ], + qr/^Latest checkpoint's NextOID: *100000$/m, + 'spot check that control changes were applied'); + +$node->start; +ok(1, 'server started after reset'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_resetwal_corrupted.pl b/contrib/pg_tde/t/pg_resetwal_corrupted.pl new file mode 100644 index 0000000000000..74cc66a2c8030 --- /dev/null +++ b/contrib/pg_tde/t/pg_resetwal_corrupted.pl @@ -0,0 +1,92 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# Tests for handling a corrupted pg_control + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +unlink('/tmp/pg_resetwal_corrupted.per'); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', q{ + +# WAL Encryption +shared_preload_libraries = 'pg_tde' +}); + +$node->start; +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_corrupted.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); + +$node->append_conf( + 'postgresql.conf', q{ +pg_tde.wal_encrypt = on +}); +$node->stop; + +my $pg_control = $node->data_dir . '/global/pg_control'; +my $size = -s $pg_control; + +# Read out the head of the file to get PG_CONTROL_VERSION in +# particular. +my $data; +open my $fh, '<', $pg_control or BAIL_OUT($!); +binmode $fh; +read $fh, $data, 16 or die $!; +close $fh; + +# Fill pg_control with zeros +open $fh, '>', $pg_control or BAIL_OUT($!); +binmode $fh; +print $fh pack("x[$size]"); +close $fh; + +command_checks_all( + [ 'pg_resetwal', '-n', $node->data_dir ], + 0, + [qr/pg_control version number/], + [ + qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/ + ], + 'processes corrupted pg_control all zeroes'); + +# Put in the previously saved header data. This uses a different code +# path internally, allowing us to process a zero WAL segment size. +open $fh, '>', $pg_control or BAIL_OUT($!); +binmode $fh; +print $fh $data, pack("x[" . ($size - 16) . "]"); +close $fh; + +command_checks_all( + [ 'pg_resetwal', '-n', $node->data_dir ], + 0, + [qr/pg_control version number/], + [ + qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/ + ], + 'processes zero WAL segment size'); + +# now try to run it +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/not proceeding because control file values were guessed/, + 'does not run when control file values were guessed'); +command_ok([ 'pg_resetwal', '-f', $node->data_dir ], + 'runs with force when control file values were guessed'); + +done_testing(); From efe9f0431b183989604b645460d5c8d89003c358 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 1 Aug 2025 16:29:50 +0300 Subject: [PATCH 550/796] Add cards in the index page of the website (#488) --- contrib/pg_tde/documentation/docs/index.md | 36 ++++++++++++++++++++-- contrib/pg_tde/documentation/variables.yml | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/index.md b/contrib/pg_tde/documentation/docs/index.md index 21e9058e3736a..e8b3ed6a5e6a7 100644 --- a/contrib/pg_tde/documentation/docs/index.md +++ b/contrib/pg_tde/documentation/docs/index.md @@ -6,6 +6,36 @@ Percona Transparent Data Encryption for PostgreSQL (`pg_tde`) is an open source, There is no safe upgrade path from the previous versions, such as Release Candidate 2, to the General Availability (GA) version of `pg_tde`. We recommend starting with a **clean installation** for GA deployments. Avoid using RC environments in production. -[Learn more about TDE and pg_tde :material-arrow-right:](index/about-tde.md){.md-button} -[Get started with installation :material-arrow-right:](install.md){.md-button} -[Check out what's new in pg_tde {{release}} :material-arrow-right:](release-notes/release-notes.md){.md-button} +
+ +### :material-progress-download: Installation guide { .title } + +Get started quickly with the step-by-step installation instructions. + +[How to install `pg_tde` :material-arrow-right:](install.md){ .md-button } + +
+ +### :rocket: Features { .title } + +Explore what features Percona's `pg_tde` extension brings to PostgreSQL. + +[Check what you can do with `pg_tde` :material-arrow-right:](features.md){ .md-button } + +
+ +### :material-cog-refresh-outline: Architecture { .title } + +Understand how `pg_tde` integrates into PostgreSQL with Percona's architecture. Learn how keys are managed, how encryption is applied, and how our design ensures performance and security. + +[Check what’s under the hood for `pg_tde` :material-arrow-right:](architecture/architecture.md){.md-button} + +
+ +### :loudspeaker: What's new? { .title } + +Learn about the releases and changes in `pg_tde`. + +[Check what’s new in the latest version :material-arrow-right:](release-notes/{{latestreleasenotes}}.md){.md-button} +
+
diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index 2938e44a53a96..bf698a07c2272 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -1,5 +1,6 @@ #Variables used throughout the docs +latestreleasenotes: 'release-notes-v1.0' tdeversion: '1.0' release: '1.0' pgversion17: '17.5' From 0319cb352bfc90236f1e9a6921d5602d422d2983 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Fri, 1 Aug 2025 19:40:04 +0300 Subject: [PATCH 551/796] Fix lsan issues in pg_resetwal --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 3 +++ src/bin/pg_resetwal/pg_resetwal.c | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index feec326eaf09b..379e56bc54cf3 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -522,6 +522,9 @@ pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocat pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); +#ifdef FRONTEND + free(principal_key); +#endif LWLockRelease(tde_lwlock_enc_keys()); } diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c index 9ce4ac43d3c68..3cebcb6adabe2 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -408,7 +408,11 @@ main(int argc, char *argv[]) WalSegSz = ControlFile.xlog_seg_size; if (log_fname != NULL) + { XLogFromFileName(log_fname, &minXlogTli, &minXlogSegNo, WalSegSz); + free(log_fname); + } + /* * Also look at existing segment files to set up newXlogSegNo @@ -639,6 +643,8 @@ read_controlfile(void) memcpy(&ControlFile, buffer, sizeof(ControlFile)); + free(buffer); + /* return false if WAL segment size is not valid */ if (!IsValidWalSegSize(ControlFile.xlog_seg_size)) { @@ -652,6 +658,8 @@ read_controlfile(void) return true; } + free(buffer); + /* Looks like it's a mess. */ pg_log_warning("pg_control exists but is broken or wrong version; ignoring it"); return false; From d707a967e3f9315c338621fd8ce84a69a28440a2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 6 Aug 2025 11:36:39 +0200 Subject: [PATCH 552/796] PG-1842 Minor optimization and cleanup of redo of SMGR create The code we ran when redoing a smgrcreate() was overly complex and not necessary to run. If the file descriptor was open we skipped it but if not we ran a bit of pointless code since the key creation is handled by it is own WAL record which is always before the SMGR creation. Also improve some outdated comments. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 68 +++++++++------------------ 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index b6dbcbb4c915f..d2e44e8dd295f 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -359,43 +359,32 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool TDESMgrRelation *tdereln = (TDESMgrRelation *) reln; InternalKey *key; - /* Copied from mdcreate() in md.c */ - if (isRedo && tdereln->md_num_open_segs[forknum] > 0) - return; + mdcreate(relold, reln, forknum, isRedo); /* - * This is the only function that gets called during actual CREATE - * TABLE/INDEX (EVENT TRIGGER) + * Creating the key is handled by a separate WAL record on redo and + * fetching the key can be delayed to when we actually need it like we do + * for other forks anyway. */ + if (isRedo) + return; - mdcreate(relold, reln, forknum, isRedo); - + /* + * Only create keys when creating the main fork. Other forks are created + * later and use the key which was created when creating the main fork. + */ if (forknum != MAIN_FORKNUM) - { - /* - * Only create keys when creating the main fork. Other forks can be - * created later, even during tde creation events. We definitely do - * not want to create keys then, even later, when we encrypt all - * forks! - * - * Later calls then decide to encrypt or not based on the existence of - * the key. - */ return; - } - if (!isRedo) - { - /* - * If we have a key for this relation already, we need to remove it. - * This can happen if OID is re-used after a crash left a key for a - * non-existing relation in the key file. - * - * If we're in redo, a separate WAL record will make sure the key is - * removed. - */ - tde_smgr_delete_key(&reln->smgr_rlocator); - } + /* + * If we have a key for this relation already, we need to remove it. This + * can happen if OID is re-used after a crash left a key for a + * non-existing relation in the key file. + * + * If we're in redo, a separate WAL record will make sure the key is + * removed. + */ + tde_smgr_delete_key(&reln->smgr_rlocator); if (!tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) { @@ -403,23 +392,8 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool return; } - if (isRedo) - { - /* - * If we're in redo, the WAL record for creating the key has already - * happened and we can just fetch it. - */ - key = tde_smgr_get_key(&reln->smgr_rlocator); - - Assert(key); - if (!key) - elog(ERROR, "could not get key when creating encrypted relation"); - } - else - { - key = tde_smgr_create_key(&reln->smgr_rlocator); - tde_smgr_log_create_key(&reln->smgr_rlocator); - } + key = tde_smgr_create_key(&reln->smgr_rlocator); + tde_smgr_log_create_key(&reln->smgr_rlocator); tdereln->encryption_status = RELATION_KEY_AVAILABLE; tdereln->relKey = *key; From 745995125cf4c48e68f8329fe6f3be566848f956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 5 Aug 2025 14:05:25 +0200 Subject: [PATCH 553/796] Use raw key data for stream crypto This is the only field from the InternalKey structure that's actually used, and other "raw" crypto functions doesn't use these structures. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 19 ++++++++++++++----- contrib/pg_tde/src/encryption/enc_tde.c | 10 ++++++++-- .../pg_tde/src/include/encryption/enc_tde.h | 8 +++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 5578167a9b471..1c51ebffd360d 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -265,9 +265,13 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #endif CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); - pg_tde_stream_crypt(iv_prefix, offset, - (char *) buf, count, - enc_buff, key, &EncryptionCryptCtx); + pg_tde_stream_crypt(iv_prefix, + offset, + (char *) buf, + count, + enc_buff, + key->key, + &EncryptionCryptCtx); return pg_pwrite(fd, enc_buff, count, offset); } @@ -392,8 +396,13 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %X/%X", dec_off, dec_off - offset, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); #endif - pg_tde_stream_crypt(iv_prefix, dec_off, dec_buf, dec_sz, dec_buf, - &curr_key->key, &curr_key->crypt_ctx); + pg_tde_stream_crypt(iv_prefix, + dec_off, + dec_buf, + dec_sz, + dec_buf, + curr_key->key.key, + &curr_key->crypt_ctx); } } } diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index 04646256bb4cc..4c783c68f8a69 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -50,7 +50,13 @@ pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type) * start_offset: is the absolute location of start of data in the file. */ void -pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr) +pg_tde_stream_crypt(const char *iv_prefix, + uint32 start_offset, + const char *data, + uint32 data_len, + char *out, + const uint8 *key, + void **ctxPtr) { const uint64 aes_start_block = start_offset / AES_BLOCK_SIZE; const uint64 aes_end_block = (start_offset + data_len + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE; @@ -65,7 +71,7 @@ pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data uint32 current_batch_bytes; uint64 batch_end_block = Min(batch_start_block + NUM_AES_BLOCKS_IN_BATCH, aes_end_block); - AesCtrEncryptedZeroBlocks(ctxPtr, key->key, iv_prefix, batch_start_block, batch_end_block, enc_key); + AesCtrEncryptedZeroBlocks(ctxPtr, key, iv_prefix, batch_start_block, batch_end_block, enc_key); #ifdef ENCRYPTION_DEBUG { diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index 93148dba244a6..dfb6cbbb0dadb 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -8,6 +8,12 @@ #include "access/pg_tde_tdemap.h" extern void pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type); -extern void pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, uint32 data_len, char *out, InternalKey *key, void **ctxPtr); +extern void pg_tde_stream_crypt(const char *iv_prefix, + uint32 start_offset, + const char *data, + uint32 data_len, + char *out, + const uint8 *key, + void **ctxPtr); #endif /* ENC_TDE_H */ From a90de9898a104d4ef0b5957bee3258ae7f84bfa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 5 Aug 2025 14:12:54 +0200 Subject: [PATCH 554/796] Expose function to sign principal key data This function probably belongs elsewhere than in the key file code, but that's where it currently resides so expose it so it can also be used elsewhere. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 3 +-- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 379e56bc54cf3..8cb182e3146d6 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -68,7 +68,6 @@ static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEn static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); #ifndef FRONTEND -static void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); @@ -432,7 +431,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) CloseTransientFile(fd); } -static void +void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) { signed_key_info->data = principal_key->keyInfo; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index e6d6d982404ec..121720827befa 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -104,6 +104,8 @@ extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincip extern void pg_tde_delete_principal_key(Oid dbOid); extern void pg_tde_delete_principal_key_redo(Oid dbOid); +extern void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key); + const char *tde_sprint_key(InternalKey *k); #endif /* PG_TDE_MAP_H */ From 25ba87785203fe644fd383ac5ec9c37fa48fcf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 5 Aug 2025 22:47:30 +0200 Subject: [PATCH 555/796] PG-1813 Separate format for smgr and wal keyfiles We want to add timeline information to the wal keys and cannot easily do so without affecting existing clusters' relation key files. This commit does the bare minimum to separate the two completely and as such contains a fair bit of duplicated code. The file format for the WAL key file is exactly the same before and after this commit. There is _a lot_ of cleanup that will have to be done on both sides of this separation, but this is a bit of "it gets worse before it gets better". --- contrib/pg_tde/Makefile | 1 + contrib/pg_tde/Makefile.tools | 1 + contrib/pg_tde/meson.build | 2 + contrib/pg_tde/src/access/pg_tde_tdemap.c | 333 +------ contrib/pg_tde/src/access/pg_tde_xlog.c | 7 +- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 871 ++++++++++++++++++ contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 10 +- .../pg_tde/src/catalog/tde_principal_key.c | 40 +- .../pg_tde/src/include/access/pg_tde_tdemap.h | 22 - .../src/include/access/pg_tde_xlog_keys.h | 65 ++ 10 files changed, 999 insertions(+), 353 deletions(-) create mode 100644 contrib/pg_tde/src/access/pg_tde_xlog_keys.c create mode 100644 contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 3eba916acc935..25726c461125e 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -31,6 +31,7 @@ OBJS = src/encryption/enc_tde.o \ src/encryption/enc_aes.o \ src/access/pg_tde_tdemap.o \ src/access/pg_tde_xlog.o \ +src/access/pg_tde_xlog_keys.o \ src/access/pg_tde_xlog_smgr.o \ src/keyring/keyring_curl.o \ src/keyring/keyring_file.o \ diff --git a/contrib/pg_tde/Makefile.tools b/contrib/pg_tde/Makefile.tools index 4832c74c9db68..6ab1ad75166d6 100644 --- a/contrib/pg_tde/Makefile.tools +++ b/contrib/pg_tde/Makefile.tools @@ -4,6 +4,7 @@ TDE_XLOG_OBJS = \ TDE_OBJS = \ src/access/pg_tde_tdemap.frontend \ src/catalog/tde_keyring.frontend \ + src/access/pg_tde_xlog_keys.frontend \ src/catalog/tde_keyring_parse_opts.frontend \ src/catalog/tde_principal_key.frontend \ src/common/pg_tde_utils.frontend \ diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 5785f3148a25c..f2c21edc8dd2f 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -3,6 +3,7 @@ curldep = dependency('libcurl') pg_tde_sources = files( 'src/access/pg_tde_tdemap.c', 'src/access/pg_tde_xlog.c', + 'src/access/pg_tde_xlog_keys.c', 'src/access/pg_tde_xlog_smgr.c', 'src/catalog/tde_keyring.c', 'src/catalog/tde_keyring_parse_opts.c', @@ -24,6 +25,7 @@ pg_tde_sources = files( tde_frontend_sources = files( 'src/access/pg_tde_tdemap.c', + 'src/access/pg_tde_xlog_keys.c', 'src/access/pg_tde_xlog_smgr.c', 'src/catalog/tde_keyring.c', 'src/catalog/tde_keyring_parse_opts.c', diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 8cb182e3146d6..fa8d01d54ad91 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -43,34 +43,27 @@ #define MAP_ENTRY_SIZE sizeof(TDEMapEntry) #define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) -#define MaxXLogRecPtr (~(XLogRecPtr)0) - typedef struct TDEFileHeader { int32 file_version; TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; -static WALKeyCacheRec *tde_wal_key_cache = NULL; -static WALKeyCacheRec *tde_wal_key_last_rec = NULL; - -static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); -static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); -static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset); -static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId); -static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn); #ifndef FRONTEND static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); static void finalize_key_rotation(const char *path_old, const char *path_new); +static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); +static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); +static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); +static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) @@ -259,6 +252,10 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p char old_path[MAXPGPATH], new_path[MAXPGPATH]; + /* This function cannot be used to rotate the server key. */ + Assert(principal_key); + Assert(principal_key->keyInfo.databaseId != GLOBAL_DATA_TDE_OID); + pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); @@ -363,74 +360,6 @@ pg_tde_delete_principal_key(Oid dbOid) #endif /* !FRONTEND */ -/* - * It's called by seg_write inside crit section so no pallocs, hence - * needs keyfile_path - */ -void -pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) -{ - LWLock *lock_pk = tde_lwlock_enc_keys(); - int fd; - off_t read_pos, - write_pos, - last_key_idx; - - LWLockAcquire(lock_pk, LW_EXCLUSIVE); - - fd = pg_tde_open_file_write(keyfile_path, NULL, false, &read_pos); - - last_key_idx = ((lseek(fd, 0, SEEK_END) - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE) - 1; - write_pos = TDE_FILE_HEADER_SIZE + (last_key_idx * MAP_ENTRY_SIZE) + offsetof(TDEMapEntry, enc_key) + offsetof(InternalKey, start_lsn); - - if (pg_pwrite(fd, &lsn, sizeof(XLogRecPtr), write_pos) != sizeof(XLogRecPtr)) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not write tde key data file: %m")); - } - - /* - * If the last key overlaps with the previous, then invalidate the - * previous one. This may (and will) happen on replicas because it - * re-reads primary's data from the beginning of the segment on restart. - */ - if (last_key_idx > 0) - { - off_t prev_key_pos = TDE_FILE_HEADER_SIZE + ((last_key_idx - 1) * MAP_ENTRY_SIZE); - TDEMapEntry prev_map_entry; - - if (pg_pread(fd, &prev_map_entry, MAP_ENTRY_SIZE, prev_key_pos) != MAP_ENTRY_SIZE) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not read previous WAL key: %m")); - } - - if (prev_map_entry.enc_key.start_lsn >= lsn) - { - prev_map_entry.enc_key.type = TDE_KEY_TYPE_WAL_INVALID; - - if (pg_pwrite(fd, &prev_map_entry, MAP_ENTRY_SIZE, prev_key_pos) != MAP_ENTRY_SIZE) - { - ereport(ERROR, - errcode_for_file_access(), - errmsg("could not write invalidated key: %m")); - } - } - } - - if (pg_fsync(fd) != 0) - { - ereport(data_sync_elevel(ERROR), - errcode_for_file_access(), - errmsg("could not fsync file: %m")); - } - - LWLockRelease(lock_pk); - CloseTransientFile(fd); -} - void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) { @@ -449,6 +378,7 @@ pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const signed_key_info->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } +#ifndef FRONTEND static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data) { @@ -469,7 +399,9 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ map_entry->enc_key.key, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } +#endif +#ifndef FRONTEND static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path) { @@ -492,41 +424,9 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, *offset += bytes_written; } - -/* - * Generates a new internal key for WAL and adds it to the key file. - * - * We have a special function for WAL as it is being called during recovery - * start so there should be no XLog records and aquired locks. The key is - * always created with start_lsn = InvalidXLogRecPtr. Which will be updated - * with the actual lsn by the first WAL write. - */ -void -pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type) -{ - TDEPrincipalKey *principal_key; - - LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); - if (principal_key == NULL) - { - ereport(ERROR, - errmsg("principal key not configured"), - errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); - } - - /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ - pg_tde_generate_internal_key(rel_key_data, entry_type); - - pg_tde_write_key_map_entry(newrlocator, rel_key_data, principal_key); - -#ifdef FRONTEND - free(principal_key); #endif - LWLockRelease(tde_lwlock_enc_keys()); -} +#ifndef FRONTEND /* * The caller must hold an exclusive lock on the key file to avoid * concurrent in place updates leading to data conflicts. @@ -580,6 +480,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re CloseTransientFile(map_fd); } +#endif /* * Returns true if we find a valid match; e.g. type is not set to @@ -727,6 +628,7 @@ pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr return fd; } +#ifndef FRONTEND /* * Open for write and Validate File Header: * header: {Format Version, Principal Key Name} @@ -756,6 +658,7 @@ pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *curr_pos = bytes_read + bytes_written; return fd; } +#endif /* * Read TDE file header from a TDE file and fill in the fheader data structure. @@ -780,6 +683,7 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader } } +#ifndef FRONTEND /* * Write TDE file header to a TDE file. */ @@ -812,6 +716,7 @@ pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrinci return fd; } +#endif /* * Returns true if a map entry if found or false if we have reached the end of @@ -836,27 +741,6 @@ pg_tde_read_one_map_entry(int map_file, TDEMapEntry *map_entry, off_t *offset) return true; } -/* - * TODO: Unify with pg_tde_read_one_map_entry() - */ -static void -pg_tde_read_one_map_entry2(int fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId) -{ - off_t read_pos; - - read_pos = TDE_FILE_HEADER_SIZE + key_index * MAP_ENTRY_SIZE; - if (pg_pread(fd, map_entry, MAP_ENTRY_SIZE, read_pos) != MAP_ENTRY_SIZE) - { - char db_map_path[MAXPGPATH]; - - pg_tde_set_db_file_path(databaseId, db_map_path); - ereport(FATAL, - errcode_for_file_access(), - errmsg("could not find the required key at index %d in tde data file \"%s\": %m", - key_index, db_map_path)); - } -} - /* * Get the principal key from the key file. The caller must hold * a LW_SHARED or higher lock on files before calling this function. @@ -975,186 +859,3 @@ pg_tde_get_smgr_key(RelFileLocator rel) return rel_key; } - -/* - * Returns last (the most recent) key for a given relation - */ -WALKeyCacheRec * -pg_tde_get_last_wal_key(void) -{ - return tde_wal_key_last_rec; -} - -WALKeyCacheRec * -pg_tde_get_wal_cache_keys(void) -{ - return tde_wal_key_cache; -} - -InternalKey * -pg_tde_read_last_wal_key(void) -{ - RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH]; - off_t read_pos = 0; - LWLock *lock_pk = tde_lwlock_enc_keys(); - TDEPrincipalKey *principal_key; - int fd; - int file_idx; - TDEMapEntry map_entry; - InternalKey *rel_key_data; - off_t fsize; - - LWLockAcquire(lock_pk, LW_EXCLUSIVE); - principal_key = GetPrincipalKey(rlocator.dbOid, LW_EXCLUSIVE); - if (principal_key == NULL) - { - LWLockRelease(lock_pk); - elog(DEBUG1, "init WAL encryption: no principal key"); - return NULL; - } - pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - - fd = pg_tde_open_file_read(db_map_path, false, &read_pos); - fsize = lseek(fd, 0, SEEK_END); - /* No keys */ - if (fsize == TDE_FILE_HEADER_SIZE) - { -#ifdef FRONTEND - pfree(principal_key); -#endif - LWLockRelease(lock_pk); - CloseTransientFile(fd); - return NULL; - } - - file_idx = ((fsize - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE) - 1; - pg_tde_read_one_map_entry2(fd, file_idx, &map_entry, rlocator.dbOid); - - rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); -#ifdef FRONTEND - pfree(principal_key); -#endif - LWLockRelease(lock_pk); - CloseTransientFile(fd); - - return rel_key_data; -} - -/* Fetches WAL keys from disk and adds them to the WAL cache */ -WALKeyCacheRec * -pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) -{ - RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH]; - off_t read_pos = 0; - LWLock *lock_pk = tde_lwlock_enc_keys(); - TDEPrincipalKey *principal_key; - int fd; - int keys_count; - WALKeyCacheRec *return_wal_rec = NULL; - - LWLockAcquire(lock_pk, LW_SHARED); - principal_key = GetPrincipalKey(rlocator.dbOid, LW_SHARED); - if (principal_key == NULL) - { - LWLockRelease(lock_pk); - elog(DEBUG1, "fetch WAL keys: no principal key"); - return NULL; - } - - pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - - fd = pg_tde_open_file_read(db_map_path, false, &read_pos); - - keys_count = (lseek(fd, 0, SEEK_END) - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE; - - /* - * If there is no keys, return a fake one (with the range 0-infinity) so - * the reader won't try to check the disk all the time. This for the - * walsender in case if WAL is unencrypted and never was. - */ - if (keys_count == 0) - { - WALKeyCacheRec *wal_rec; - InternalKey stub_key = { - .start_lsn = InvalidXLogRecPtr, - }; - - wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); - -#ifdef FRONTEND - /* The backend frees it after copying to the cache. */ - pfree(principal_key); -#endif - LWLockRelease(lock_pk); - CloseTransientFile(fd); - return wal_rec; - } - - for (int file_idx = 0; file_idx < keys_count; file_idx++) - { - TDEMapEntry map_entry; - - pg_tde_read_one_map_entry2(fd, file_idx, &map_entry, rlocator.dbOid); - - /* - * Skip new (just created but not updated by write) and invalid keys - */ - if (map_entry.enc_key.start_lsn != InvalidXLogRecPtr && - (map_entry.enc_key.type == TDE_KEY_TYPE_WAL_UNENCRYPTED || - map_entry.enc_key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) && - map_entry.enc_key.start_lsn >= start_lsn) - { - InternalKey *rel_key_data = tde_decrypt_rel_key(principal_key, &map_entry); - WALKeyCacheRec *wal_rec; - - wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data, map_entry.enc_key.start_lsn); - - pfree(rel_key_data); - - if (!return_wal_rec) - return_wal_rec = wal_rec; - } - } -#ifdef FRONTEND - pfree(principal_key); -#endif - LWLockRelease(lock_pk); - CloseTransientFile(fd); - - return return_wal_rec; -} - -static WALKeyCacheRec * -pg_tde_add_wal_key_to_cache(InternalKey *key, XLogRecPtr start_lsn) -{ - WALKeyCacheRec *wal_rec; -#ifndef FRONTEND - MemoryContext oldCtx; - - oldCtx = MemoryContextSwitchTo(TopMemoryContext); -#endif - wal_rec = palloc0_object(WALKeyCacheRec); -#ifndef FRONTEND - MemoryContextSwitchTo(oldCtx); -#endif - - wal_rec->start_lsn = start_lsn; - wal_rec->end_lsn = MaxXLogRecPtr; - wal_rec->key = *key; - wal_rec->crypt_ctx = NULL; - if (!tde_wal_key_last_rec) - { - tde_wal_key_last_rec = wal_rec; - tde_wal_key_cache = tde_wal_key_last_rec; - } - else - { - tde_wal_key_last_rec->next = wal_rec; - tde_wal_key_last_rec->end_lsn = wal_rec->start_lsn; - tde_wal_key_last_rec = wal_rec; - } - - return wal_rec; -} diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 0081acb269d57..2ffe99e7f4478 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -12,7 +12,9 @@ #include "utils/guc.h" #include "utils/memutils.h" +#include "access/pg_tde_xlog_keys.h" #include "access/pg_tde_xlog.h" +#include "catalog/tde_global_space.h" #include "catalog/tde_keyring.h" #include "encryption/enc_tde.h" #include "pg_tde.h" @@ -51,7 +53,10 @@ tdeheap_rmgr_redo(XLogReaderState *record) { TDESignedPrincipalKeyInfo *mkey = (TDESignedPrincipalKeyInfo *) XLogRecGetData(record); - pg_tde_save_principal_key_redo(mkey); + if (mkey->data.databaseId == GLOBAL_DATA_TDE_OID) + pg_tde_save_server_key_redo(mkey); + else + pg_tde_save_principal_key_redo(mkey); } else if (info == XLOG_TDE_DELETE_RELATION_KEY) { diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c new file mode 100644 index 0000000000000..4187fa9768e7c --- /dev/null +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -0,0 +1,871 @@ +#include "postgres.h" + +#include +#include + +#include "access/xlog_internal.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "storage/fd.h" +#include "utils/memutils.h" + +#include "access/pg_tde_tdemap.h" +#include "access/pg_tde_xlog_keys.h" +#include "access/pg_tde_xlog.h" +#include "catalog/tde_global_space.h" +#include "catalog/tde_principal_key.h" +#include "encryption/enc_aes.h" +#include "encryption/enc_tde.h" + +#ifdef FRONTEND +#include "pg_tde_fe.h" +#endif + +#define PG_TDE_WAL_KEY_FILE_MAGIC 0x03454454 /* version ID value = TDE 03 */ + +#define MaxXLogRecPtr (~(XLogRecPtr)0) + +static WALKeyCacheRec *tde_wal_key_cache = NULL; +static WALKeyCacheRec *tde_wal_key_last_rec = NULL; + +static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key, XLogRecPtr start_lsn); +static WalEncryptionKey *pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); +static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data); +static int pg_tde_open_wal_key_file_basic(const char *filename, int flags, bool ignore_missing); +static int pg_tde_open_wal_key_file_read(const char *filename, bool ignore_missing, off_t *curr_pos); +static int pg_tde_open_wal_key_file_write(const char *filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); +static bool pg_tde_read_one_wal_key_file_entry(int fd, WalKeyFileEntry *entry, off_t *offset); +static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, WalKeyFileEntry *entry, Oid databaseId); +static void pg_tde_wal_key_file_header_read(const char *filename, int fd, WalKeyFileHeader *fheader, off_t *bytes_read); +static int pg_tde_wal_key_file_header_write(const char *filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); +static void pg_tde_write_one_wal_key_file_entry(int fd, const WalKeyFileEntry *entry, off_t *offset, const char *db_map_path); +static void pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key); + +/* + * It's called by seg_write inside crit section so no pallocs, hence + * needs keyfile_path + */ +void +pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) +{ + LWLock *lock_pk = tde_lwlock_enc_keys(); + int fd; + off_t read_pos, + write_pos, + last_key_idx; + + LWLockAcquire(lock_pk, LW_EXCLUSIVE); + + fd = pg_tde_open_wal_key_file_write(keyfile_path, NULL, false, &read_pos); + + last_key_idx = ((lseek(fd, 0, SEEK_END) - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; + write_pos = sizeof(WalKeyFileHeader) + + (last_key_idx * sizeof(WalKeyFileEntry)) + + offsetof(WalKeyFileEntry, enc_key) + + offsetof(WalEncryptionKey, start_lsn); + + if (pg_pwrite(fd, &lsn, sizeof(XLogRecPtr), write_pos) != sizeof(XLogRecPtr)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write WAL key data file: %m")); + } + + /* + * If the last key overlaps with the previous, then invalidate the + * previous one. This may (and will) happen on replicas because it + * re-reads primary's data from the beginning of the segment on restart. + */ + if (last_key_idx > 0) + { + off_t prev_key_pos = sizeof(WalKeyFileHeader) + ((last_key_idx - 1) * sizeof(WalKeyFileEntry)); + WalKeyFileEntry prev_entry; + + if (pg_pread(fd, &prev_entry, sizeof(WalKeyFileEntry), prev_key_pos) != sizeof(WalKeyFileEntry)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not read previous WAL key: %m")); + } + + if (prev_entry.enc_key.start_lsn >= lsn) + { + prev_entry.enc_key.type = TDE_KEY_TYPE_WAL_INVALID; + + if (pg_pwrite(fd, &prev_entry, sizeof(WalKeyFileEntry), prev_key_pos) != sizeof(WalKeyFileEntry)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write invalidated key: %m")); + } + } + } + + if (pg_fsync(fd) != 0) + { + ereport(data_sync_elevel(ERROR), + errcode_for_file_access(), + errmsg("could not fsync file: %m")); + } + + LWLockRelease(lock_pk); + CloseTransientFile(fd); +} + +/* + * Generates a new internal key for WAL and adds it to the key file. + * + * We have a special function for WAL as it is being called during recovery + * start so there should be no XLog records and aquired locks. The key is + * always created with start_lsn = InvalidXLogRecPtr. Which will be updated + * with the actual lsn by the first WAL write. + */ +void +pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, + const RelFileLocator *newrlocator, + TDEMapEntryType entry_type) +{ + TDEPrincipalKey *principal_key; + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); + if (principal_key == NULL) + { + ereport(ERROR, + errmsg("principal key not configured"), + errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); + } + + /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ + rel_key_data->type = entry_type; + rel_key_data->start_lsn = InvalidXLogRecPtr; + + if (!RAND_bytes(rel_key_data->key, INTERNAL_KEY_LEN)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate WAL encryption key: %s", + ERR_error_string(ERR_get_error(), NULL))); + if (!RAND_bytes(rel_key_data->base_iv, INTERNAL_KEY_IV_LEN)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate IV for WAL encryption key: %s", + ERR_error_string(ERR_get_error(), NULL))); + + pg_tde_write_wal_key_file_entry(newrlocator, rel_key_data, principal_key); + +#ifdef FRONTEND + free(principal_key); +#endif + LWLockRelease(tde_lwlock_enc_keys()); +} + +/* + * Returns last (the most recent) key for a given relation + */ +WALKeyCacheRec * +pg_tde_get_last_wal_key(void) +{ + return tde_wal_key_last_rec; +} + +WALKeyCacheRec * +pg_tde_get_wal_cache_keys(void) +{ + return tde_wal_key_cache; +} + +WalEncryptionKey * +pg_tde_read_last_wal_key(void) +{ + RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); + char db_map_path[MAXPGPATH]; + off_t read_pos = 0; + LWLock *lock_pk = tde_lwlock_enc_keys(); + TDEPrincipalKey *principal_key; + int fd; + int file_idx; + WalKeyFileEntry entry; + WalEncryptionKey *rel_key_data; + off_t fsize; + + LWLockAcquire(lock_pk, LW_EXCLUSIVE); + principal_key = GetPrincipalKey(rlocator.dbOid, LW_EXCLUSIVE); + if (principal_key == NULL) + { + LWLockRelease(lock_pk); + elog(DEBUG1, "init WAL encryption: no principal key"); + return NULL; + } + pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); + + fd = pg_tde_open_wal_key_file_read(db_map_path, false, &read_pos); + fsize = lseek(fd, 0, SEEK_END); + /* No keys */ + if (fsize == sizeof(WalKeyFileHeader)) + { +#ifdef FRONTEND + pfree(principal_key); +#endif + LWLockRelease(lock_pk); + CloseTransientFile(fd); + return NULL; + } + + file_idx = ((fsize - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; + pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry, rlocator.dbOid); + + rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); +#ifdef FRONTEND + pfree(principal_key); +#endif + LWLockRelease(lock_pk); + CloseTransientFile(fd); + + return rel_key_data; +} + +/* Fetches WAL keys from disk and adds them to the WAL cache */ +WALKeyCacheRec * +pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) +{ + RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); + char db_map_path[MAXPGPATH]; + off_t read_pos = 0; + LWLock *lock_pk = tde_lwlock_enc_keys(); + TDEPrincipalKey *principal_key; + int fd; + int keys_count; + WALKeyCacheRec *return_wal_rec = NULL; + + LWLockAcquire(lock_pk, LW_SHARED); + principal_key = GetPrincipalKey(rlocator.dbOid, LW_SHARED); + if (principal_key == NULL) + { + LWLockRelease(lock_pk); + elog(DEBUG1, "fetch WAL keys: no principal key"); + return NULL; + } + + pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); + + fd = pg_tde_open_wal_key_file_read(db_map_path, false, &read_pos); + + keys_count = (lseek(fd, 0, SEEK_END) - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry); + + /* + * If there is no keys, return a fake one (with the range 0-infinity) so + * the reader won't try to check the disk all the time. This for the + * walsender in case if WAL is unencrypted and never was. + */ + if (keys_count == 0) + { + WALKeyCacheRec *wal_rec; + WalEncryptionKey stub_key = { + .start_lsn = InvalidXLogRecPtr, + }; + + wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); + +#ifdef FRONTEND + /* The backend frees it after copying to the cache. */ + pfree(principal_key); +#endif + LWLockRelease(lock_pk); + CloseTransientFile(fd); + return wal_rec; + } + + for (int file_idx = 0; file_idx < keys_count; file_idx++) + { + WalKeyFileEntry entry; + + pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry, rlocator.dbOid); + + /* + * Skip new (just created but not updated by write) and invalid keys + */ + if (entry.enc_key.start_lsn != InvalidXLogRecPtr && + (entry.enc_key.type == TDE_KEY_TYPE_WAL_UNENCRYPTED || + entry.enc_key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) && + entry.enc_key.start_lsn >= start_lsn) + { + WalEncryptionKey *rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); + WALKeyCacheRec *wal_rec; + + wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data, entry.enc_key.start_lsn); + + pfree(rel_key_data); + + if (!return_wal_rec) + return_wal_rec = wal_rec; + } + } +#ifdef FRONTEND + pfree(principal_key); +#endif + LWLockRelease(lock_pk); + CloseTransientFile(fd); + + return return_wal_rec; +} + +static WALKeyCacheRec * +pg_tde_add_wal_key_to_cache(WalEncryptionKey *key, XLogRecPtr start_lsn) +{ + WALKeyCacheRec *wal_rec; +#ifndef FRONTEND + MemoryContext oldCtx; + + oldCtx = MemoryContextSwitchTo(TopMemoryContext); +#endif + wal_rec = palloc0_object(WALKeyCacheRec); +#ifndef FRONTEND + MemoryContextSwitchTo(oldCtx); +#endif + + wal_rec->start_lsn = start_lsn; + wal_rec->end_lsn = MaxXLogRecPtr; + wal_rec->key = *key; + wal_rec->crypt_ctx = NULL; + if (!tde_wal_key_last_rec) + { + tde_wal_key_last_rec = wal_rec; + tde_wal_key_cache = tde_wal_key_last_rec; + } + else + { + tde_wal_key_last_rec->next = wal_rec; + tde_wal_key_last_rec->end_lsn = wal_rec->start_lsn; + tde_wal_key_last_rec = wal_rec; + } + + return wal_rec; +} + +static int +pg_tde_open_wal_key_file_basic(const char *filename, + int flags, + bool ignore_missing) +{ + int fd; + + fd = OpenTransientFile(filename, flags); + if (fd < 0 && !(errno == ENOENT && ignore_missing == true)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not open WAL key file \"%s\": %m", filename)); + } + + return fd; +} + +static int +pg_tde_open_wal_key_file_read(const char *filename, + bool ignore_missing, + off_t *curr_pos) +{ + int fd; + WalKeyFileHeader fheader; + off_t bytes_read = 0; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || + LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + + fd = pg_tde_open_wal_key_file_basic(filename, O_RDONLY | PG_BINARY, ignore_missing); + if (ignore_missing && fd < 0) + return fd; + + pg_tde_wal_key_file_header_read(filename, fd, &fheader, &bytes_read); + *curr_pos = bytes_read; + + return fd; +} + +static int +pg_tde_open_wal_key_file_write(const char *filename, + const TDESignedPrincipalKeyInfo *signed_key_info, + bool truncate, + off_t *curr_pos) +{ + int fd; + WalKeyFileHeader fheader; + off_t bytes_read = 0; + off_t bytes_written = 0; + int file_flags = O_RDWR | O_CREAT | PG_BINARY | (truncate ? O_TRUNC : 0); + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + + fd = pg_tde_open_wal_key_file_basic(filename, file_flags, false); + + pg_tde_wal_key_file_header_read(filename, fd, &fheader, &bytes_read); + + /* In case it's a new file, let's add the header now. */ + if (bytes_read == 0 && signed_key_info) + pg_tde_wal_key_file_header_write(filename, fd, signed_key_info, &bytes_written); + + *curr_pos = bytes_read + bytes_written; + return fd; +} + +static void +pg_tde_wal_key_file_header_read(const char *filename, + int fd, + WalKeyFileHeader *fheader, + off_t *bytes_read) +{ + Assert(fheader); + + *bytes_read = pg_pread(fd, fheader, sizeof(WalKeyFileHeader), 0); + + /* File is empty */ + if (*bytes_read == 0) + return; + + if (*bytes_read != sizeof(WalKeyFileHeader) + || fheader->file_version != PG_TDE_WAL_KEY_FILE_MAGIC) + { + ereport(FATAL, + errcode_for_file_access(), + errmsg("WAL key file \"%s\" is corrupted: %m", filename)); + } +} + +static int +pg_tde_wal_key_file_header_write(const char *filename, + int fd, + const TDESignedPrincipalKeyInfo *signed_key_info, + off_t *bytes_written) +{ + WalKeyFileHeader fheader; + + Assert(signed_key_info); + + fheader.file_version = PG_TDE_WAL_KEY_FILE_MAGIC; + fheader.signed_key_info = *signed_key_info; + *bytes_written = pg_pwrite(fd, &fheader, sizeof(WalKeyFileHeader), 0); + + if (*bytes_written != sizeof(WalKeyFileHeader)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write WAL key file \"%s\": %m", filename)); + } + + if (pg_fsync(fd) != 0) + { + ereport(data_sync_elevel(ERROR), + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", filename)); + } + + ereport(DEBUG2, errmsg("Wrote the header to %s", filename)); + + return fd; +} + +/* + * Returns true if an entry is found or false if we have reached the end of the + * file. + */ +static bool +pg_tde_read_one_wal_key_file_entry(int fd, + WalKeyFileEntry *entry, + off_t *offset) +{ + off_t bytes_read = 0; + + Assert(entry); + Assert(offset); + + bytes_read = pg_pread(fd, entry, sizeof(WalKeyFileEntry), *offset); + + /* We've reached the end of the file. */ + if (bytes_read != sizeof(WalKeyFileEntry)) + return false; + + *offset += bytes_read; + + return true; +} + +static void +pg_tde_read_one_wal_key_file_entry2(int fd, + int32 key_index, + WalKeyFileEntry *entry, + Oid databaseId) +{ + off_t read_pos; + + read_pos = sizeof(WalKeyFileHeader) + key_index * sizeof(WalKeyFileEntry); + if (pg_pread(fd, entry, sizeof(WalKeyFileEntry), read_pos) != sizeof(WalKeyFileEntry)) + { + char db_map_path[MAXPGPATH]; + + pg_tde_set_db_file_path(databaseId, db_map_path); + ereport(FATAL, + errcode_for_file_access(), + errmsg("could not find the required key at index %d in WAL key file \"%s\": %m", + key_index, db_map_path)); + } +} + +static void +pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, + const WalEncryptionKey *rel_key_data, + TDEPrincipalKey *principal_key) +{ + char db_map_path[MAXPGPATH]; + int fd; + off_t curr_pos = 0; + WalKeyFileEntry write_entry; + TDESignedPrincipalKeyInfo signed_key_Info; + + Assert(rlocator); + + pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); + + pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + + /* Open and validate file for basic correctness. */ + fd = pg_tde_open_wal_key_file_write(db_map_path, &signed_key_Info, false, &curr_pos); + + /* + * Read until we find an empty slot. Otherwise, read until end. This seems + * to be less frequent than vacuum. So let's keep this function here + * rather than overloading the vacuum process. + */ + while (1) + { + WalKeyFileEntry read_entry; + off_t prev_pos = curr_pos; + + if (!pg_tde_read_one_wal_key_file_entry(fd, &read_entry, &curr_pos)) + { + curr_pos = prev_pos; + break; + } + + if (read_entry.type == MAP_ENTRY_EMPTY) + { + curr_pos = prev_pos; + break; + } + } + + /* Initialize WAL key file entry and encrypt key */ + pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rlocator, rel_key_data); + + /* Write the given entry at curr_pos; i.e. the free entry. */ + pg_tde_write_one_wal_key_file_entry(fd, &write_entry, &curr_pos, db_map_path); + + CloseTransientFile(fd); +} + +static WalEncryptionKey * +pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) +{ + WalEncryptionKey *key = palloc_object(WalEncryptionKey); + + Assert(principal_key); + + *key = entry->enc_key; + + if (!AesGcmDecrypt(principal_key->keyData, + entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) entry, offsetof(TDEMapEntry, enc_key), + entry->enc_key.key, INTERNAL_KEY_LEN, + key->key, + entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) + ereport(ERROR, + errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); + + return key; +} + +static void +pg_tde_write_one_wal_key_file_entry(int fd, + const WalKeyFileEntry *entry, + off_t *offset, + const char *db_map_path) +{ + int bytes_written = 0; + + bytes_written = pg_pwrite(fd, entry, sizeof(WalKeyFileEntry), *offset); + + if (bytes_written != sizeof(WalKeyFileEntry)) + { + ereport(ERROR, + errcode_for_file_access(), + errmsg("could not write WAL key file \"%s\": %m", db_map_path)); + } + if (pg_fsync(fd) != 0) + { + ereport(data_sync_elevel(ERROR), + errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", db_map_path)); + } + + *offset += bytes_written; +} + +static void +pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, + const TDEPrincipalKey *principal_key, + const RelFileLocator *rlocator, + const WalEncryptionKey *rel_key_data) +{ + entry->spcOid = rlocator->spcOid; + entry->relNumber = rlocator->relNumber; + entry->type = rel_key_data->type; + entry->enc_key = *rel_key_data; + + if (!RAND_bytes(entry->entry_iv, MAP_ENTRY_IV_SIZE)) + ereport(ERROR, + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate iv for wal key file entry: %s", ERR_error_string(ERR_get_error(), NULL))); + + AesGcmEncrypt(principal_key->keyData, + entry->entry_iv, MAP_ENTRY_IV_SIZE, + (unsigned char *) entry, offsetof(WalKeyFileEntry, enc_key), + rel_key_data->key, INTERNAL_KEY_LEN, + entry->enc_key.key, + entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); +} + +#ifndef FRONTEND +/* + * Rotate keys and generates the WAL record for it. + */ +void +pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, + TDEPrincipalKey *new_principal_key, + bool write_xlog) +{ + TDESignedPrincipalKeyInfo new_signed_key_info; + off_t old_curr_pos, + new_curr_pos; + int old_fd, + new_fd; + char old_path[MAXPGPATH], + new_path[MAXPGPATH]; + + Assert(principal_key); + Assert(principal_key->keyInfo.databaseId == GLOBAL_DATA_TDE_OID); + + pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); + + pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); + snprintf(new_path, MAXPGPATH, "%s.r", old_path); + + old_fd = pg_tde_open_wal_key_file_read(old_path, false, &old_curr_pos); + new_fd = pg_tde_open_wal_key_file_write(new_path, &new_signed_key_info, true, &new_curr_pos); + + /* Read all entries until EOF */ + while (1) + { + WalEncryptionKey *key; + WalKeyFileEntry read_map_entry; + WalKeyFileEntry write_map_entry; + RelFileLocator rloc = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); + + if (!pg_tde_read_one_wal_key_file_entry(old_fd, &read_map_entry, &old_curr_pos)) + break; + + if (read_map_entry.type == MAP_ENTRY_EMPTY) + continue; + + /* Decrypt and re-encrypt key */ + key = pg_tde_decrypt_wal_key(principal_key, &read_map_entry); + pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, &rloc, key); + + pg_tde_write_one_wal_key_file_entry(new_fd, &write_map_entry, &new_curr_pos, new_path); + + pfree(key); + } + + CloseTransientFile(old_fd); + CloseTransientFile(new_fd); + + /* + * Do the final steps - replace the current WAL key file with the file + * with new data. + */ + durable_unlink(old_path, ERROR); + durable_rename(new_path, old_path, ERROR); + + /* + * We do WAL writes past the event ("the write behind logging") rather + * than before ("the write ahead") because we need logging here only for + * replication purposes. The rotation results in data written and fsynced + * to disk. Which in most cases would happen way before it's written to + * the WAL disk file. As WAL will be flushed at the end of the + * transaction, on its commit, hence after this function returns (there is + * also a bg writer, but the commit is what is guaranteed). And it makes + * sense to replicate the event only after its effect has been + * successfully applied to the source. + */ + if (write_xlog) + { + XLogPrincipalKeyRotate xlrec; + + xlrec.databaseId = principal_key->keyInfo.databaseId; + xlrec.keyringId = principal_key->keyInfo.keyringId; + memcpy(xlrec.keyName, new_principal_key->keyInfo.name, sizeof(new_principal_key->keyInfo.name)); + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(XLogPrincipalKeyRotate)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ROTATE_PRINCIPAL_KEY); + } +} +#endif + +#ifndef FRONTEND +void +pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) +{ + int fd; + off_t curr_pos; + char db_map_path[MAXPGPATH]; + + pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); + + LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); + + fd = pg_tde_open_wal_key_file_write(db_map_path, signed_key_info, false, &curr_pos); + CloseTransientFile(fd); + + LWLockRelease(tde_lwlock_enc_keys()); +} +#endif + +#ifndef FRONTEND +/* + * Creates the key file and saves the principal key information. + * + * If the file pre-exist, it truncates the file before adding principal key + * information. + * + * The caller must have an EXCLUSIVE LOCK on the files before calling this function. + * + * write_xlog: if true, the function will write an XLOG record about the + * principal key addition. We may want to skip this during server recovery/startup + * or in some other cases when WAL writes are not allowed. + */ +void +pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) +{ + int fd; + off_t curr_pos = 0; + char db_map_path[MAXPGPATH]; + TDESignedPrincipalKeyInfo signed_key_Info; + + pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); + + ereport(DEBUG2, errmsg("pg_tde_save_server_key")); + + pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); + + if (write_xlog) + { + XLogBeginInsert(); + XLogRegisterData((char *) &signed_key_Info, sizeof(TDESignedPrincipalKeyInfo)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY); + } + + fd = pg_tde_open_wal_key_file_write(db_map_path, &signed_key_Info, true, &curr_pos); + CloseTransientFile(fd); +} +#endif + +/* + * Get the principal key from the key file. The caller must hold + * a LW_SHARED or higher lock on files before calling this function. + */ +TDESignedPrincipalKeyInfo * +pg_tde_get_server_key_info(Oid dbOid) +{ + char db_map_path[MAXPGPATH]; + int fd; + WalKeyFileHeader fheader; + TDESignedPrincipalKeyInfo *signed_key_info = NULL; + off_t bytes_read = 0; + + pg_tde_set_db_file_path(dbOid, db_map_path); + + /* + * Ensuring that we always open the file in binary mode. The caller must + * specify other flags for reading, writing or creating the file. + */ + fd = pg_tde_open_wal_key_file_basic(db_map_path, O_RDONLY, true); + + /* The file does not exist. */ + if (fd < 0) + return NULL; + + pg_tde_wal_key_file_header_read(db_map_path, fd, &fheader, &bytes_read); + + CloseTransientFile(fd); + + /* + * It's not a new file. So we can copy the principal key info from the + * header + */ + if (bytes_read > 0) + { + signed_key_info = palloc_object(TDESignedPrincipalKeyInfo); + *signed_key_info = fheader.signed_key_info; + } + + return signed_key_info; +} + +int +pg_tde_count_wal_keys_in_file(Oid dbOid) +{ + char db_map_path[MAXPGPATH]; + File fd; + off_t curr_pos = 0; + WalKeyFileEntry entry; + int count = 0; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || + LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + + pg_tde_set_db_file_path(dbOid, db_map_path); + + fd = pg_tde_open_wal_key_file_read(db_map_path, true, &curr_pos); + if (fd < 0) + return count; + + while (pg_tde_read_one_wal_key_file_entry(fd, &entry, &curr_pos)) + { + if (entry.type != MAP_ENTRY_EMPTY) + count++; + } + + CloseTransientFile(fd); + + return count; +} + +#ifndef FRONTEND +void +pg_tde_delete_server_key(Oid dbOid) +{ + char path[MAXPGPATH]; + + Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); + Assert(pg_tde_count_wal_keys_in_file(dbOid) == 0); + + pg_tde_set_db_file_path(dbOid, path); + + XLogBeginInsert(); + XLogRegisterData((char *) &dbOid, sizeof(Oid)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_PRINCIPAL_KEY); + + /* Remove whole key map file */ + durable_unlink(path, ERROR); +} +#endif diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 1c51ebffd360d..7e1ecbfc383ae 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -13,7 +13,7 @@ #include "utils/guc.h" #include "utils/memutils.h" -#include "access/pg_tde_tdemap.h" +#include "access/pg_tde_xlog_keys.h" #include "access/pg_tde_xlog_smgr.h" #include "catalog/tde_global_space.h" #include "encryption/enc_tde.h" @@ -42,7 +42,7 @@ static const XLogSmgr tde_xlog_smgr = { static void *EncryptionCryptCtx = NULL; /* TODO: can be swapped out to the disk */ -static InternalKey EncryptionKey = +static WalEncryptionKey EncryptionKey = { .type = MAP_ENTRY_EMPTY, .start_lsn = InvalidXLogRecPtr, @@ -200,7 +200,7 @@ TDEXLogSmgrInit() void TDEXLogSmgrInitWrite(bool encrypt_xlog) { - InternalKey *key = pg_tde_read_last_wal_key(); + WalEncryptionKey *key = pg_tde_read_last_wal_key(); /* * Always generate a new key on starting PostgreSQL to protect against @@ -232,7 +232,7 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) void TDEXLogSmgrInitWriteReuseKey() { - InternalKey *key = pg_tde_read_last_wal_key(); + WalEncryptionKey *key = pg_tde_read_last_wal_key(); if (key) { @@ -252,7 +252,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno) { char iv_prefix[16]; - InternalKey *key = &EncryptionKey; + WalEncryptionKey *key = &EncryptionKey; char *enc_buff = EncryptionBuf; #ifndef FRONTEND diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 178461905a93c..49915f8f416a3 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -22,6 +22,7 @@ #include "utils/wait_event.h" #include "access/pg_tde_tdemap.h" +#include "access/pg_tde_xlog_keys.h" #include "access/pg_tde_xlog.h" #include "catalog/tde_global_space.h" #include "catalog/tde_principal_key.h" @@ -276,13 +277,19 @@ set_principal_key_with_keyring(const char *key_name, if (!already_has_key) { /* First key created for the database */ - pg_tde_save_principal_key(new_principal_key, true); + if (dbOid == GLOBAL_DATA_TDE_OID) + pg_tde_save_server_key(new_principal_key, true); + else + pg_tde_save_principal_key(new_principal_key, true); push_principal_key_to_cache(new_principal_key); } else { /* key rotation */ - pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, true); + if (dbOid == GLOBAL_DATA_TDE_OID) + pg_tde_perform_rotate_server_key(curr_principal_key, new_principal_key, true); + else + pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, true); clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); push_principal_key_to_cache(new_principal_key); @@ -343,7 +350,10 @@ xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec) memcpy(new_principal_key->keyData, keyInfo->data.data, keyInfo->data.len); - pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, false); + if (xlrec->databaseId == GLOBAL_DATA_TDE_OID) + pg_tde_perform_rotate_server_key(curr_principal_key, new_principal_key, false); + else + pg_tde_perform_rotate_key(curr_principal_key, new_principal_key, false); clear_principal_key_cache(curr_principal_key->keyInfo.databaseId); push_principal_key_to_cache(new_principal_key); @@ -807,12 +817,14 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(default_principal_key, principal_key)) { - if (pg_tde_count_encryption_keys(GLOBAL_DATA_TDE_OID) != 0) + if (pg_tde_count_wal_keys_in_file(GLOBAL_DATA_TDE_OID) != 0) ereport(ERROR, errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete default principal key"), errhint("There are WAL encryption keys.")); - dbs = lappend_oid(dbs, GLOBAL_DATA_TDE_OID); + + pg_tde_delete_server_key(GLOBAL_DATA_TDE_OID); + clear_principal_key_cache(GLOBAL_DATA_TDE_OID); } /* @@ -950,7 +962,11 @@ get_principal_key_from_keyring(Oid dbOid) Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - principalKeyInfo = pg_tde_get_principal_key_info(dbOid); + if (dbOid == GLOBAL_DATA_TDE_OID) + principalKeyInfo = pg_tde_get_server_key_info(dbOid); + else + principalKeyInfo = pg_tde_get_principal_key_info(dbOid); + if (principalKeyInfo == NULL) return NULL; @@ -1083,7 +1099,10 @@ GetPrincipalKey(Oid dbOid, LWLockMode lockMode) * current funcion may be invoked during server startup/recovery where * WAL writes forbidden. */ - pg_tde_save_principal_key(newPrincipalKey, false); + if (dbOid == GLOBAL_DATA_TDE_OID) + pg_tde_save_server_key(newPrincipalKey, false); + else + pg_tde_save_principal_key(newPrincipalKey, false); push_principal_key_to_cache(newPrincipalKey); @@ -1204,7 +1223,7 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); /* Check the server key that is used for WAL encryption */ - existing_principal_key = pg_tde_get_principal_key_info(GLOBAL_DATA_TDE_OID); + existing_principal_key = pg_tde_get_server_key_info(GLOBAL_DATA_TDE_OID); if (existing_principal_key != NULL && existing_principal_key->data.keyringId == modified_provider->keyring_id) { @@ -1309,7 +1328,10 @@ pg_tde_rotate_default_key_for_database(TDEPrincipalKey *oldKey, TDEPrincipalKey *newKey = *newKeyTemplate; newKey->keyInfo.databaseId = oldKey->keyInfo.databaseId; - pg_tde_perform_rotate_key(oldKey, newKey, true); + if (oldKey->keyInfo.databaseId == GLOBAL_DATA_TDE_OID) + pg_tde_perform_rotate_server_key(oldKey, newKey, true); + else + pg_tde_perform_rotate_key(oldKey, newKey, true); clear_principal_key_cache(oldKey->keyInfo.databaseId); push_principal_key_to_cache(newKey); diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 121720827befa..88a6249bff8fe 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -54,28 +54,6 @@ typedef struct XLogRelKey RelFileLocator rlocator; } XLogRelKey; -/* - * TODO: For now it's a simple linked list which is no good. So consider having - * dedicated WAL keys cache inside some proper data structure. - */ -typedef struct WALKeyCacheRec -{ - XLogRecPtr start_lsn; - XLogRecPtr end_lsn; - - InternalKey key; - void *crypt_ctx; - - struct WALKeyCacheRec *next; -} WALKeyCacheRec; - -extern InternalKey *pg_tde_read_last_wal_key(void); -extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); -extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); -extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); -extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); -extern void pg_tde_create_wal_key(InternalKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type); - #define PG_TDE_MAP_FILENAME "%d_keys" static inline void diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h new file mode 100644 index 0000000000000..1d9b85fa2a8d4 --- /dev/null +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -0,0 +1,65 @@ +#ifndef PG_TDE_XLOG_KEYS_H +#define PG_TDE_XLOG_KEYS_H + +#include "access/xlog_internal.h" +#include "storage/relfilelocator.h" + +#include "access/pg_tde_tdemap.h" +#include "catalog/tde_principal_key.h" +#include "common/pg_tde_utils.h" + +typedef struct WalEncryptionKey +{ + uint8 key[INTERNAL_KEY_LEN]; + uint8 base_iv[INTERNAL_KEY_IV_LEN]; + uint32 type; + + XLogRecPtr start_lsn; +} WalEncryptionKey; + +typedef struct WalKeyFileEntry +{ + Oid spcOid; + RelFileNumber relNumber; + uint32 type; + WalEncryptionKey enc_key; + /* IV and tag used when encrypting the key itself */ + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} WalKeyFileEntry; + +typedef struct WalKeyFileHeader +{ + int32 file_version; + TDESignedPrincipalKeyInfo signed_key_info; +} WalKeyFileHeader; + +/* + * TODO: For now it's a simple linked list which is no good. So consider having + * dedicated WAL keys cache inside some proper data structure. + */ +typedef struct WALKeyCacheRec +{ + XLogRecPtr start_lsn; + XLogRecPtr end_lsn; + + WalEncryptionKey key; + void *crypt_ctx; + + struct WALKeyCacheRec *next; +} WALKeyCacheRec; + +extern int pg_tde_count_wal_keys_in_file(Oid dbOid); +extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type); +extern void pg_tde_delete_server_key(Oid dbOid); +extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); +extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); +extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(Oid dbOid); +extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); +extern void pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); +extern WalEncryptionKey *pg_tde_read_last_wal_key(void); +extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); +extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); +extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); + +#endif /* PG_TDE_XLOG_KEYS_H */ From 436d2a6dbde0e90db2d1372d89390cda7e38ec0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 5 Aug 2025 22:54:03 +0200 Subject: [PATCH 556/796] PG-1813 Change file magic for wal keyfile This is to prevent this file from ever getting mixed up with a relation key file as these might no longer be of the same format. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 4187fa9768e7c..831e97bce05be 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -21,7 +21,7 @@ #include "pg_tde_fe.h" #endif -#define PG_TDE_WAL_KEY_FILE_MAGIC 0x03454454 /* version ID value = TDE 03 */ +#define PG_TDE_WAL_KEY_FILE_MAGIC 0x014B4557 /* version ID value = WEK 01 */ #define MaxXLogRecPtr (~(XLogRecPtr)0) From 583f8efe1143258e722fd98313d810e8f92b435c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 6 Aug 2025 11:34:20 +0200 Subject: [PATCH 557/796] PG-1813 Rename wal key file Instead of 1664_keys it's now called wal_encryption_keys. This lets us use a constant name for it instead of generating it from an Oid pretending it's a relation key file. Also remove some now unused Oid parameters to functions. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 93 ++++++++----------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 8 +- .../pg_tde/src/catalog/tde_principal_key.c | 8 +- .../src/include/access/pg_tde_xlog_keys.h | 8 +- 4 files changed, 46 insertions(+), 71 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 831e97bce05be..4535e7fc82df1 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -22,6 +22,7 @@ #endif #define PG_TDE_WAL_KEY_FILE_MAGIC 0x014B4557 /* version ID value = WEK 01 */ +#define PG_TDE_WAL_KEY_FILE_NAME "wal_encryption_keys" #define MaxXLogRecPtr (~(XLogRecPtr)0) @@ -41,12 +42,19 @@ static int pg_tde_wal_key_file_header_write(const char *filename, int fd, const static void pg_tde_write_one_wal_key_file_entry(int fd, const WalKeyFileEntry *entry, off_t *offset, const char *db_map_path); static void pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key); -/* - * It's called by seg_write inside crit section so no pallocs, hence - * needs keyfile_path - */ +static char * +get_wal_key_file_path(void) +{ + static char wal_key_file_path[MAXPGPATH] = {0}; + + if (strlen(wal_key_file_path) == 0) + join_path_components(wal_key_file_path, pg_tde_get_data_dir(), PG_TDE_WAL_KEY_FILE_NAME); + + return wal_key_file_path; +} + void -pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) +pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn) { LWLock *lock_pk = tde_lwlock_enc_keys(); int fd; @@ -56,7 +64,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path) LWLockAcquire(lock_pk, LW_EXCLUSIVE); - fd = pg_tde_open_wal_key_file_write(keyfile_path, NULL, false, &read_pos); + fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), NULL, false, &read_pos); last_key_idx = ((lseek(fd, 0, SEEK_END) - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; write_pos = sizeof(WalKeyFileHeader) + @@ -179,7 +187,6 @@ WalEncryptionKey * pg_tde_read_last_wal_key(void) { RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH]; off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; @@ -197,9 +204,8 @@ pg_tde_read_last_wal_key(void) elog(DEBUG1, "init WAL encryption: no principal key"); return NULL; } - pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - fd = pg_tde_open_wal_key_file_read(db_map_path, false, &read_pos); + fd = pg_tde_open_wal_key_file_read(get_wal_key_file_path(), false, &read_pos); fsize = lseek(fd, 0, SEEK_END); /* No keys */ if (fsize == sizeof(WalKeyFileHeader)) @@ -230,7 +236,6 @@ WALKeyCacheRec * pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) { RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); - char db_map_path[MAXPGPATH]; off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; @@ -247,9 +252,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) return NULL; } - pg_tde_set_db_file_path(rlocator.dbOid, db_map_path); - - fd = pg_tde_open_wal_key_file_read(db_map_path, false, &read_pos); + fd = pg_tde_open_wal_key_file_read(get_wal_key_file_path(), false, &read_pos); keys_count = (lseek(fd, 0, SEEK_END) - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry); @@ -501,13 +504,10 @@ pg_tde_read_one_wal_key_file_entry2(int fd, read_pos = sizeof(WalKeyFileHeader) + key_index * sizeof(WalKeyFileEntry); if (pg_pread(fd, entry, sizeof(WalKeyFileEntry), read_pos) != sizeof(WalKeyFileEntry)) { - char db_map_path[MAXPGPATH]; - - pg_tde_set_db_file_path(databaseId, db_map_path); ereport(FATAL, errcode_for_file_access(), errmsg("could not find the required key at index %d in WAL key file \"%s\": %m", - key_index, db_map_path)); + key_index, get_wal_key_file_path())); } } @@ -516,7 +516,6 @@ pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key) { - char db_map_path[MAXPGPATH]; int fd; off_t curr_pos = 0; WalKeyFileEntry write_entry; @@ -524,12 +523,10 @@ pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, Assert(rlocator); - pg_tde_set_db_file_path(rlocator->dbOid, db_map_path); - pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); /* Open and validate file for basic correctness. */ - fd = pg_tde_open_wal_key_file_write(db_map_path, &signed_key_Info, false, &curr_pos); + fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_Info, false, &curr_pos); /* * Read until we find an empty slot. Otherwise, read until end. This seems @@ -558,7 +555,7 @@ pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rlocator, rel_key_data); /* Write the given entry at curr_pos; i.e. the free entry. */ - pg_tde_write_one_wal_key_file_entry(fd, &write_entry, &curr_pos, db_map_path); + pg_tde_write_one_wal_key_file_entry(fd, &write_entry, &curr_pos, get_wal_key_file_path()); CloseTransientFile(fd); } @@ -648,19 +645,17 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, new_curr_pos; int old_fd, new_fd; - char old_path[MAXPGPATH], - new_path[MAXPGPATH]; + char tmp_path[MAXPGPATH]; Assert(principal_key); Assert(principal_key->keyInfo.databaseId == GLOBAL_DATA_TDE_OID); pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); - pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); - snprintf(new_path, MAXPGPATH, "%s.r", old_path); + snprintf(tmp_path, MAXPGPATH, "%s.r", get_wal_key_file_path()); - old_fd = pg_tde_open_wal_key_file_read(old_path, false, &old_curr_pos); - new_fd = pg_tde_open_wal_key_file_write(new_path, &new_signed_key_info, true, &new_curr_pos); + old_fd = pg_tde_open_wal_key_file_read(get_wal_key_file_path(), false, &old_curr_pos); + new_fd = pg_tde_open_wal_key_file_write(tmp_path, &new_signed_key_info, true, &new_curr_pos); /* Read all entries until EOF */ while (1) @@ -680,7 +675,7 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, key = pg_tde_decrypt_wal_key(principal_key, &read_map_entry); pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, &rloc, key); - pg_tde_write_one_wal_key_file_entry(new_fd, &write_map_entry, &new_curr_pos, new_path); + pg_tde_write_one_wal_key_file_entry(new_fd, &write_map_entry, &new_curr_pos, tmp_path); pfree(key); } @@ -692,8 +687,8 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, * Do the final steps - replace the current WAL key file with the file * with new data. */ - durable_unlink(old_path, ERROR); - durable_rename(new_path, old_path, ERROR); + durable_unlink(get_wal_key_file_path(), ERROR); + durable_rename(tmp_path, get_wal_key_file_path(), ERROR); /* * We do WAL writes past the event ("the write behind logging") rather @@ -727,13 +722,10 @@ pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) { int fd; off_t curr_pos; - char db_map_path[MAXPGPATH]; - - pg_tde_set_db_file_path(signed_key_info->data.databaseId, db_map_path); LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - fd = pg_tde_open_wal_key_file_write(db_map_path, signed_key_info, false, &curr_pos); + fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), signed_key_info, false, &curr_pos); CloseTransientFile(fd); LWLockRelease(tde_lwlock_enc_keys()); @@ -758,11 +750,8 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) { int fd; off_t curr_pos = 0; - char db_map_path[MAXPGPATH]; TDESignedPrincipalKeyInfo signed_key_Info; - pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); - ereport(DEBUG2, errmsg("pg_tde_save_server_key")); pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); @@ -774,7 +763,7 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY); } - fd = pg_tde_open_wal_key_file_write(db_map_path, &signed_key_Info, true, &curr_pos); + fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_Info, true, &curr_pos); CloseTransientFile(fd); } #endif @@ -784,27 +773,24 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) * a LW_SHARED or higher lock on files before calling this function. */ TDESignedPrincipalKeyInfo * -pg_tde_get_server_key_info(Oid dbOid) +pg_tde_get_server_key_info(void) { - char db_map_path[MAXPGPATH]; int fd; WalKeyFileHeader fheader; TDESignedPrincipalKeyInfo *signed_key_info = NULL; off_t bytes_read = 0; - pg_tde_set_db_file_path(dbOid, db_map_path); - /* * Ensuring that we always open the file in binary mode. The caller must * specify other flags for reading, writing or creating the file. */ - fd = pg_tde_open_wal_key_file_basic(db_map_path, O_RDONLY, true); + fd = pg_tde_open_wal_key_file_basic(get_wal_key_file_path(), O_RDONLY, true); /* The file does not exist. */ if (fd < 0) return NULL; - pg_tde_wal_key_file_header_read(db_map_path, fd, &fheader, &bytes_read); + pg_tde_wal_key_file_header_read(get_wal_key_file_path(), fd, &fheader, &bytes_read); CloseTransientFile(fd); @@ -822,9 +808,8 @@ pg_tde_get_server_key_info(Oid dbOid) } int -pg_tde_count_wal_keys_in_file(Oid dbOid) +pg_tde_count_wal_keys_in_file(void) { - char db_map_path[MAXPGPATH]; File fd; off_t curr_pos = 0; WalKeyFileEntry entry; @@ -833,9 +818,7 @@ pg_tde_count_wal_keys_in_file(Oid dbOid) Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - pg_tde_set_db_file_path(dbOid, db_map_path); - - fd = pg_tde_open_wal_key_file_read(db_map_path, true, &curr_pos); + fd = pg_tde_open_wal_key_file_read(get_wal_key_file_path(), true, &curr_pos); if (fd < 0) return count; @@ -852,20 +835,18 @@ pg_tde_count_wal_keys_in_file(Oid dbOid) #ifndef FRONTEND void -pg_tde_delete_server_key(Oid dbOid) +pg_tde_delete_server_key(void) { - char path[MAXPGPATH]; + Oid dbOid = GLOBAL_DATA_TDE_OID; Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - Assert(pg_tde_count_wal_keys_in_file(dbOid) == 0); - - pg_tde_set_db_file_path(dbOid, path); + Assert(pg_tde_count_wal_keys_in_file() == 0); XLogBeginInsert(); XLogRegisterData((char *) &dbOid, sizeof(Oid)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_PRINCIPAL_KEY); /* Remove whole key map file */ - durable_unlink(path, ERROR); + durable_unlink(get_wal_key_file_path(), ERROR); } #endif diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 7e1ecbfc383ae..7227bf0a8ba79 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -65,7 +65,6 @@ static WalEncryptionKey EncryptionKey = typedef struct EncryptionStateData { - char db_map_path[MAXPGPATH]; pg_atomic_uint64 enc_key_lsn; /* to sync with readers */ } EncryptionStateData; @@ -167,7 +166,6 @@ TDEXLogShmemInit(void) typedef struct EncryptionStateData { - char db_map_path[MAXPGPATH]; XLogRecPtr enc_key_lsn; /* to sync with reader */ } EncryptionStateData; @@ -225,8 +223,6 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) if (key) pfree(key); - - pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); } void @@ -240,8 +236,6 @@ TDEXLogSmgrInitWriteReuseKey() TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn); pfree(key); } - - pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path); } /* @@ -291,7 +285,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, XLogSegNoOffsetToRecPtr(segno, offset, segSize, lsn); - pg_tde_wal_last_key_set_lsn(lsn, EncryptionState->db_map_path); + pg_tde_wal_last_key_set_lsn(lsn); EncryptionKey.start_lsn = lsn; TDEXLogSetEncKeyLsn(lsn); } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 49915f8f416a3..c7f299a90ba5c 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -817,13 +817,13 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(default_principal_key, principal_key)) { - if (pg_tde_count_wal_keys_in_file(GLOBAL_DATA_TDE_OID) != 0) + if (pg_tde_count_wal_keys_in_file() != 0) ereport(ERROR, errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete default principal key"), errhint("There are WAL encryption keys.")); - pg_tde_delete_server_key(GLOBAL_DATA_TDE_OID); + pg_tde_delete_server_key(); clear_principal_key_cache(GLOBAL_DATA_TDE_OID); } @@ -963,7 +963,7 @@ get_principal_key_from_keyring(Oid dbOid) Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); if (dbOid == GLOBAL_DATA_TDE_OID) - principalKeyInfo = pg_tde_get_server_key_info(dbOid); + principalKeyInfo = pg_tde_get_server_key_info(); else principalKeyInfo = pg_tde_get_principal_key_info(dbOid); @@ -1223,7 +1223,7 @@ pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider) LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); /* Check the server key that is used for WAL encryption */ - existing_principal_key = pg_tde_get_server_key_info(GLOBAL_DATA_TDE_OID); + existing_principal_key = pg_tde_get_server_key_info(); if (existing_principal_key != NULL && existing_principal_key->data.keyringId == modified_provider->keyring_id) { diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 1d9b85fa2a8d4..5a64aaf6ebe07 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -49,17 +49,17 @@ typedef struct WALKeyCacheRec struct WALKeyCacheRec *next; } WALKeyCacheRec; -extern int pg_tde_count_wal_keys_in_file(Oid dbOid); +extern int pg_tde_count_wal_keys_in_file(void); extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type); -extern void pg_tde_delete_server_key(Oid dbOid); +extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); -extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(Oid dbOid); +extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(void); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); extern void pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); extern WalEncryptionKey *pg_tde_read_last_wal_key(void); extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); -extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn, const char *keyfile_path); +extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn); #endif /* PG_TDE_XLOG_KEYS_H */ From ac0c58d3d65e51dce714f16000d1b17947ff06b4 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 12:28:40 +0200 Subject: [PATCH 558/796] Use correct struct when calculating offset It happened to work by coincidence since the two structs had the same shape but is a bug waiting to happen. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 4535e7fc82df1..384bcadb99f9a 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -571,7 +571,7 @@ pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) if (!AesGcmDecrypt(principal_key->keyData, entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) entry, offsetof(TDEMapEntry, enc_key), + (unsigned char *) entry, offsetof(WalKeyFileEntry, enc_key), entry->enc_key.key, INTERNAL_KEY_LEN, key->key, entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) From 91997fb26c15fb65d39ddc331613f8b52909f75c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 12:28:35 +0200 Subject: [PATCH 559/796] Remove RelFileLocator from the WAL key file Since the RelFileLocator has never actually been used for WAL keys we can remove all traces of it from the new file and from the code. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 40 +++++++------------ contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 6 +-- .../src/include/access/pg_tde_xlog_keys.h | 5 +-- .../src/include/catalog/tde_global_space.h | 12 ------ 4 files changed, 17 insertions(+), 46 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 384bcadb99f9a..b50f3d940becc 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -31,16 +31,16 @@ static WALKeyCacheRec *tde_wal_key_last_rec = NULL; static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key, XLogRecPtr start_lsn); static WalEncryptionKey *pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); -static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data); +static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const WalEncryptionKey *rel_key_data); static int pg_tde_open_wal_key_file_basic(const char *filename, int flags, bool ignore_missing); static int pg_tde_open_wal_key_file_read(const char *filename, bool ignore_missing, off_t *curr_pos); static int pg_tde_open_wal_key_file_write(const char *filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); static bool pg_tde_read_one_wal_key_file_entry(int fd, WalKeyFileEntry *entry, off_t *offset); -static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, WalKeyFileEntry *entry, Oid databaseId); +static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, WalKeyFileEntry *entry); static void pg_tde_wal_key_file_header_read(const char *filename, int fd, WalKeyFileHeader *fheader, off_t *bytes_read); static int pg_tde_wal_key_file_header_write(const char *filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_write_one_wal_key_file_entry(int fd, const WalKeyFileEntry *entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key); +static void pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key); static char * get_wal_key_file_path(void) @@ -129,15 +129,13 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn) * with the actual lsn by the first WAL write. */ void -pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, - const RelFileLocator *newrlocator, - TDEMapEntryType entry_type) +pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type) { TDEPrincipalKey *principal_key; LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - principal_key = GetPrincipalKey(newrlocator->dbOid, LW_EXCLUSIVE); + principal_key = GetPrincipalKey(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (principal_key == NULL) { ereport(ERROR, @@ -160,7 +158,7 @@ pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, errmsg("could not generate IV for WAL encryption key: %s", ERR_error_string(ERR_get_error(), NULL))); - pg_tde_write_wal_key_file_entry(newrlocator, rel_key_data, principal_key); + pg_tde_write_wal_key_file_entry(rel_key_data, principal_key); #ifdef FRONTEND free(principal_key); @@ -186,7 +184,6 @@ pg_tde_get_wal_cache_keys(void) WalEncryptionKey * pg_tde_read_last_wal_key(void) { - RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; @@ -197,7 +194,7 @@ pg_tde_read_last_wal_key(void) off_t fsize; LWLockAcquire(lock_pk, LW_EXCLUSIVE); - principal_key = GetPrincipalKey(rlocator.dbOid, LW_EXCLUSIVE); + principal_key = GetPrincipalKey(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (principal_key == NULL) { LWLockRelease(lock_pk); @@ -219,7 +216,7 @@ pg_tde_read_last_wal_key(void) } file_idx = ((fsize - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; - pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry, rlocator.dbOid); + pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry); rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); #ifdef FRONTEND @@ -235,7 +232,6 @@ pg_tde_read_last_wal_key(void) WALKeyCacheRec * pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) { - RelFileLocator rlocator = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); TDEPrincipalKey *principal_key; @@ -244,7 +240,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) WALKeyCacheRec *return_wal_rec = NULL; LWLockAcquire(lock_pk, LW_SHARED); - principal_key = GetPrincipalKey(rlocator.dbOid, LW_SHARED); + principal_key = GetPrincipalKey(GLOBAL_DATA_TDE_OID, LW_SHARED); if (principal_key == NULL) { LWLockRelease(lock_pk); @@ -283,7 +279,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) { WalKeyFileEntry entry; - pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry, rlocator.dbOid); + pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry); /* * Skip new (just created but not updated by write) and invalid keys @@ -496,8 +492,7 @@ pg_tde_read_one_wal_key_file_entry(int fd, static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, - WalKeyFileEntry *entry, - Oid databaseId) + WalKeyFileEntry *entry) { off_t read_pos; @@ -512,8 +507,7 @@ pg_tde_read_one_wal_key_file_entry2(int fd, } static void -pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, - const WalEncryptionKey *rel_key_data, +pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key) { int fd; @@ -521,8 +515,6 @@ pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, WalKeyFileEntry write_entry; TDESignedPrincipalKeyInfo signed_key_Info; - Assert(rlocator); - pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); /* Open and validate file for basic correctness. */ @@ -552,7 +544,7 @@ pg_tde_write_wal_key_file_entry(const RelFileLocator *rlocator, } /* Initialize WAL key file entry and encrypt key */ - pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rlocator, rel_key_data); + pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rel_key_data); /* Write the given entry at curr_pos; i.e. the free entry. */ pg_tde_write_one_wal_key_file_entry(fd, &write_entry, &curr_pos, get_wal_key_file_path()); @@ -610,11 +602,8 @@ pg_tde_write_one_wal_key_file_entry(int fd, static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, - const RelFileLocator *rlocator, const WalEncryptionKey *rel_key_data) { - entry->spcOid = rlocator->spcOid; - entry->relNumber = rlocator->relNumber; entry->type = rel_key_data->type; entry->enc_key = *rel_key_data; @@ -663,7 +652,6 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, WalEncryptionKey *key; WalKeyFileEntry read_map_entry; WalKeyFileEntry write_map_entry; - RelFileLocator rloc = GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID); if (!pg_tde_read_one_wal_key_file_entry(old_fd, &read_map_entry, &old_curr_pos)) break; @@ -673,7 +661,7 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, /* Decrypt and re-encrypt key */ key = pg_tde_decrypt_wal_key(principal_key, &read_map_entry); - pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, &rloc, key); + pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, key); pg_tde_write_one_wal_key_file_entry(new_fd, &write_map_entry, &new_curr_pos, tmp_path); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 7227bf0a8ba79..7dd9e7b821872 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -207,13 +207,11 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) */ if (encrypt_xlog) { - pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), - TDE_KEY_TYPE_WAL_ENCRYPTED); + pg_tde_create_wal_key(&EncryptionKey, TDE_KEY_TYPE_WAL_ENCRYPTED); } else if (key && key->type == TDE_KEY_TYPE_WAL_ENCRYPTED) { - pg_tde_create_wal_key(&EncryptionKey, &GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID), - TDE_KEY_TYPE_WAL_UNENCRYPTED); + pg_tde_create_wal_key(&EncryptionKey, TDE_KEY_TYPE_WAL_UNENCRYPTED); } else if (key) { diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 5a64aaf6ebe07..1b5fccdb9272a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -2,7 +2,6 @@ #define PG_TDE_XLOG_KEYS_H #include "access/xlog_internal.h" -#include "storage/relfilelocator.h" #include "access/pg_tde_tdemap.h" #include "catalog/tde_principal_key.h" @@ -19,8 +18,6 @@ typedef struct WalEncryptionKey typedef struct WalKeyFileEntry { - Oid spcOid; - RelFileNumber relNumber; uint32 type; WalEncryptionKey enc_key; /* IV and tag used when encrypting the key itself */ @@ -50,7 +47,7 @@ typedef struct WALKeyCacheRec } WALKeyCacheRec; extern int pg_tde_count_wal_keys_in_file(void); -extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, const RelFileLocator *newrlocator, TDEMapEntryType entry_type); +extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type); extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); diff --git a/contrib/pg_tde/src/include/catalog/tde_global_space.h b/contrib/pg_tde/src/include/catalog/tde_global_space.h index faa2098a27f0c..3bbec11f41a00 100644 --- a/contrib/pg_tde/src/include/catalog/tde_global_space.h +++ b/contrib/pg_tde/src/include/catalog/tde_global_space.h @@ -14,16 +14,4 @@ #define GLOBAL_DATA_TDE_OID GLOBALTABLESPACE_OID #define DEFAULT_DATA_TDE_OID DEFAULTTABLESPACE_OID -/* - * This oid can be anything since the database oid is gauranteed to not be a - * real database. - */ -#define XLOG_TDE_OID 1 - -#define GLOBAL_SPACE_RLOCATOR(_obj_oid) (RelFileLocator) { \ - GLOBALTABLESPACE_OID, \ - GLOBAL_DATA_TDE_OID, \ - _obj_oid \ -} - #endif /* TDE_GLOBAL_CATALOG_H */ From 8c40309aa32deb83d72fb3176fa73c25071fcf7e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 13:39:31 +0200 Subject: [PATCH 560/796] Change the file name of the wal keys to match better The other keys are stored in _keys so wal_keys fits better into that pattern than the more redundant wal_encryption_keys where "encryption" does not add any information but just makes the path longer. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index b50f3d940becc..b32e8161714eb 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -22,7 +22,7 @@ #endif #define PG_TDE_WAL_KEY_FILE_MAGIC 0x014B4557 /* version ID value = WEK 01 */ -#define PG_TDE_WAL_KEY_FILE_NAME "wal_encryption_keys" +#define PG_TDE_WAL_KEY_FILE_NAME "wal_keys" #define MaxXLogRecPtr (~(XLogRecPtr)0) From 8d7192cdbaa8dbe229ea168e60ed97b55684859c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 12:27:27 +0200 Subject: [PATCH 561/796] Move things out of header files after key file split Some definitions should be in the .c files rather than in the header files since they are just used in one file. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 23 +++++++++++++++ contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 15 ++++++++++ .../pg_tde/src/include/access/pg_tde_tdemap.h | 28 ------------------- .../pg_tde/src/include/access/pg_tde_xlog.h | 5 ++++ .../src/include/access/pg_tde_xlog_keys.h | 16 ----------- 5 files changed, 43 insertions(+), 44 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index fa8d01d54ad91..096d3a6fa2bb2 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -39,6 +39,7 @@ #endif #define PG_TDE_FILEMAGIC 0x03454454 /* version ID value = TDE 03 */ +#define PG_TDE_MAP_FILENAME "%d_keys" #define MAP_ENTRY_SIZE sizeof(TDEMapEntry) #define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) @@ -49,6 +50,19 @@ typedef struct TDEFileHeader TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; +/* We do not need the dbOid since the entries are stored in a file per db */ +typedef struct TDEMapEntry +{ + Oid spcOid; + RelFileNumber relNumber; + uint32 type; + InternalKey enc_key; + /* IV and tag used when encrypting the key itself */ + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} TDEMapEntry; + +static void pg_tde_set_db_file_path(Oid dbOid, char *path); static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); @@ -360,6 +374,15 @@ pg_tde_delete_principal_key(Oid dbOid) #endif /* !FRONTEND */ +static void +pg_tde_set_db_file_path(Oid dbOid, char *path) +{ + char *fname = psprintf(PG_TDE_MAP_FILENAME, dbOid); + + join_path_components(path, pg_tde_get_data_dir(), fname); + pfree(fname); +} + void pg_tde_sign_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key) { diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index b32e8161714eb..25c8473a20198 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -26,6 +26,21 @@ #define MaxXLogRecPtr (~(XLogRecPtr)0) +typedef struct WalKeyFileHeader +{ + int32 file_version; + TDESignedPrincipalKeyInfo signed_key_info; +} WalKeyFileHeader; + +typedef struct WalKeyFileEntry +{ + uint32 type; + WalEncryptionKey enc_key; + /* IV and tag used when encrypting the key itself */ + unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} WalKeyFileEntry; + static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 88a6249bff8fe..ed39c5af846fe 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -37,34 +37,6 @@ typedef struct unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; } TDESignedPrincipalKeyInfo; -/* We do not need the dbOid since the entries are stored in a file per db */ -typedef struct TDEMapEntry -{ - Oid spcOid; - RelFileNumber relNumber; - uint32 type; - InternalKey enc_key; - /* IV and tag used when encrypting the key itself */ - unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; - unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; -} TDEMapEntry; - -typedef struct XLogRelKey -{ - RelFileLocator rlocator; -} XLogRelKey; - -#define PG_TDE_MAP_FILENAME "%d_keys" - -static inline void -pg_tde_set_db_file_path(Oid dbOid, char *path) -{ - char *fname = psprintf(PG_TDE_MAP_FILENAME, dbOid); - - join_path_components(path, pg_tde_get_data_dir(), fname); - pfree(fname); -} - extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key); extern bool pg_tde_has_smgr_key(RelFileLocator rel); extern InternalKey *pg_tde_get_smgr_key(RelFileLocator rel); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 8c93a7a1807fc..03f8421ff9609 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -19,6 +19,11 @@ /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 +typedef struct XLogRelKey +{ + RelFileLocator rlocator; +} XLogRelKey; + extern void RegisterTdeRmgr(void); #endif /* !FRONTEND */ diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 1b5fccdb9272a..e1c6efc665b97 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -5,7 +5,6 @@ #include "access/pg_tde_tdemap.h" #include "catalog/tde_principal_key.h" -#include "common/pg_tde_utils.h" typedef struct WalEncryptionKey { @@ -16,21 +15,6 @@ typedef struct WalEncryptionKey XLogRecPtr start_lsn; } WalEncryptionKey; -typedef struct WalKeyFileEntry -{ - uint32 type; - WalEncryptionKey enc_key; - /* IV and tag used when encrypting the key itself */ - unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; - unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; -} WalKeyFileEntry; - -typedef struct WalKeyFileHeader -{ - int32 file_version; - TDESignedPrincipalKeyInfo signed_key_info; -} WalKeyFileHeader; - /* * TODO: For now it's a simple linked list which is no good. So consider having * dedicated WAL keys cache inside some proper data structure. From 87c55e6690a05b1715fadd4cdbb63ce3dee06b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 7 Aug 2025 14:13:12 +0200 Subject: [PATCH 562/796] Remove some unused fields from InternalKey Add them as unused fields in the TDEMapEntry structure however, so we do not affect existing key files. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 32 ++++++++++++++++--- contrib/pg_tde/src/encryption/enc_tde.c | 5 +-- .../pg_tde/src/include/access/pg_tde_tdemap.h | 3 -- .../pg_tde/src/include/encryption/enc_tde.h | 2 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 4 +-- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 096d3a6fa2bb2..584dd9f1e448a 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -56,7 +56,25 @@ typedef struct TDEMapEntry Oid spcOid; RelFileNumber relNumber; uint32 type; - InternalKey enc_key; + + /* + * This anonymous struct is here to ensure the same alignment as before + * the unused fields were removed from InternalKey. + */ + struct + { + InternalKey enc_key; + + /* + * These fields were added here to keep the file format the same after + * some fields were removed from InternalKey. Feel free to use them + * for something, but beware that existing files may contain + * unexpected values here. + */ + uint32 _unused1; /* Will be 1 in existing files entries. */ + uint64 _unused2; /* Will be 0 in existing files entries. */ + }; + /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; @@ -210,9 +228,6 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) { TDEMapEntry empty_map_entry = { .type = MAP_ENTRY_EMPTY, - .enc_key = { - .type = MAP_ENTRY_EMPTY, - }, }; pg_tde_write_one_map_entry(map_fd, &empty_map_entry, &prev_pos, db_map_path); @@ -407,9 +422,16 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ { map_entry->spcOid = rlocator->spcOid; map_entry->relNumber = rlocator->relNumber; - map_entry->type = rel_key_data->type; + map_entry->type = TDE_KEY_TYPE_SMGR; map_entry->enc_key = *rel_key_data; + /* + * We set these fields here so that existing file entries will be + * consistent and future use of these fields easier. + */ + map_entry->_unused1 = 1; + map_entry->_unused2 = 0; + if (!RAND_bytes(map_entry->entry_iv, MAP_ENTRY_IV_SIZE)) ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), diff --git a/contrib/pg_tde/src/encryption/enc_tde.c b/contrib/pg_tde/src/encryption/enc_tde.c index 4c783c68f8a69..bceaaf7cf8b52 100644 --- a/contrib/pg_tde/src/encryption/enc_tde.c +++ b/contrib/pg_tde/src/encryption/enc_tde.c @@ -27,11 +27,8 @@ iv_prefix_debug(const char *iv_prefix, char *out_hex) #endif void -pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type) +pg_tde_generate_internal_key(InternalKey *int_key) { - int_key->type = entry_type; - int_key->start_lsn = InvalidXLogRecPtr; - if (!RAND_bytes(int_key->key, INTERNAL_KEY_LEN)) ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index ed39c5af846fe..8c8781380d796 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -22,9 +22,6 @@ typedef struct InternalKey { uint8 key[INTERNAL_KEY_LEN]; uint8 base_iv[INTERNAL_KEY_IV_LEN]; - uint32 type; - - XLogRecPtr start_lsn; } InternalKey; #define MAP_ENTRY_IV_SIZE 16 diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index dfb6cbbb0dadb..7873606d61dab 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -7,7 +7,7 @@ #include "access/pg_tde_tdemap.h" -extern void pg_tde_generate_internal_key(InternalKey *int_key, TDEMapEntryType entry_type); +extern void pg_tde_generate_internal_key(InternalKey *int_key); extern void pg_tde_stream_crypt(const char *iv_prefix, uint32 start_offset, const char *data, diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index d2e44e8dd295f..939b4ea552da2 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -75,7 +75,7 @@ tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) { InternalKey *key = palloc_object(InternalKey); - pg_tde_generate_internal_key(key, TDE_KEY_TYPE_SMGR); + pg_tde_generate_internal_key(key); if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) tde_smgr_save_temp_key(&smgr_rlocator->locator, key); @@ -105,7 +105,7 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) if (pg_tde_has_smgr_key(*rlocator)) return; - pg_tde_generate_internal_key(&key, TDE_KEY_TYPE_SMGR); + pg_tde_generate_internal_key(&key); pg_tde_save_smgr_key(*rlocator, &key); } From 1a20e9bb45fbcdf83a4020620667dafd1baf1403 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 7 Aug 2025 17:02:02 +0300 Subject: [PATCH 563/796] PG-1813 Make WAL keys TLI aware Before this commit, WAL keys didn't mind TLI at all. But after pg_rewind, for example, pg_wal/ may contain segments from two timelines. And the wal reader choosing the key may pick the wrong one because LSNs of different TLIs may overlap. There was also another bug: There is a key with the start LSN 0/30000 in TLI 1. And after the start in TLI 2, the wal writer creates a new key with the SN 0/30000, but in TLI 2. But the reader wouldn't fetch the latest key because w/o TLI, these are the same. This commit adds TLI to the Internal keys and makes use of it along with LSN for key compares. --- contrib/pg_tde/meson.build | 1 + contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 35 +++--- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 103 ++++++++++++------ .../src/include/access/pg_tde_xlog_keys.h | 44 +++++++- contrib/pg_tde/src/include/pg_tde_fe.h | 2 + contrib/pg_tde/t/wal_key_tli.pl | 83 ++++++++++++++ 6 files changed, 213 insertions(+), 55 deletions(-) create mode 100644 contrib/pg_tde/t/wal_key_tli.pl diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index f2c21edc8dd2f..6410a3b406295 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -126,6 +126,7 @@ tap_tests = [ 't/unlogged_tables.pl', 't/wal_archiving.pl', 't/wal_encrypt.pl', + 't/wal_key_tli.pl', ] tests += { diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 25c8473a20198..fc3e8820e194c 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -25,6 +25,7 @@ #define PG_TDE_WAL_KEY_FILE_NAME "wal_keys" #define MaxXLogRecPtr (~(XLogRecPtr)0) +#define MaxTimeLineID (~(TimeLineID)0) typedef struct WalKeyFileHeader { @@ -44,7 +45,7 @@ typedef struct WalKeyFileEntry static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; -static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key, XLogRecPtr start_lsn); +static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key); static WalEncryptionKey *pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const WalEncryptionKey *rel_key_data); static int pg_tde_open_wal_key_file_basic(const char *filename, int flags, bool ignore_missing); @@ -69,7 +70,7 @@ get_wal_key_file_path(void) } void -pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn) +pg_tde_wal_last_key_set_location(WalLocation loc) { LWLock *lock_pk = tde_lwlock_enc_keys(); int fd; @@ -85,9 +86,9 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn) write_pos = sizeof(WalKeyFileHeader) + (last_key_idx * sizeof(WalKeyFileEntry)) + offsetof(WalKeyFileEntry, enc_key) + - offsetof(WalEncryptionKey, start_lsn); + offsetof(WalEncryptionKey, wal_start); - if (pg_pwrite(fd, &lsn, sizeof(XLogRecPtr), write_pos) != sizeof(XLogRecPtr)) + if (pg_pwrite(fd, &loc, sizeof(WalLocation), write_pos) != sizeof(WalLocation)) { ereport(ERROR, errcode_for_file_access(), @@ -111,7 +112,7 @@ pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn) errmsg("could not read previous WAL key: %m")); } - if (prev_entry.enc_key.start_lsn >= lsn) + if (wal_location_cmp(prev_entry.enc_key.wal_start, loc) >= 0) { prev_entry.enc_key.type = TDE_KEY_TYPE_WAL_INVALID; @@ -160,7 +161,8 @@ pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ rel_key_data->type = entry_type; - rel_key_data->start_lsn = InvalidXLogRecPtr; + rel_key_data->wal_start.lsn = InvalidXLogRecPtr; + rel_key_data->wal_start.tli = 0; if (!RAND_bytes(rel_key_data->key, INTERNAL_KEY_LEN)) ereport(ERROR, @@ -245,7 +247,7 @@ pg_tde_read_last_wal_key(void) /* Fetches WAL keys from disk and adds them to the WAL cache */ WALKeyCacheRec * -pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) +pg_tde_fetch_wal_keys(WalLocation start) { off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); @@ -276,10 +278,10 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) { WALKeyCacheRec *wal_rec; WalEncryptionKey stub_key = { - .start_lsn = InvalidXLogRecPtr, + .wal_start = {.tli = 0,.lsn = InvalidXLogRecPtr}, }; - wal_rec = pg_tde_add_wal_key_to_cache(&stub_key, InvalidXLogRecPtr); + wal_rec = pg_tde_add_wal_key_to_cache(&stub_key); #ifdef FRONTEND /* The backend frees it after copying to the cache. */ @@ -299,15 +301,15 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) /* * Skip new (just created but not updated by write) and invalid keys */ - if (entry.enc_key.start_lsn != InvalidXLogRecPtr && + if (wal_location_valid(entry.enc_key.wal_start) && (entry.enc_key.type == TDE_KEY_TYPE_WAL_UNENCRYPTED || entry.enc_key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) && - entry.enc_key.start_lsn >= start_lsn) + wal_location_cmp(entry.enc_key.wal_start, start) >= 0) { WalEncryptionKey *rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); WALKeyCacheRec *wal_rec; - wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data, entry.enc_key.start_lsn); + wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data); pfree(rel_key_data); @@ -325,7 +327,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn) } static WALKeyCacheRec * -pg_tde_add_wal_key_to_cache(WalEncryptionKey *key, XLogRecPtr start_lsn) +pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) { WALKeyCacheRec *wal_rec; #ifndef FRONTEND @@ -338,8 +340,9 @@ pg_tde_add_wal_key_to_cache(WalEncryptionKey *key, XLogRecPtr start_lsn) MemoryContextSwitchTo(oldCtx); #endif - wal_rec->start_lsn = start_lsn; - wal_rec->end_lsn = MaxXLogRecPtr; + wal_rec->start = key->wal_start; + wal_rec->end.tli = MaxTimeLineID; + wal_rec->end.lsn = MaxXLogRecPtr; wal_rec->key = *key; wal_rec->crypt_ctx = NULL; if (!tde_wal_key_last_rec) @@ -350,7 +353,7 @@ pg_tde_add_wal_key_to_cache(WalEncryptionKey *key, XLogRecPtr start_lsn) else { tde_wal_key_last_rec->next = wal_rec; - tde_wal_key_last_rec->end_lsn = wal_rec->start_lsn; + tde_wal_key_last_rec->end = wal_rec->start; tde_wal_key_last_rec = wal_rec; } diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 7dd9e7b821872..cbffeeff4584e 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -45,7 +45,7 @@ static void *EncryptionCryptCtx = NULL; static WalEncryptionKey EncryptionKey = { .type = MAP_ENTRY_EMPTY, - .start_lsn = InvalidXLogRecPtr, + .wal_start = {.tli = 0,.lsn = InvalidXLogRecPtr}, }; /* @@ -65,7 +65,12 @@ static WalEncryptionKey EncryptionKey = typedef struct EncryptionStateData { - pg_atomic_uint64 enc_key_lsn; /* to sync with readers */ + /* + * To sync with readers. We sync on LSN only and TLI here just to + * communicate its value to readers. + */ + pg_atomic_uint32 enc_key_tli; + pg_atomic_uint64 enc_key_lsn; } EncryptionStateData; static EncryptionStateData *EncryptionState = NULL; @@ -78,10 +83,24 @@ TDEXLogGetEncKeyLsn() return (XLogRecPtr) pg_atomic_read_u64(&EncryptionState->enc_key_lsn); } +static TimeLineID +TDEXLogGetEncKeyTli() +{ + return (TimeLineID) pg_atomic_read_u32(&EncryptionState->enc_key_tli); +} + static void -TDEXLogSetEncKeyLsn(XLogRecPtr start_lsn) +TDEXLogSetEncKeyLocation(WalLocation loc) { - pg_atomic_write_u64(&EncryptionState->enc_key_lsn, start_lsn); + /* + * Write TLI first and then LSN. The barrier ensures writes won't be + * reordered. When reading, the opposite must be done (with a matching + * barrier in between), so we always see a valid TLI after observing a + * valid LSN. + */ + pg_atomic_write_u32(&EncryptionState->enc_key_tli, loc.tli); + pg_write_barrier(); + pg_atomic_write_u64(&EncryptionState->enc_key_lsn, loc.lsn); } static Size TDEXLogEncryptBuffSize(void); @@ -166,7 +185,8 @@ TDEXLogShmemInit(void) typedef struct EncryptionStateData { - XLogRecPtr enc_key_lsn; /* to sync with reader */ + TimeLineID enc_key_tli; + XLogRecPtr enc_key_lsn; } EncryptionStateData; static EncryptionStateData EncryptionStateD = {0}; @@ -181,10 +201,17 @@ TDEXLogGetEncKeyLsn() return (XLogRecPtr) EncryptionState->enc_key_lsn; } +static TimeLineID +TDEXLogGetEncKeyTli() +{ + return (TimeLineID) EncryptionState->enc_key_tli; +} + static void -TDEXLogSetEncKeyLsn(XLogRecPtr start_lsn) +TDEXLogSetEncKeyLocation(WalLocation loc) { - EncryptionState->enc_key_lsn = EncryptionKey.start_lsn; + EncryptionState->enc_key_tli = loc.tli; + EncryptionState->enc_key_lsn = loc.lsn; } #endif /* FRONTEND */ @@ -216,7 +243,7 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) else if (key) { EncryptionKey = *key; - TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn); + TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); } if (key) @@ -231,7 +258,7 @@ TDEXLogSmgrInitWriteReuseKey() if (key) { EncryptionKey = *key; - TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn); + TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); pfree(key); } } @@ -252,8 +279,8 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #endif #ifdef TDE_XLOG_DEBUG - elog(DEBUG1, "write encrypted WAL, size: %lu, offset: %ld [%lX], seg: %X/%X, key_start_lsn: %X/%X", - count, offset, offset, LSN_FORMAT_ARGS(segno), LSN_FORMAT_ARGS(key->start_lsn)); + elog(DEBUG1, "write encrypted WAL, size: %lu, offset: %ld [%lX], seg: %X/%X, key_start_lsn: %u_%X/%X", + count, offset, offset, LSN_FORMAT_ARGS(segno), key->wal_start.tli, LSN_FORMAT_ARGS(key->wal_start.lsn)); #endif CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); @@ -279,13 +306,13 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, */ if (EncryptionKey.type != MAP_ENTRY_EMPTY && TDEXLogGetEncKeyLsn() == 0) { - XLogRecPtr lsn; + WalLocation loc = {.tli = tli}; - XLogSegNoOffsetToRecPtr(segno, offset, segSize, lsn); + XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); - pg_tde_wal_last_key_set_lsn(lsn); - EncryptionKey.start_lsn = lsn; - TDEXLogSetEncKeyLsn(lsn); + pg_tde_wal_last_key_set_location(loc); + EncryptionKey.wal_start = loc; + TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); } if (EncryptionKey.type == TDE_KEY_TYPE_WAL_ENCRYPTED) @@ -304,12 +331,12 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, ssize_t readsz; WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); XLogRecPtr write_key_lsn; - XLogRecPtr data_start; - XLogRecPtr data_end; + WalLocation data_end = {.tli = tli}; + WalLocation data_start = {.tli = tli}; #ifdef TDE_XLOG_DEBUG - elog(DEBUG1, "read from a WAL segment, size: %lu offset: %ld [%lX], seg: %X/%X", - count, offset, offset, LSN_FORMAT_ARGS(segno)); + elog(DEBUG1, "read from a WAL segment, size: %lu offset: %ld [%lX], seg: %u_%X/%X", + count, offset, offset, tli, LSN_FORMAT_ARGS(segno)); #endif readsz = pg_pread(fd, buf, count, offset); @@ -319,30 +346,38 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, if (!keys) { + WalLocation start = {.tli = 1,.lsn = 0}; + /* cache is empty, try to read keys from disk */ - keys = pg_tde_fetch_wal_keys(InvalidXLogRecPtr); + keys = pg_tde_fetch_wal_keys(start); } + /* + * The barrier ensures that we always read a vaild TLI after the valid + * LSN. See the comment in TDEXLogSetEncKeyLocation() + */ write_key_lsn = TDEXLogGetEncKeyLsn(); + pg_read_barrier(); if (!XLogRecPtrIsInvalid(write_key_lsn)) { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); + WalLocation write_loc = {.tli = TDEXLogGetEncKeyTli(),.lsn = write_key_lsn}; Assert(last_key); /* write has generated a new key, need to fetch it */ - if (last_key->start_lsn < write_key_lsn) + if (wal_location_cmp(last_key->start, write_loc) < 0) { - pg_tde_fetch_wal_keys(write_key_lsn); + pg_tde_fetch_wal_keys(write_loc); /* in case cache was empty before */ keys = pg_tde_get_wal_cache_keys(); } } - XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start); - XLogSegNoOffsetToRecPtr(segno, offset + readsz, segSize, data_end); + XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start.lsn); + XLogSegNoOffsetToRecPtr(segno, offset + readsz, segSize, data_end.lsn); /* * TODO: this is higly ineffective. We should get rid of linked list and @@ -351,24 +386,24 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, for (WALKeyCacheRec *curr_key = keys; curr_key != NULL; curr_key = curr_key->next) { #ifdef TDE_XLOG_DEBUG - elog(DEBUG1, "WAL key %X/%X-%X/%X, encrypted: %s", - LSN_FORMAT_ARGS(curr_key->start_lsn), - LSN_FORMAT_ARGS(curr_key->end_lsn), + elog(DEBUG1, "WAL key %u_%X/%X - %u_%X/%X, encrypted: %s", + curr_key->start.tli, LSN_FORMAT_ARGS(curr_key->start.lsn), + curr_key->end.tli, LSN_FORMAT_ARGS(curr_key->end.lsn), curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); #endif - if (curr_key->key.start_lsn != InvalidXLogRecPtr && + if (wal_location_valid(curr_key->key.wal_start) && curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) { /* * Check if the key's range overlaps with the buffer's and decypt * the part that does. */ - if (data_start < curr_key->end_lsn && data_end > curr_key->start_lsn) + if (wal_location_cmp(data_start, curr_key->end) < 0 && wal_location_cmp(data_end, curr_key->start) > 0) { char iv_prefix[16]; - off_t dec_off = XLogSegmentOffset(Max(data_start, curr_key->start_lsn), segSize); - off_t dec_end = XLogSegmentOffset(Min(data_end, curr_key->end_lsn), segSize); + off_t dec_off = XLogSegmentOffset(Max(data_start.lsn, curr_key->start.lsn), segSize); + off_t dec_end = XLogSegmentOffset(Min(data_end.lsn, curr_key->end.lsn), segSize); size_t dec_sz; char *dec_buf = (char *) buf + (dec_off - offset); @@ -385,8 +420,8 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, dec_sz = dec_end - dec_off; #ifdef TDE_XLOG_DEBUG - elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %X/%X", - dec_off, dec_off - offset, dec_sz, LSN_FORMAT_ARGS(curr_key->key->start_lsn)); + elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %u_%X/%X", + dec_off, dec_off - offset, dec_sz, curr_key->key.wal_start.tli, LSN_FORMAT_ARGS(curr_key->key.wal_start.lsn)); #endif pg_tde_stream_crypt(iv_prefix, dec_off, diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index e1c6efc665b97..68835ac11e835 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -6,13 +6,47 @@ #include "access/pg_tde_tdemap.h" #include "catalog/tde_principal_key.h" +typedef struct WalLocation +{ + XLogRecPtr lsn; + TimeLineID tli; +} WalLocation; + +/* + * Compares given WAL locations and returns -1 if l1 < l2, 0 if l1 == l2, + * and 1 if l1 > l2 + */ +static inline int +wal_location_cmp(WalLocation l1, WalLocation l2) +{ + if (unlikely(l1.tli < l2.tli)) + return -1; + + if (unlikely(l1.tli > l2.tli)) + return 1; + + if (l1.lsn < l2.lsn) + return -1; + + if (l1.lsn > l2.lsn) + return 1; + + return 0; +} + +static inline bool +wal_location_valid(WalLocation loc) +{ + return loc.tli != 0 && loc.lsn != InvalidXLogRecPtr; +} + typedef struct WalEncryptionKey { uint8 key[INTERNAL_KEY_LEN]; uint8 base_iv[INTERNAL_KEY_IV_LEN]; uint32 type; - XLogRecPtr start_lsn; + WalLocation wal_start; } WalEncryptionKey; /* @@ -21,8 +55,8 @@ typedef struct WalEncryptionKey */ typedef struct WALKeyCacheRec { - XLogRecPtr start_lsn; - XLogRecPtr end_lsn; + WalLocation start; + WalLocation end; WalEncryptionKey key; void *crypt_ctx; @@ -33,7 +67,7 @@ typedef struct WALKeyCacheRec extern int pg_tde_count_wal_keys_in_file(void); extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type); extern void pg_tde_delete_server_key(void); -extern WALKeyCacheRec *pg_tde_fetch_wal_keys(XLogRecPtr start_lsn); +extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(void); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); @@ -41,6 +75,6 @@ extern void pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, TDE extern WalEncryptionKey *pg_tde_read_last_wal_key(void); extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); -extern void pg_tde_wal_last_key_set_lsn(XLogRecPtr lsn); +extern void pg_tde_wal_last_key_set_location(WalLocation loc); #endif /* PG_TDE_XLOG_KEYS_H */ diff --git a/contrib/pg_tde/src/include/pg_tde_fe.h b/contrib/pg_tde/src/include/pg_tde_fe.h index 86d4f2377998d..7d11d9c6a85ad 100644 --- a/contrib/pg_tde/src/include/pg_tde_fe.h +++ b/contrib/pg_tde/src/include/pg_tde_fe.h @@ -88,6 +88,8 @@ static int tde_fe_error_level = 0; #define FreeFile(file) fclose(file) #define pg_fsync(fd) fsync(fd) + +#define pg_read_barrier() NULL #endif /* FRONTEND */ #endif /* PG_TDE_EREPORT_H */ diff --git a/contrib/pg_tde/t/wal_key_tli.pl b/contrib/pg_tde/t/wal_key_tli.pl new file mode 100644 index 0000000000000..c9ba707983176 --- /dev/null +++ b/contrib/pg_tde/t/wal_key_tli.pl @@ -0,0 +1,83 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# A copy pg_rewind_databases with added restart of the standby, which forces two +# WAL keys with the same LSN but different TLI on the primary after pg_rewind. + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +sub run_test +{ + my $test_mode = shift; + + RewindTest::setup_cluster($test_mode, ['-g']); + RewindTest::start_primary(); + + # Create a database in primary with a table. + primary_psql('CREATE DATABASE inprimary'); + primary_psql('CREATE TABLE inprimary_tab (a int)', 'inprimary'); + + RewindTest::create_standby($test_mode); + + # Generates a new WAL key with the start LSN 0/300000. After running + # pg_rewind, the primary will end up with that key and another one with the + # same LSN 0/300000, but different TLI. + $node_standby->restart; + + # Create another database with another table, the creation is + # replicated to the standby. + primary_psql('CREATE DATABASE beforepromotion'); + primary_psql('CREATE TABLE beforepromotion_tab (a int)', + 'beforepromotion'); + + RewindTest::promote_standby(); + + # Create databases in the old primary and the new promoted standby. + primary_psql('CREATE DATABASE primary_afterpromotion'); + primary_psql('CREATE TABLE primary_promotion_tab (a int)', + 'primary_afterpromotion'); + standby_psql('CREATE DATABASE standby_afterpromotion'); + standby_psql('CREATE TABLE standby_promotion_tab (a int)', + 'standby_afterpromotion'); + + # The clusters are now diverged. + + RewindTest::run_pg_rewind($test_mode); + + # Check that the correct databases are present after pg_rewind. + check_query( + 'SELECT datname FROM pg_database ORDER BY 1', + qq(beforepromotion +inprimary +postgres +standby_afterpromotion +template0 +template1 +), + 'database names'); + + # Permissions on PGDATA should have group permissions + SKIP: + { + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os || $Config::Config{osname} eq 'cygwin'); + + ok(check_mode_recursive($node_primary->data_dir(), 0750, 0640), + 'check PGDATA permissions'); + } + + RewindTest::clean_rewind_test(); + return; +} + +run_test('remote'); + +done_testing(); From d7b42c1fde36d32bfb31297084618f8d002bf0e3 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 13:21:14 +0200 Subject: [PATCH 564/796] Remove checks for empty entries in WAL key file Sincw we never delete WAL keys this logic only confuses the reader of the code. Plus we can optimize the insertion of a new WAL key by using seek(). --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 32 ++------------------ 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index fc3e8820e194c..d2a74be0f55fe 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -538,28 +538,8 @@ pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, /* Open and validate file for basic correctness. */ fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_Info, false, &curr_pos); - /* - * Read until we find an empty slot. Otherwise, read until end. This seems - * to be less frequent than vacuum. So let's keep this function here - * rather than overloading the vacuum process. - */ - while (1) - { - WalKeyFileEntry read_entry; - off_t prev_pos = curr_pos; - - if (!pg_tde_read_one_wal_key_file_entry(fd, &read_entry, &curr_pos)) - { - curr_pos = prev_pos; - break; - } - - if (read_entry.type == MAP_ENTRY_EMPTY) - { - curr_pos = prev_pos; - break; - } - } + /* WAL keys are always added at the end of the file */ + curr_pos = lseek(fd, 0, SEEK_END); /* Initialize WAL key file entry and encrypt key */ pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rel_key_data); @@ -674,9 +654,6 @@ pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, if (!pg_tde_read_one_wal_key_file_entry(old_fd, &read_map_entry, &old_curr_pos)) break; - if (read_map_entry.type == MAP_ENTRY_EMPTY) - continue; - /* Decrypt and re-encrypt key */ key = pg_tde_decrypt_wal_key(principal_key, &read_map_entry); pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, key); @@ -829,10 +806,7 @@ pg_tde_count_wal_keys_in_file(void) return count; while (pg_tde_read_one_wal_key_file_entry(fd, &entry, &curr_pos)) - { - if (entry.type != MAP_ENTRY_EMPTY) - count++; - } + count++; CloseTransientFile(fd); From 588938d7b9c2e6a4eea889f67835bf7cd4cec02f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 17:23:04 +0200 Subject: [PATCH 565/796] Clean up type code for the key map file Let's stop pretending that we support more than two status: empty or that there is a SMGR key. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 584dd9f1e448a..60102c5da56b0 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -81,7 +81,7 @@ typedef struct TDEMapEntry } TDEMapEntry; static void pg_tde_set_db_file_path(Oid dbOid, char *path); -static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry); +static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, char *db_map_path, TDEMapEntry *map_entry); static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); @@ -224,7 +224,7 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) if (!pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) break; - if (map_entry.type != MAP_ENTRY_EMPTY && map_entry.spcOid == rlocator.spcOid && map_entry.relNumber == rlocator.relNumber) + if (map_entry.type == TDE_KEY_TYPE_SMGR && map_entry.spcOid == rlocator.spcOid && map_entry.relNumber == rlocator.relNumber) { TDEMapEntry empty_map_entry = { .type = MAP_ENTRY_EMPTY, @@ -533,7 +533,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re * rlocator. */ static bool -pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, char *db_map_path, TDEMapEntry *map_entry) +pg_tde_find_map_entry(const RelFileLocator *rlocator, char *db_map_path, TDEMapEntry *map_entry) { File map_fd; off_t curr_pos = 0; @@ -545,7 +545,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, TDEMapEntryType key_type, while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { - if (map_entry->type == key_type && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) + if (map_entry->type == TDE_KEY_TYPE_SMGR && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) { found = true; break; @@ -848,7 +848,7 @@ pg_tde_has_smgr_key(RelFileLocator rel) LWLockAcquire(tde_lwlock_enc_keys(), LW_SHARED); - result = pg_tde_find_map_entry(&rel, TDE_KEY_TYPE_SMGR, db_map_path, &map_entry); + result = pg_tde_find_map_entry(&rel, db_map_path, &map_entry); LWLockRelease(tde_lwlock_enc_keys()); return result; @@ -875,7 +875,7 @@ pg_tde_get_smgr_key(RelFileLocator rel) LWLockAcquire(lock_pk, LW_SHARED); - if (!pg_tde_find_map_entry(&rel, TDE_KEY_TYPE_SMGR, db_map_path, &map_entry)) + if (!pg_tde_find_map_entry(&rel, db_map_path, &map_entry)) { LWLockRelease(lock_pk); return NULL; From 602cd736e26cfaf7f5e8451c458e79885134779e Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 7 Aug 2025 13:22:04 +0200 Subject: [PATCH 566/796] Split key type enum into two to make code less confusing Also rename enum variants for consistency plus renumber the types for the WAL keys which is fine since this file is newly introduced which makes breaking backwards compatibility not an issue. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 24 ++++++++++++------- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 10 ++++---- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 16 ++++++------- .../pg_tde/src/include/access/pg_tde_tdemap.h | 9 ------- .../src/include/access/pg_tde_xlog_keys.h | 9 ++++++- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 60102c5da56b0..ed01210adccf1 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -44,6 +44,12 @@ #define MAP_ENTRY_SIZE sizeof(TDEMapEntry) #define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) +typedef enum +{ + MAP_ENTRY_TYPE_EMPTY = 0, + MAP_ENTRY_TYPE_KEY = 1, +} TDEMapEntryType; + typedef struct TDEFileHeader { int32 file_version; @@ -224,10 +230,10 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) if (!pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) break; - if (map_entry.type == TDE_KEY_TYPE_SMGR && map_entry.spcOid == rlocator.spcOid && map_entry.relNumber == rlocator.relNumber) + if (map_entry.type == MAP_ENTRY_TYPE_KEY && map_entry.spcOid == rlocator.spcOid && map_entry.relNumber == rlocator.relNumber) { TDEMapEntry empty_map_entry = { - .type = MAP_ENTRY_EMPTY, + .type = MAP_ENTRY_TYPE_EMPTY, }; pg_tde_write_one_map_entry(map_fd, &empty_map_entry, &prev_pos, db_map_path); @@ -303,7 +309,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p if (!pg_tde_read_one_map_entry(old_fd, &read_map_entry, &old_curr_pos)) break; - if (read_map_entry.type == MAP_ENTRY_EMPTY) + if (read_map_entry.type == MAP_ENTRY_TYPE_EMPTY) continue; rloc.spcOid = read_map_entry.spcOid; @@ -422,7 +428,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ { map_entry->spcOid = rlocator->spcOid; map_entry->relNumber = rlocator->relNumber; - map_entry->type = TDE_KEY_TYPE_SMGR; + map_entry->type = MAP_ENTRY_TYPE_KEY; map_entry->enc_key = *rel_key_data; /* @@ -510,7 +516,7 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re break; } - if (read_map_entry.type == MAP_ENTRY_EMPTY) + if (read_map_entry.type == MAP_ENTRY_TYPE_EMPTY) { curr_pos = prev_pos; break; @@ -529,8 +535,8 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re /* * Returns true if we find a valid match; e.g. type is not set to - * MAP_ENTRY_EMPTY and the relNumber and spcOid matches the one provided in - * rlocator. + * MAP_ENTRY_TYPE_EMPTY and the relNumber and spcOid matches the one provided + * in rlocator. */ static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, char *db_map_path, TDEMapEntry *map_entry) @@ -545,7 +551,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, char *db_map_path, TDEMapE while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos)) { - if (map_entry->type == TDE_KEY_TYPE_SMGR && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) + if (map_entry->type == MAP_ENTRY_TYPE_KEY && map_entry->spcOid == rlocator->spcOid && map_entry->relNumber == rlocator->relNumber) { found = true; break; @@ -584,7 +590,7 @@ pg_tde_count_encryption_keys(Oid dbOid) while (pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos)) { - if (map_entry.type == TDE_KEY_TYPE_SMGR) + if (map_entry.type == MAP_ENTRY_TYPE_KEY) count++; } diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index d2a74be0f55fe..2af45c0f363e5 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -114,7 +114,7 @@ pg_tde_wal_last_key_set_location(WalLocation loc) if (wal_location_cmp(prev_entry.enc_key.wal_start, loc) >= 0) { - prev_entry.enc_key.type = TDE_KEY_TYPE_WAL_INVALID; + prev_entry.enc_key.type = WAL_KEY_TYPE_INVALID; if (pg_pwrite(fd, &prev_entry, sizeof(WalKeyFileEntry), prev_key_pos) != sizeof(WalKeyFileEntry)) { @@ -145,7 +145,7 @@ pg_tde_wal_last_key_set_location(WalLocation loc) * with the actual lsn by the first WAL write. */ void -pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type) +pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry_type) { TDEPrincipalKey *principal_key; @@ -159,7 +159,7 @@ pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); } - /* TODO: no need in generating key if TDE_KEY_TYPE_WAL_UNENCRYPTED */ + /* TODO: no need in generating key if WAL_KEY_TYPE_UNENCRYPTED */ rel_key_data->type = entry_type; rel_key_data->wal_start.lsn = InvalidXLogRecPtr; rel_key_data->wal_start.tli = 0; @@ -302,8 +302,8 @@ pg_tde_fetch_wal_keys(WalLocation start) * Skip new (just created but not updated by write) and invalid keys */ if (wal_location_valid(entry.enc_key.wal_start) && - (entry.enc_key.type == TDE_KEY_TYPE_WAL_UNENCRYPTED || - entry.enc_key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) && + (entry.enc_key.type == WAL_KEY_TYPE_UNENCRYPTED || + entry.enc_key.type == WAL_KEY_TYPE_ENCRYPTED) && wal_location_cmp(entry.enc_key.wal_start, start) >= 0) { WalEncryptionKey *rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index cbffeeff4584e..aca076423a6b6 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -44,7 +44,7 @@ static void *EncryptionCryptCtx = NULL; /* TODO: can be swapped out to the disk */ static WalEncryptionKey EncryptionKey = { - .type = MAP_ENTRY_EMPTY, + .type = WAL_KEY_TYPE_INVALID, .wal_start = {.tli = 0,.lsn = InvalidXLogRecPtr}, }; @@ -234,11 +234,11 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) */ if (encrypt_xlog) { - pg_tde_create_wal_key(&EncryptionKey, TDE_KEY_TYPE_WAL_ENCRYPTED); + pg_tde_create_wal_key(&EncryptionKey, WAL_KEY_TYPE_ENCRYPTED); } - else if (key && key->type == TDE_KEY_TYPE_WAL_ENCRYPTED) + else if (key && key->type == WAL_KEY_TYPE_ENCRYPTED) { - pg_tde_create_wal_key(&EncryptionKey, TDE_KEY_TYPE_WAL_UNENCRYPTED); + pg_tde_create_wal_key(&EncryptionKey, WAL_KEY_TYPE_UNENCRYPTED); } else if (key) { @@ -304,7 +304,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, * * This func called with WALWriteLock held, so no need in any extra sync. */ - if (EncryptionKey.type != MAP_ENTRY_EMPTY && TDEXLogGetEncKeyLsn() == 0) + if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && TDEXLogGetEncKeyLsn() == 0) { WalLocation loc = {.tli = tli}; @@ -315,7 +315,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); } - if (EncryptionKey.type == TDE_KEY_TYPE_WAL_ENCRYPTED) + if (EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) return TDEXLogWriteEncryptedPages(fd, buf, count, offset, tli, segno); else return pg_pwrite(fd, buf, count, offset); @@ -389,11 +389,11 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, elog(DEBUG1, "WAL key %u_%X/%X - %u_%X/%X, encrypted: %s", curr_key->start.tli, LSN_FORMAT_ARGS(curr_key->start.lsn), curr_key->end.tli, LSN_FORMAT_ARGS(curr_key->end.lsn), - curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED ? "yes" : "no"); + curr_key->key.type == WAL_KEY_TYPE_ENCRYPTED ? "yes" : "no"); #endif if (wal_location_valid(curr_key->key.wal_start) && - curr_key->key.type == TDE_KEY_TYPE_WAL_ENCRYPTED) + curr_key->key.type == WAL_KEY_TYPE_ENCRYPTED) { /* * Check if the key's range overlaps with the buffer's and decypt diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 8c8781380d796..1cf39990eace8 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -6,15 +6,6 @@ #include "catalog/tde_principal_key.h" #include "common/pg_tde_utils.h" -typedef enum -{ - MAP_ENTRY_EMPTY = 0, - TDE_KEY_TYPE_SMGR = 1, - TDE_KEY_TYPE_WAL_UNENCRYPTED = 2, - TDE_KEY_TYPE_WAL_ENCRYPTED = 3, - TDE_KEY_TYPE_WAL_INVALID = 4, -} TDEMapEntryType; - #define INTERNAL_KEY_LEN 16 #define INTERNAL_KEY_IV_LEN 16 diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 68835ac11e835..6c92f81ec4aa7 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -6,6 +6,13 @@ #include "access/pg_tde_tdemap.h" #include "catalog/tde_principal_key.h" +typedef enum +{ + WAL_KEY_TYPE_INVALID = 0, + WAL_KEY_TYPE_UNENCRYPTED = 1, + WAL_KEY_TYPE_ENCRYPTED = 2, +} WalEncryptionKeyType; + typedef struct WalLocation { XLogRecPtr lsn; @@ -65,7 +72,7 @@ typedef struct WALKeyCacheRec } WALKeyCacheRec; extern int pg_tde_count_wal_keys_in_file(void); -extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, TDEMapEntryType entry_type); +extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry_type); extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); From da899e0f32408d39e5b5e63d54d0cf08c76d6f43 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Wed, 21 May 2025 17:27:29 +0300 Subject: [PATCH 567/796] PG-1603 Make pg_basebackup work with encrypted WAL When WAL is streamed during the backup (default mode), it comes in unencrypted. But we need keys to encrypt it. For now, we expect that the user would put `pg_tde` dir containing the `1664_key` and `1664_providers` into the destination directory before starting the backup. We encrypt the streamed WAL according to internal keys. No `pg_tde` dir means no streamed WAL encryption. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 27 ++++++++++++----- .../src/include/access/pg_tde_xlog_smgr.h | 3 ++ contrib/pg_tde/t/RewindTest.pm | 3 +- contrib/pg_tde/t/pgtde.pm | 13 +++++++++ src/bin/pg_basebackup/Makefile | 16 +++++++++- src/bin/pg_basebackup/bbstreamer_file.c | 7 ++++- src/bin/pg_basebackup/meson.build | 7 +++++ src/bin/pg_basebackup/pg_basebackup.c | 29 +++++++++++++++++-- src/bin/pg_basebackup/receivelog.c | 14 +++++++++ 9 files changed, 106 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index aca076423a6b6..7b79844276366 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -329,10 +329,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize) { ssize_t readsz; - WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); - XLogRecPtr write_key_lsn; - WalLocation data_end = {.tli = tli}; - WalLocation data_start = {.tli = tli}; #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "read from a WAL segment, size: %lu offset: %ld [%lX], seg: %u_%X/%X", @@ -344,6 +340,23 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, if (readsz <= 0) return readsz; + TDEXLogCryptBuffer(buf, count, offset, tli, segno, segSize); + + return readsz; +} + +/* + * [De]Crypt buffer if needed based on provided segment offset, number and TLI + */ +void +TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, + TimeLineID tli, XLogSegNo segno, int segSize) +{ + WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); + XLogRecPtr write_key_lsn; + WalLocation data_end = {.tli = tli}; + WalLocation data_start = {.tli = tli}; + if (!keys) { WalLocation start = {.tli = 1,.lsn = 0}; @@ -377,7 +390,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, } XLogSegNoOffsetToRecPtr(segno, offset, segSize, data_start.lsn); - XLogSegNoOffsetToRecPtr(segno, offset + readsz, segSize, data_end.lsn); + XLogSegNoOffsetToRecPtr(segno, offset + count, segSize, data_end.lsn); /* * TODO: this is higly ineffective. We should get rid of linked list and @@ -414,7 +427,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, /* We have reached the end of the segment */ if (dec_end == 0) { - dec_end = offset + readsz; + dec_end = offset + count; } dec_sz = dec_end - dec_off; @@ -433,8 +446,6 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, } } } - - return readsz; } union u128cast diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 6b434de3d3c93..8069db3c4c0ae 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -13,4 +13,7 @@ extern void TDEXLogSmgrInit(void); extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); extern void TDEXLogSmgrInitWriteReuseKey(void); +extern void TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, + TimeLineID tli, XLogSegNo segno, int segSize); + #endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/t/RewindTest.pm b/contrib/pg_tde/t/RewindTest.pm index 0fa74f2b3150f..2ddc013e9ed95 100644 --- a/contrib/pg_tde/t/RewindTest.pm +++ b/contrib/pg_tde/t/RewindTest.pm @@ -44,6 +44,7 @@ use PostgreSQL::Test::Cluster; use PostgreSQL::Test::RecursiveCopy; use PostgreSQL::Test::Utils; use Test::More; +use pgtde; our @EXPORT = qw( $node_primary @@ -199,7 +200,7 @@ sub create_standby $node_standby = PostgreSQL::Test::Cluster->new( 'standby' . ($extra_name ? "_${extra_name}" : '')); - $node_primary->backup('my_backup'); + PGTDE::backup($node_primary, 'my_backup'); $node_standby->init_from_backup($node_primary, 'my_backup'); my $connstr_primary = $node_primary->connstr(); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index 8f02dce151555..fa86d954b7701 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -109,4 +109,17 @@ sub compare_results return compare($expected_filename_with_path, $out_filename_with_path); } +sub backup +{ + my ($node, $backup_name, %params) = @_; + my $backup_dir = $node->backup_dir . '/' . $backup_name; + + mkdir $backup_dir or die "mkdir($backup_dir) failed: $!"; + + PostgreSQL::Test::RecursiveCopy::copypath($node->data_dir . '/pg_tde', + $backup_dir . '/pg_tde'); + + $node->backup($backup_name, %params); +} + 1; diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile index 26c53e473f560..64147cde3e1ba 100644 --- a/src/bin/pg_basebackup/Makefile +++ b/src/bin/pg_basebackup/Makefile @@ -44,6 +44,17 @@ BBOBJS = \ bbstreamer_tar.o \ bbstreamer_zstd.o +ifeq ($(enable_percona_ext),yes) + +OBJS += \ + xlogreader.o \ + $(top_srcdir)/src/fe_utils/simple_list.o \ + $(top_builddir)/src/libtde/libtdexlog.a \ + $(top_builddir)/src/libtde/libtde.a + +override CPPFLAGS := -I$(top_srcdir)/contrib/pg_tde/src/include -I$(top_srcdir)/contrib/pg_tde/src/libkmip/libkmip/include -DFRONTEND $(CPPFLAGS) +endif + all: pg_basebackup pg_createsubscriber pg_receivewal pg_recvlogical pg_basebackup: $(BBOBJS) $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils @@ -58,6 +69,9 @@ pg_receivewal: pg_receivewal.o $(OBJS) | submake-libpq submake-libpgport submake pg_recvlogical: pg_recvlogical.o $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils $(CC) $(CFLAGS) pg_recvlogical.o $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) +xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/% + rm -f $@ && $(LN_S) $< . + install: all installdirs $(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(bindir)/pg_basebackup$(X)' $(INSTALL_PROGRAM) pg_createsubscriber$(X) '$(DESTDIR)$(bindir)/pg_createsubscriber$(X)' @@ -76,7 +90,7 @@ uninstall: clean distclean: rm -f pg_basebackup$(X) pg_createsubscriber$(X) pg_receivewal$(X) pg_recvlogical$(X) \ $(BBOBJS) pg_createsubscriber.o pg_receivewal.o pg_recvlogical.o \ - $(OBJS) + $(OBJS) xlogreader.c rm -rf tmp_check check: diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c index 0be39dddc977a..b58d8ab9160dd 100644 --- a/src/bin/pg_basebackup/bbstreamer_file.c +++ b/src/bin/pg_basebackup/bbstreamer_file.c @@ -18,6 +18,10 @@ #include "common/logging.h" #include "common/string.h" +#ifdef PERCONA_EXT +#include "pg_tde.h" +#endif + typedef struct bbstreamer_plain_writer { bbstreamer base; @@ -297,7 +301,8 @@ should_allow_existing_directory(const char *pathname) strcmp(filename, "pg_xlog") == 0 || strcmp(filename, "archive_status") == 0 || strcmp(filename, "summaries") == 0 || - strcmp(filename, "pg_tblspc") == 0) + strcmp(filename, "pg_tblspc") == 0 || + strcmp(filename, PG_TDE_DATA_DIR) == 0) return true; if (strspn(filename, "0123456789") == strlen(filename)) diff --git a/src/bin/pg_basebackup/meson.build b/src/bin/pg_basebackup/meson.build index c00acd5e11828..8e15c24e42725 100644 --- a/src/bin/pg_basebackup/meson.build +++ b/src/bin/pg_basebackup/meson.build @@ -12,10 +12,15 @@ common_sources = files( 'walmethods.c', ) +common_sources += xlogreader_sources + pg_basebackup_deps = [frontend_code, libpq, lz4, zlib, zstd] pg_basebackup_common = static_library('libpg_basebackup_common', common_sources, + c_args: ['-DFRONTEND'], # needed for xlogreader et al + link_with: pg_tde_frontend, dependencies: pg_basebackup_deps, + include_directories: pg_tde_inc, kwargs: internal_lib_args, ) @@ -34,6 +39,7 @@ pg_basebackup = executable('pg_basebackup', link_with: [pg_basebackup_common], dependencies: pg_basebackup_deps, kwargs: default_bin_args, + include_directories: pg_tde_inc, ) bin_targets += pg_basebackup @@ -71,6 +77,7 @@ pg_receivewal = executable('pg_receivewal', link_with: [pg_basebackup_common], dependencies: pg_basebackup_deps, kwargs: default_bin_args, + include_directories: pg_tde_inc, ) bin_targets += pg_receivewal diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index 8f3dd04fd2226..1916ec9c805f3 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -38,7 +38,14 @@ #include "receivelog.h" #include "streamutil.h" -#define ERRCODE_DATA_CORRUPTED "XX001" +#ifdef PERCONA_EXT +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" +#include "access/xlog_smgr.h" +#include "pg_tde.h" +#endif + +#define ERRCODE_DATA_CORRUPTED_BCP "XX001" typedef struct TablespaceListCell { @@ -654,6 +661,16 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier, PQserverVersion(conn) < MINIMUM_VERSION_FOR_PG_WAL ? "pg_xlog" : "pg_wal"); +#ifdef PERCONA_EXT + { + char tdedir[MAXPGPATH]; + + snprintf(tdedir, sizeof(tdedir), "%s/%s", basedir, PG_TDE_DATA_DIR); + pg_tde_fe_init(tdedir); + TDEXLogSmgrInit(); + } +#endif + /* Temporary replication slots are only supported in 10 and newer */ if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_TEMP_SLOTS) temp_replication_slot = false; @@ -770,6 +787,14 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found) case 3: case 4: +#ifdef PERCONA_EXT + /* + * `pg_tde` may exists and contain keys and providers for the WAL + * encryption + */ + if (strcmp(dirname, PG_TDE_DATA_DIR)) + return; +#endif /* * Exists, not empty */ @@ -2201,7 +2226,7 @@ BaseBackup(char *compression_algorithm, char *compression_detail, const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (sqlstate && - strcmp(sqlstate, ERRCODE_DATA_CORRUPTED) == 0) + strcmp(sqlstate, ERRCODE_DATA_CORRUPTED_BCP) == 0) { pg_log_error("checksum error occurred"); checksum_failure = true; diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c index 8543f3576a85d..2c4ce11a50a6c 100644 --- a/src/bin/pg_basebackup/receivelog.c +++ b/src/bin/pg_basebackup/receivelog.c @@ -25,6 +25,12 @@ #include "receivelog.h" #include "streamutil.h" +#ifdef PERCONA_EXT +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" +#include "catalog/tde_global_space.h" +#endif + /* currently open WAL file */ static Walfile *walfile = NULL; static bool reportFlushPosition = false; @@ -1044,6 +1050,7 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len, int bytes_left; int bytes_written; int hdr_len; + XLogSegNo segno; /* * Once we've decided we don't want to receive any more, just ignore any @@ -1071,6 +1078,8 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len, /* Extract WAL location for this block */ xlogoff = XLogSegmentOffset(*blockpos, WalSegSz); + XLByteToSeg(*blockpos, segno, WalSegSz); + /* * Verify that the initial location in the stream matches where we think * we are. @@ -1121,6 +1130,11 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len, } } +#ifdef PERCONA_EXT + TDEXLogCryptBuffer(copybuf + hdr_len + bytes_written, bytes_to_write, + xlogoff, stream->timeline, segno, WalSegSz); +#endif + if (stream->walmethod->ops->write(walfile, copybuf + hdr_len + bytes_written, bytes_to_write) != bytes_to_write) From 458e6ed0bec582b8081911102dcb73d611b3a248 Mon Sep 17 00:00:00 2001 From: Artem Gavrilov Date: Mon, 11 Aug 2025 10:54:56 +0200 Subject: [PATCH 568/796] Add missing test to meson build Add missing key vaidation test to meson build configuration. --- contrib/pg_tde/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 6410a3b406295..38ed4622671bc 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -107,6 +107,7 @@ tap_tests = [ 't/change_key_provider.pl', 't/crash_recovery.pl', 't/key_rotate_tablespace.pl', + 't/key_validation.pl', 't/multiple_extensions.pl', 't/pg_tde_change_key_provider.pl', 't/pg_rewind_basic.pl', From bbe1728be4a3d1a75814acff59d7c104a4d51e28 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 11 Aug 2025 14:40:25 +0300 Subject: [PATCH 569/796] PG-1523 Rework uninstallation documentation cleanup (#490) - update the pg_tde uninstall steps and add a troubleshooting section in case user receives an error during uninstall - improve introductory paragraph --- .../documentation/docs/how-to/uninstall.md | 149 ++++++++++++++---- 1 file changed, 116 insertions(+), 33 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/how-to/uninstall.md b/contrib/pg_tde/documentation/docs/how-to/uninstall.md index 16a97e179a86e..7cc6ba83d257f 100644 --- a/contrib/pg_tde/documentation/docs/how-to/uninstall.md +++ b/contrib/pg_tde/documentation/docs/how-to/uninstall.md @@ -1,59 +1,142 @@ # Uninstall pg_tde -If you no longer wish to use TDE in your deployment, you can remove the `pg_tde` extension. To do so, you must have superuser privileges, or database owner privileges in case you only want to remove it from a single database. +If you no longer wish to use Transparent Data Encryption (TDE) in your deployment, you can remove the `pg_tde` extension. + +To proceed, you must have one of the following privileges: + +- Superuser privileges (to remove the extension globally), or +- Database owner privileges (to remove it from a specific database only) + +To uninstall `pg_tde`, follow the steps below. + +## Step 1. Remove `pg_tde` from all databases + +Before uninstalling, you must remove the extension from every database where it is loaded. This includes template databases if `pg_tde` was previously enabled there. + +a. Clean up encrypted tables: + +To decrypt a table and restore it to its default storage method: + +```sql +ALTER TABLE SET ACCESS METHOD heap; +``` + +b. Remove the extension once all encrypted tables have been handled: + +```sql +DROP EXTENSION pg_tde; +``` + +!!! note + + If there are any encrypted objects that were not previously decrypted or deleted, this command will fail and you have to follow the steps above for these objects. + +## Step 2. Turn off WAL encryption + +If you are using WAL encryption, you need to **turn it off** before you uninstall the `pg_tde` library: + +a. Run: + +```sql +ALTER SYSTEM SET pg_tde.wal_encrypt = off; +``` + +b. Restart the PostgreSQL cluster to apply the changes: + +- On Debian and Ubuntu: + +```sh +sudo systemctl restart postgresql +``` + +- On RHEL and derivatives: + +```sh +sudo systemctl restart postgresql-17 +``` + +## Step 3. Uninstall the `pg_tde` shared library !!! warning - This process removes the extension, but does not decrypt data automatically. Only uninstall the extension after all encrypted data **has been removed or decrypted**. -To uninstall `pg_tde`, follow these steps: + This process removes the extension, but **does not** decrypt data automatically. Only uninstall the shared library after all encrypted data **has been removed or decrypted** and WAL encryption **has been disabled**. -1. Decrypt or drop encrypted tables: +!!! note - Before removing the extension, you must either **decrypt** or **drop** all encrypted tables: + Encrypted WAL pages **will not be decrypted**, so any postgres cluster needing to read them will need the `pg_tde` library loaded, and the WAL encryption keys available and in use. - - To decrypt a table, run: +At this point, the shared library is still loaded but no longer active. To fully uninstall `pg_tde`, complete the steps below. - ```sql - ALTER TABLE SET ACCESS METHOD heap; - ``` +a. Run `SHOW shared_preload_libraries` to view the current configuration of preloaded libraries. + +For example: + +```sql +postgres=# SHOW shared_preload_libraries; + shared_preload_libraries +----------------------------------------- +pg_stat_statements,pg_tde,auto_explain +(1 row) + +postgres=# +``` + +b. Remove `pg_tde` from the list and apply the new setting using `ALTER SYSTEM SET shared_preload_libraries=`. + +For example: + +```sql +postgres=# ALTER SYSTEM SET shared_preload_libraries=pg_stat_statements,auto_explain; +ALTER SYSTEM +postgres=# +``` + +!!! note + + Your list of libraries will most likely be different than the above example. + + If `pg_tde` is the only shared library in the list, and it was set via `postgresql.conf` you cannot disable it using the `ALTER SYSTEM SET ...` command. Instead: + + 1. Remove the `shared_preload_libraries` line from `postgresql.conf` + 2. Run `ALTER SYSTEM RESET shared_preload_libraries;` - - To discard data, drop the encrypted tables. +c. Restart the `postgresql` cluster to apply the changes: -2. Drop the extension using the `DROP EXTENSION` command: +- On Debian and Ubuntu: - ```sql - DROP EXTENSION pg_tde; + ```sh + sudo systemctl restart postgresql ``` - Alternatively, to remove everything at once: +- On RHEL and derivatives: - ```sql - DROP EXTENSION pg_tde CASCADE; + ```sh + sudo systemctl restart postgresql-17 ``` - !!! note - The `DROP EXTENSION` command does not delete the underlying `pg_tde`-specific data files from disk. +## Step 4. (Optional) Clean up configuration -3. Run the `DROP EXTENSION` command against every database where you have enabled the `pg_tde` extension, if the goal is to completely remove the extension. This also includes the template databases, in case `pg_tde` was previously enabled there. +At this point it is safe to remove any configuration related to `pg_tde` from `postgresql.conf` and `postgresql.auto.conf`. Look for any configuration parameters prefixed with `pg_tde.` and remove or comment them out, as needed. -4. Remove any reference to `pg_tde` GUC variables from the PostgreSQL configuration file. +## Troubleshooting: PANIC checkpoint not found on restart -5. Modify the `shared_preload_libraries` and remove the 'pg_tde' from it. Use the `ALTER SYSTEM` command for this purpose, or edit the configuration file. +This can happen if WAL encryption was not properly disabled before removing `pg_tde` from `shared_preload_libraries`, when the PostgreSQL server was not restarted after disabling WAL encryption (see step 3.c). - !!! warning - Once `pg_tde` is removed from the `shared_preload_libraries`, reading any leftover encrypted files will fail. Removing the extension from the `shared_preload_libraries` is also possible if the extension is still installed in some databases. - Make sure to do this only if the server has no encrypted files in its data directory. +You might see this when restarting the PostgreSQL cluster: -6. Start or restart the `postgresql` cluster to apply the changes. +```sh +2025-04-01 17:12:50.607 CEST [496385] PANIC: could not locate a valid checkpoint record at 0/17B2580 +``` - * On Debian and Ubuntu: +To resolve it follow these steps: - ```sh - sudo systemctl restart postgresql - ``` +1. Re-add `pg_tde` to `shared_preload_libraries` +2. Restart the PostgreSQL cluster +3. Follow the [instructions for turning off WAL encryption](#step-2-turn-off-wal-encryption) before uninstalling the shared library again - * On RHEL and derivatives +!!! note - ```sh - sudo systemctl restart postgresql-17 - ``` + Two restarts are required to uninstall properly if WAL encryption was enabled: + + - First to disable WAL encryption + - Second to remove the `pg_tde` library From cf91f9471014a292a32b325bd8435ee3e6030580 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sun, 10 Aug 2025 22:42:06 +0200 Subject: [PATCH 570/796] Use sizeof directly instead of defines We have already done this refactoring for the WAL key code so let's do it for the SMGR keys too. This makes the code easier to understand. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index ed01210adccf1..16fda32010050 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -41,9 +41,6 @@ #define PG_TDE_FILEMAGIC 0x03454454 /* version ID value = TDE 03 */ #define PG_TDE_MAP_FILENAME "%d_keys" -#define MAP_ENTRY_SIZE sizeof(TDEMapEntry) -#define TDE_FILE_HEADER_SIZE sizeof(TDEFileHeader) - typedef enum { MAP_ENTRY_TYPE_EMPTY = 0, @@ -458,9 +455,9 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, { int bytes_written = 0; - bytes_written = pg_pwrite(fd, map_entry, MAP_ENTRY_SIZE, *offset); + bytes_written = pg_pwrite(fd, map_entry, sizeof(TDEMapEntry), *offset); - if (bytes_written != MAP_ENTRY_SIZE) + if (bytes_written != sizeof(TDEMapEntry)) { ereport(ERROR, errcode_for_file_access(), @@ -719,13 +716,13 @@ pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader { Assert(fheader); - *bytes_read = pg_pread(fd, fheader, TDE_FILE_HEADER_SIZE, 0); + *bytes_read = pg_pread(fd, fheader, sizeof(TDEFileHeader), 0); /* File is empty */ if (*bytes_read == 0) return; - if (*bytes_read != TDE_FILE_HEADER_SIZE + if (*bytes_read != sizeof(TDEFileHeader) || fheader->file_version != PG_TDE_FILEMAGIC) { ereport(FATAL, @@ -747,9 +744,9 @@ pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrinci fheader.file_version = PG_TDE_FILEMAGIC; fheader.signed_key_info = *signed_key_info; - *bytes_written = pg_pwrite(fd, &fheader, TDE_FILE_HEADER_SIZE, 0); + *bytes_written = pg_pwrite(fd, &fheader, sizeof(TDEFileHeader), 0); - if (*bytes_written != TDE_FILE_HEADER_SIZE) + if (*bytes_written != sizeof(TDEFileHeader)) { ereport(ERROR, errcode_for_file_access(), @@ -781,10 +778,10 @@ pg_tde_read_one_map_entry(int map_file, TDEMapEntry *map_entry, off_t *offset) Assert(map_entry); Assert(offset); - bytes_read = pg_pread(map_file, map_entry, MAP_ENTRY_SIZE, *offset); + bytes_read = pg_pread(map_file, map_entry, sizeof(TDEMapEntry), *offset); /* We've reached the end of the file. */ - if (bytes_read != MAP_ENTRY_SIZE) + if (bytes_read != sizeof(TDEMapEntry)) return false; *offset += bytes_read; From 695a1426cc94c0cea1d1963be180b2aacdd05878 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sun, 10 Aug 2025 22:44:08 +0200 Subject: [PATCH 571/796] Move code from key rotation helpers into the function Breaking these particular snippets out as separate functions did not improve readability and was only done because they use to be called from multiple locations. This change has already been done in the WAL key code. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 35 +++-------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 16fda32010050..99f51ca48afef 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -93,8 +93,6 @@ static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *off #ifndef FRONTEND static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, const char *db_map_path); -static int keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos); -static void finalize_key_rotation(const char *path_old, const char *path_new); static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); @@ -243,33 +241,6 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) LWLockRelease(tde_lwlock_enc_keys()); } -/* - * Accepts the unrotated filename and returns the rotation temp - * filename. Both the strings are expected to be of the size - * MAXPGPATH. - * - * No error checking by this function. - */ -static File -keyrotation_init_file(const TDESignedPrincipalKeyInfo *signed_key_info, char *rotated_filename, const char *filename, off_t *curr_pos) -{ - /* - * Set the new filenames for the key rotation process - temporary at the - * moment - */ - snprintf(rotated_filename, MAXPGPATH, "%s.r", filename); - - /* Create file, truncate if the rotate file already exits */ - return pg_tde_open_file_write(rotated_filename, signed_key_info, true, curr_pos); -} - -static void -finalize_key_rotation(const char *path_old, const char *path_new) -{ - durable_unlink(path_old, ERROR); - durable_rename(path_new, path_old, ERROR); -} - /* * Rotate keys and generates the WAL record for it. */ @@ -291,9 +262,10 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p pg_tde_sign_principal_key_info(&new_signed_key_info, new_principal_key); pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path); + snprintf(new_path, MAXPGPATH, "%s.r", old_path); old_fd = pg_tde_open_file_read(old_path, false, &old_curr_pos); - new_fd = keyrotation_init_file(&new_signed_key_info, new_path, old_path, &new_curr_pos); + new_fd = pg_tde_open_file_write(new_path, &new_signed_key_info, true, &new_curr_pos); /* Read all entries until EOF */ while (1) @@ -329,7 +301,8 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p * Do the final steps - replace the current _map with the file with new * data */ - finalize_key_rotation(old_path, new_path); + durable_unlink(old_path, ERROR); + durable_rename(new_path, old_path, ERROR); /* * We do WAL writes past the event ("the write behind logging") rather From 1d12fe4d26bdc027a46391d2e80ab6d83d286692 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 11 Aug 2025 11:53:53 +0200 Subject: [PATCH 572/796] Remove two pointless debug log statemenets Just logging that the function was called at DEBUG2 is not very helpful to anyone and is presumably jsut a leftover from someone's attempt at debugging a particular issue they had at some point. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 2 -- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 99f51ca48afef..237714f499087 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -182,8 +182,6 @@ pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog) pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, db_map_path); - ereport(DEBUG2, errmsg("pg_tde_save_principal_key")); - pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); if (write_xlog) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 2af45c0f363e5..28476c820f97c 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -735,8 +735,6 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) off_t curr_pos = 0; TDESignedPrincipalKeyInfo signed_key_Info; - ereport(DEBUG2, errmsg("pg_tde_save_server_key")); - pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); if (write_xlog) From 3d90419b08e707ce7b5256ef393eb81a1df0c50f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sun, 10 Aug 2025 22:59:56 +0200 Subject: [PATCH 573/796] Constify some function arguments Some of the functions which take a principal key should take a const pointer. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 10 +++++----- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 12 ++++++------ contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 2 +- contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 237714f499087..7f22120e45ccf 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -85,7 +85,7 @@ typedef struct TDEMapEntry static void pg_tde_set_db_file_path(Oid dbOid, char *path); static bool pg_tde_find_map_entry(const RelFileLocator *rlocator, char *db_map_path, TDEMapEntry *map_entry); -static InternalKey *tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); +static InternalKey *tde_decrypt_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntry *map_entry); static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool ignore_missing); static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos); static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read); @@ -96,7 +96,7 @@ static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key); +static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key); void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) @@ -243,7 +243,7 @@ pg_tde_free_key_map_entry(const RelFileLocator rlocator) * Rotate keys and generates the WAL record for it. */ void -pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog) +pg_tde_perform_rotate_key(const TDEPrincipalKey *principal_key, const TDEPrincipalKey *new_principal_key, bool write_xlog) { TDESignedPrincipalKeyInfo new_signed_key_info; off_t old_curr_pos, @@ -451,7 +451,7 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, * concurrent in place updates leading to data conflicts. */ void -pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, TDEPrincipalKey *principal_key) +pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key) { char db_map_path[MAXPGPATH]; int map_fd; @@ -579,7 +579,7 @@ pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, con } static InternalKey * -tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) +tde_decrypt_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) { InternalKey *rel_key_data = palloc_object(InternalKey); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 28476c820f97c..004d9bc99fae5 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -46,7 +46,7 @@ static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key); -static WalEncryptionKey *pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); +static WalEncryptionKey *pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const WalEncryptionKey *rel_key_data); static int pg_tde_open_wal_key_file_basic(const char *filename, int flags, bool ignore_missing); static int pg_tde_open_wal_key_file_read(const char *filename, bool ignore_missing, off_t *curr_pos); @@ -56,7 +56,7 @@ static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, WalKeyF static void pg_tde_wal_key_file_header_read(const char *filename, int fd, WalKeyFileHeader *fheader, off_t *bytes_read); static int pg_tde_wal_key_file_header_write(const char *filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_write_one_wal_key_file_entry(int fd, const WalKeyFileEntry *entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, TDEPrincipalKey *principal_key); +static void pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, const TDEPrincipalKey *principal_key); static char * get_wal_key_file_path(void) @@ -526,7 +526,7 @@ pg_tde_read_one_wal_key_file_entry2(int fd, static void pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, - TDEPrincipalKey *principal_key) + const TDEPrincipalKey *principal_key) { int fd; off_t curr_pos = 0; @@ -551,7 +551,7 @@ pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, } static WalEncryptionKey * -pg_tde_decrypt_wal_key(TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) +pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) { WalEncryptionKey *key = palloc_object(WalEncryptionKey); @@ -623,8 +623,8 @@ pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, * Rotate keys and generates the WAL record for it. */ void -pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, - TDEPrincipalKey *new_principal_key, +pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, + const TDEPrincipalKey *new_principal_key, bool write_xlog) { TDESignedPrincipalKeyInfo new_signed_key_info; diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 1cf39990eace8..bd7d53fbcb1e3 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -38,7 +38,7 @@ extern TDESignedPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); extern bool pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const KeyData *principal_key_data); extern void pg_tde_save_principal_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_principal_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); -extern void pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); +extern void pg_tde_perform_rotate_key(const TDEPrincipalKey *principal_key, const TDEPrincipalKey *new_principal_key, bool write_xlog); extern void pg_tde_delete_principal_key(Oid dbOid); extern void pg_tde_delete_principal_key_redo(Oid dbOid); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 6c92f81ec4aa7..b0fedb3bf8a64 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -78,7 +78,7 @@ extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(void); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); -extern void pg_tde_perform_rotate_server_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key, bool write_xlog); +extern void pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, const TDEPrincipalKey *new_principal_key, bool write_xlog); extern WalEncryptionKey *pg_tde_read_last_wal_key(void); extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); From cff0bf5ad31b8ccb1c9ec37633e44cc6fcccb9db Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 9 Aug 2025 15:01:19 +0200 Subject: [PATCH 574/796] Remove warning about WAL encryption being unstable In the next release the WAL encryption will no longer be in beta testing and the on disk format is guaranteed to be stable going forward. --- contrib/pg_tde/expected/access_control.out | 1 - contrib/pg_tde/expected/delete_principal_key.out | 1 - contrib/pg_tde/expected/key_provider.out | 4 ---- contrib/pg_tde/src/catalog/tde_principal_key.c | 3 --- contrib/pg_tde/t/expected/crash_recovery.out | 3 --- contrib/pg_tde/t/expected/replication.out | 1 - contrib/pg_tde/t/expected/wal_encrypt.out | 1 - 7 files changed, 14 deletions(-) diff --git a/contrib/pg_tde/expected/access_control.out b/contrib/pg_tde/expected/access_control.out index 22da3aff28df2..37f649853a38b 100644 --- a/contrib/pg_tde/expected/access_control.out +++ b/contrib/pg_tde/expected/access_control.out @@ -64,7 +64,6 @@ ERROR: must be superuser to access global key providers SELECT pg_tde_set_default_key_using_global_key_provider('key1', 'global-file-provider'); ERROR: must be superuser to access global key providers SELECT pg_tde_set_server_key_using_global_key_provider('key1', 'global-file-provider'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: must be superuser to access global key providers SELECT pg_tde_delete_default_key(); ERROR: must be superuser to access global key providers diff --git a/contrib/pg_tde/expected/delete_principal_key.out b/contrib/pg_tde/expected/delete_principal_key.out index 92b8299c2b725..ffabfa7603b41 100644 --- a/contrib/pg_tde/expected/delete_principal_key.out +++ b/contrib/pg_tde/expected/delete_principal_key.out @@ -150,7 +150,6 @@ SELECT pg_tde_set_key_using_global_key_provider('test-db-key','file-provider'); (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('test-db-key','file-provider'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- diff --git a/contrib/pg_tde/expected/key_provider.out b/contrib/pg_tde/expected/key_provider.out index 7be4f59044fd2..3bf4f6de9623a 100644 --- a/contrib/pg_tde/expected/key_provider.out +++ b/contrib/pg_tde/expected/key_provider.out @@ -245,7 +245,6 @@ SELECT pg_tde_create_key_using_global_key_provider('server-key', 'global-provide (1 row) SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! pg_tde_set_server_key_using_global_key_provider ------------------------------------------------- @@ -319,7 +318,6 @@ ERROR: key provider name cannot be null SELECT pg_tde_set_key_using_global_key_provider('key', NULL); ERROR: key provider name cannot be null SELECT pg_tde_set_server_key_using_global_key_provider('key', NULL); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key provider name cannot be null -- Setting principal key fails if key name is NULL SELECT pg_tde_set_default_key_using_global_key_provider(NULL, 'file-keyring'); @@ -329,7 +327,6 @@ ERROR: key name cannot be null SELECT pg_tde_set_key_using_global_key_provider(NULL, 'file-keyring'); ERROR: key name cannot be null SELECT pg_tde_set_server_key_using_global_key_provider(NULL, 'file-keyring'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key name cannot be null -- Empty string is not allowed for a principal key name SELECT pg_tde_create_key_using_database_key_provider('', 'file-provider'); @@ -364,7 +361,6 @@ SELECT pg_tde_set_key_using_global_key_provider('not-existing', 'file-keyring'); ERROR: key "not-existing" does not exist HINT: Use pg_tde_create_key_using_global_key_provider() to create it. SELECT pg_tde_set_server_key_using_global_key_provider('not-existing', 'file-keyring'); -WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! ERROR: key "not-existing" does not exist HINT: Use pg_tde_create_key_using_global_key_provider() to create it. DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index c7f299a90ba5c..e44cabfea44ca 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -593,9 +593,6 @@ pg_tde_set_server_key_using_global_key_provider(PG_FUNCTION_ARGS) char *principal_key_name = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(0)); char *provider_name = PG_ARGISNULL(1) ? NULL : text_to_cstring(PG_GETARG_TEXT_PP(1)); - ereport(WARNING, - errmsg("The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments!")); - /* Using a global provider for the global (wal) database */ pg_tde_set_principal_key_internal(GLOBAL_DATA_TDE_OID, GLOBAL_DATA_TDE_OID, diff --git a/contrib/pg_tde/t/expected/crash_recovery.out b/contrib/pg_tde/t/expected/crash_recovery.out index 441c72b0ccd49..15604704fe92b 100644 --- a/contrib/pg_tde/t/expected/crash_recovery.out +++ b/contrib/pg_tde/t/expected/crash_recovery.out @@ -17,7 +17,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key', 'gl (1 row) -psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_add_database_key_provider_file('db_keyring', '/tmp/crash_recovery.per'); pg_tde_add_database_key_provider_file --------------------------------------- @@ -56,7 +55,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_1', ' (1 row) -psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_create_key_using_database_key_provider('db_key_1', 'db_keyring'); pg_tde_create_key_using_database_key_provider ----------------------------------------------- @@ -86,7 +84,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider('wal_encryption_key_2', ' (1 row) -psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_create_key_using_database_key_provider('db_key_2', 'db_keyring'); pg_tde_create_key_using_database_key_provider ----------------------------------------------- diff --git a/contrib/pg_tde/t/expected/replication.out b/contrib/pg_tde/t/expected/replication.out index fa8ffa53d3d28..292409cf99195 100644 --- a/contrib/pg_tde/t/expected/replication.out +++ b/contrib/pg_tde/t/expected/replication.out @@ -80,7 +80,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider('test-global-key', 'file- (1 row) -psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! CREATE TABLE test_enc2 (x int PRIMARY KEY) USING tde_heap; INSERT INTO test_enc2 (x) VALUES (1), (2); ALTER SYSTEM SET pg_tde.wal_encrypt = 'on'; diff --git a/contrib/pg_tde/t/expected/wal_encrypt.out b/contrib/pg_tde/t/expected/wal_encrypt.out index 183f20b9beda9..5f374e9fbb9a2 100644 --- a/contrib/pg_tde/t/expected/wal_encrypt.out +++ b/contrib/pg_tde/t/expected/wal_encrypt.out @@ -25,7 +25,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyri (1 row) -psql::1: WARNING: The WAL encryption feature is currently in beta and may be unstable. Do not use it in production environments! SELECT pg_tde_verify_server_key(); pg_tde_verify_server_key -------------------------- From b2bb77c0eff3b6e9da3c2cd71a1d5265afc8819f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Sat, 9 Aug 2025 18:18:33 +0200 Subject: [PATCH 575/796] Move common things for key files into a separate header file Instead of having the WAL key code include the headers for the SMGR keys we move the shared code into a separate header file. Additionally we clean up some minor header issues. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 1 + contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 2 +- .../src/include/access/pg_tde_keys_common.h | 19 +++++++++++++++++++ .../pg_tde/src/include/access/pg_tde_tdemap.h | 16 +--------------- .../src/include/access/pg_tde_xlog_keys.h | 3 +-- 5 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 contrib/pg_tde/src/include/access/pg_tde_keys_common.h diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 7f22120e45ccf..41984704f156e 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -17,6 +17,7 @@ #include "access/pg_tde_xlog.h" #include "catalog/tde_global_space.h" #include "catalog/tde_principal_key.h" +#include "common/pg_tde_utils.h" #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" #include "keyring/keyring_api.h" diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 004d9bc99fae5..460fced80269d 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -9,11 +9,11 @@ #include "storage/fd.h" #include "utils/memutils.h" -#include "access/pg_tde_tdemap.h" #include "access/pg_tde_xlog_keys.h" #include "access/pg_tde_xlog.h" #include "catalog/tde_global_space.h" #include "catalog/tde_principal_key.h" +#include "common/pg_tde_utils.h" #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" diff --git a/contrib/pg_tde/src/include/access/pg_tde_keys_common.h b/contrib/pg_tde/src/include/access/pg_tde_keys_common.h new file mode 100644 index 0000000000000..91554f84474cb --- /dev/null +++ b/contrib/pg_tde/src/include/access/pg_tde_keys_common.h @@ -0,0 +1,19 @@ +#ifndef PG_TDE_KEYS_COMMON_H +#define PG_TDE_KEYS_COMMON_H + +#include "catalog/tde_principal_key.h" + +#define INTERNAL_KEY_LEN 16 +#define INTERNAL_KEY_IV_LEN 16 + +#define MAP_ENTRY_IV_SIZE 16 +#define MAP_ENTRY_AEAD_TAG_SIZE 16 + +typedef struct +{ + TDEPrincipalKeyInfo data; + unsigned char sign_iv[MAP_ENTRY_IV_SIZE]; + unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; +} TDESignedPrincipalKeyInfo; + +#endif /* PG_TDE_KEYS_COMMON_H */ diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index bd7d53fbcb1e3..241d0e7f732ff 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -1,13 +1,9 @@ #ifndef PG_TDE_MAP_H #define PG_TDE_MAP_H -#include "access/xlog_internal.h" #include "storage/relfilelocator.h" -#include "catalog/tde_principal_key.h" -#include "common/pg_tde_utils.h" -#define INTERNAL_KEY_LEN 16 -#define INTERNAL_KEY_IV_LEN 16 +#include "access/pg_tde_keys_common.h" typedef struct InternalKey { @@ -15,16 +11,6 @@ typedef struct InternalKey uint8 base_iv[INTERNAL_KEY_IV_LEN]; } InternalKey; -#define MAP_ENTRY_IV_SIZE 16 -#define MAP_ENTRY_AEAD_TAG_SIZE 16 - -typedef struct -{ - TDEPrincipalKeyInfo data; - unsigned char sign_iv[MAP_ENTRY_IV_SIZE]; - unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; -} TDESignedPrincipalKeyInfo; - extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key); extern bool pg_tde_has_smgr_key(RelFileLocator rel); extern InternalKey *pg_tde_get_smgr_key(RelFileLocator rel); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index b0fedb3bf8a64..e36e7e59a85b7 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -3,8 +3,7 @@ #include "access/xlog_internal.h" -#include "access/pg_tde_tdemap.h" -#include "catalog/tde_principal_key.h" +#include "access/pg_tde_keys_common.h" typedef enum { From 57eefad2a1763ae976d4f6f06607d834eb5b1efe Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 11 Aug 2025 23:20:42 +0200 Subject: [PATCH 576/796] Clean up code blocks in our documentation - Fix whitespace - Make sure to use the right languages - Do not wrap short SQL queries unnecessarily - Add missing end of code block - Add missing semicolon to SQL query --- contrib/pg_tde/documentation/CONTRIBUTING.md | 3 +- contrib/pg_tde/documentation/docs/apt.md | 14 +++---- .../docs/architecture/architecture.md | 2 +- .../pg-tde-change-key-provider.md | 4 +- .../pg_tde/documentation/docs/contribute.md | 3 +- .../pg_tde/documentation/docs/functions.md | 3 ++ .../documentation/docs/how-to/decrypt.md | 3 +- .../docs/how-to/multi-tenant-setup.md | 40 +++++++++---------- .../documentation/docs/how-to/uninstall.md | 2 +- .../pg_tde/documentation/docs/replication.md | 3 +- contrib/pg_tde/documentation/docs/yum.md | 8 ++-- 11 files changed, 44 insertions(+), 41 deletions(-) diff --git a/contrib/pg_tde/documentation/CONTRIBUTING.md b/contrib/pg_tde/documentation/CONTRIBUTING.md index 5708d979dee9f..580022cfc11aa 100644 --- a/contrib/pg_tde/documentation/CONTRIBUTING.md +++ b/contrib/pg_tde/documentation/CONTRIBUTING.md @@ -54,7 +54,7 @@ When you work, you should periodically run tests to check that your changes don To run the tests, use the following command: -``` +```sh source ci_scripts/setup-keyring-servers.sh ci_scripts/make-test.sh all ``` @@ -148,6 +148,7 @@ To verify how your changes look, generate the static site with the documentation cd contrib/pg_tde/documentation docker run --rm -v $(pwd):/docs perconalab/pmm-doc-md mkdocs build ``` + If Docker can't find the image locally, it first downloads the image, and then runs it to build the documentation. 3. Go to the ``site`` directory and open the ``index.html`` file to see the documentation. diff --git a/contrib/pg_tde/documentation/docs/apt.md b/contrib/pg_tde/documentation/docs/apt.md index da8f9a9f9ac93..2751eb89e7d2d 100644 --- a/contrib/pg_tde/documentation/docs/apt.md +++ b/contrib/pg_tde/documentation/docs/apt.md @@ -21,31 +21,31 @@ Check the [list of supported platforms](install.md#__tabbed_1_1) before continui Install them with the following command: ```{.bash data-prompt="$"} - sudo apt-get install -y wget gnupg2 curl lsb-release + sudo apt-get install -y wget gnupg2 curl lsb-release ``` 2. Fetch the `percona-release` package ```{.bash data-prompt="$"} - sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb + sudo wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb ``` 3. Install `percona-release` ```{.bash data-prompt="$"} - sudo dpkg -i percona-release_latest.generic_all.deb + sudo dpkg -i percona-release_latest.generic_all.deb ``` 4. Enable the Percona Distribution for PostgreSQL repository ```{.bash data-prompt="$"} - sudo percona-release enable-only ppg-{{pgversion17}} + sudo percona-release enable-only ppg-{{pgversion17}} ``` 6. Update the local cache ```{.bash data-prompt="$"} - sudo apt-get update + sudo apt-get update ``` ## Install pg_tde {.power-number} @@ -59,7 +59,7 @@ Check the [list of supported platforms](install.md#__tabbed_1_1) before continui The use of the `CASCADE` parameter deletes all tables that were created in the database with `pg_tde` enabled and also all dependencies upon the encrypted table (e.g. foreign keys in a non-encrypted table used in the encrypted one). ```sql - DROP EXTENSION pg_tde CASCADE; + DROP EXTENSION pg_tde CASCADE; ``` 2. Uninstall the `percona-postgresql-17-pg-tde` package. @@ -67,7 +67,7 @@ Check the [list of supported platforms](install.md#__tabbed_1_1) before continui After all [preconditions](#preconditions) are met, run the following command to install `pg_tde`: ```{.bash data-prompt="$"} - sudo apt-get install -y percona-postgresql-17 +sudo apt-get install -y percona-postgresql-17 ``` ## Next steps diff --git a/contrib/pg_tde/documentation/docs/architecture/architecture.md b/contrib/pg_tde/documentation/docs/architecture/architecture.md index bc1df2f25df6e..c06dcb55161f7 100644 --- a/contrib/pg_tde/documentation/docs/architecture/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture/architecture.md @@ -227,7 +227,7 @@ To change a provider from a command line, `pg_tde` provides the `pg_tde_change_k This tool work similarly to the above functions, with the following syntax: -```bash +```sh pg_tde_change_key_provider ... details ... ``` diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md index d372d4a5d166d..4a94de9c84cce 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-change-key-provider.md @@ -16,7 +16,7 @@ To modify the key provider configuration, specify all parameters depending on th The general syntax is as follows: -```bash +```sh pg_tde_change_key_provider [-D ] ``` @@ -29,7 +29,7 @@ pg_tde_change_key_provider [-D ] ] file pg_tde_change_key_provider [-D ] vault-v2 [] pg_tde_change_key_provider [-D ] kmip [] diff --git a/contrib/pg_tde/documentation/docs/contribute.md b/contrib/pg_tde/documentation/docs/contribute.md index b7eceb4f2c15c..841bbbf64fcc8 100644 --- a/contrib/pg_tde/documentation/docs/contribute.md +++ b/contrib/pg_tde/documentation/docs/contribute.md @@ -54,7 +54,7 @@ When you work, you should periodically run tests to check that your changes don To run the tests, use the following command: -``` +```sh source ci_scripts/setup-keyring-servers.sh ci_scripts/make-test.sh all ``` @@ -108,6 +108,7 @@ The steps are the following: ```sh git clone --recursive git@github.com:/postgres.git +``` 3. Change the directory to `contrib/pg_tde` and add the remote upstream repository: diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 73f2da208faa0..1a12073156673 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -117,6 +117,7 @@ SELECT pg_tde_add_database_key_provider_kmip( '/path_to/client_key.pem', '/path_to/server_certificate.pem' ); + SELECT pg_tde_add_global_key_provider_kmip( 'provider-name', 'kmip-addr', @@ -138,6 +139,7 @@ SELECT pg_tde_change_database_key_provider_kmip( '/path_to/client_key.pem', '/path_to/server_certificate.pem' ); + SELECT pg_tde_change_global_key_provider_kmip( 'provider-name', 'kmip-addr', @@ -261,6 +263,7 @@ SELECT pg_tde_set_key_using_database_key_provider( 'provider-name' ); ``` + ### pg_tde_set_key_using_global_key_provider Sets or rotates the global principal key using the specified global key provider and the key name. This key is used for global settings like WAL encryption. diff --git a/contrib/pg_tde/documentation/docs/how-to/decrypt.md b/contrib/pg_tde/documentation/docs/how-to/decrypt.md index 59cc1c4a0d84f..d0117a9905761 100644 --- a/contrib/pg_tde/documentation/docs/how-to/decrypt.md +++ b/contrib/pg_tde/documentation/docs/how-to/decrypt.md @@ -27,8 +27,7 @@ The output returns `f` meaning that the table is no longer encrypted. Alternatively, you can create a new not encrypted table with the same structure and data as the initial table. For example, the original encrypted table is `EncryptedCustomers`. Use the following command to create a new table `Customers`: ```sql -CREATE TABLE Customers AS -SELECT * FROM EncryptedCustomers; +CREATE TABLE Customers AS SELECT * FROM EncryptedCustomers; ``` The new table `Customers` inherits the structure and the data from `EncryptedCustomers`. diff --git a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md index 92bc892285a39..7cb64197baf6d 100644 --- a/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md +++ b/contrib/pg_tde/documentation/docs/how-to/multi-tenant-setup.md @@ -17,7 +17,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo 1. Use the [ALTER SYSTEM :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-altersystem.html) command from `psql` terminal to modify the `shared_preload_libraries` parameter. This requires superuser privileges. - ``` + ```sql ALTER SYSTEM SET shared_preload_libraries = 'pg_tde'; ``` @@ -37,7 +37,7 @@ Load the `pg_tde` at startup time. The extension requires additional shared memo 3. Create the extension using the [CREATE EXTENSION :octicons-link-external-16:](https://www.postgresql.org/docs/current/sql-createextension.html) command. You must have the privileges of a superuser or a database owner to use this command. Connect to `psql` as a superuser for a database and run the following command: - ``` + ```sql CREATE EXTENSION pg_tde; ``` @@ -73,7 +73,7 @@ You must do these steps for every database where you have created the extension. '/path_to/client_cert.pem', '/path_to/client_key.pem', '/path_to/server_certificate.pem' - ); + ); ``` where: @@ -95,7 +95,7 @@ You must do these steps for every database where you have created the extension. '/tmp/client_cert_jane_doe.pem', '/tmp/client_key_jane_doe.pem', '/tmp/server_certificate.pem' - ); + ); ``` === "With HashiCorp Vault" @@ -109,7 +109,7 @@ You must do these steps for every database where you have created the extension. 'mount', 'secret_token_path', 'ca_path' - ); + ); ``` where: @@ -121,15 +121,15 @@ You must do these steps for every database where you have created the extension. :material-information: Warning: This example is for testing purposes only: - ```sql - SELECT pg_tde_add_database_key_provider_file_vault_v2( + ```sql + SELECT pg_tde_add_database_key_provider_file_vault_v2( 'my-vault', 'http://vault.vault.svc.cluster.local:8200', 'secret/data', 'hvs.zPuyktykA...example...ewUEnIRVaKoBzs2', NULL - ); - ``` + ); + ``` === "With a keyring file (not recommended)" @@ -139,25 +139,25 @@ You must do these steps for every database where you have created the extension. SELECT pg_tde_add_database_key_provider_file( 'provider-name', '/path/to/the/keyring/data.file' - ); + ); ``` :material-information: Warning: This example is for testing purposes only: - ```sql - SELECT pg_tde_add_database_key_provider_file( + ```sql + SELECT pg_tde_add_database_key_provider_file( 'file-keyring', '/tmp/pg_tde_test_local_keyring.per' - ); - ``` + ); + ``` 2. Create a key - ```sql + ```sql SELECT pg_tde_create_key_using_database_key_provider( 'name-of-the-key', 'provider-name' - ); + ); ``` where: @@ -171,19 +171,19 @@ You must do these steps for every database where you have created the extension. SELECT pg_tde_create_key_using_database_key_provider( 'test-db-master-key', 'file-vault' - ); + ); ``` !!! note The key is auto-generated. 3. Use the key as principal key - ```sql + ```sql SELECT pg_tde_set_key_using_database_key_provider( 'name-of-the-key', 'provider-name' - ); + ); ``` where: @@ -197,5 +197,5 @@ You must do these steps for every database where you have created the extension. SELECT pg_tde_set_key_using_database_key_provider( 'test-db-master-key', 'file-vault' - ); + ); ``` diff --git a/contrib/pg_tde/documentation/docs/how-to/uninstall.md b/contrib/pg_tde/documentation/docs/how-to/uninstall.md index 7cc6ba83d257f..cfb0412799a43 100644 --- a/contrib/pg_tde/documentation/docs/how-to/uninstall.md +++ b/contrib/pg_tde/documentation/docs/how-to/uninstall.md @@ -124,7 +124,7 @@ This can happen if WAL encryption was not properly disabled before removing `pg_ You might see this when restarting the PostgreSQL cluster: -```sh +``` 2025-04-01 17:12:50.607 CEST [496385] PANIC: could not locate a valid checkpoint record at 0/17B2580 ``` diff --git a/contrib/pg_tde/documentation/docs/replication.md b/contrib/pg_tde/documentation/docs/replication.md index 88af22ba04dde..ac47d122f39e9 100644 --- a/contrib/pg_tde/documentation/docs/replication.md +++ b/contrib/pg_tde/documentation/docs/replication.md @@ -82,8 +82,7 @@ sudo systemctl start postgresql * On primary: ```sql -SELECT client_addr, state -FROM pg_stat_replication; +SELECT client_addr, state FROM pg_stat_replication; ``` * On standby: diff --git a/contrib/pg_tde/documentation/docs/yum.md b/contrib/pg_tde/documentation/docs/yum.md index b8d5eb911c1b7..e627833f34c60 100644 --- a/contrib/pg_tde/documentation/docs/yum.md +++ b/contrib/pg_tde/documentation/docs/yum.md @@ -11,13 +11,13 @@ You need the `percona-release` repository management tool that enables the desir 1. Install `percona-release`: ```{.bash data-prompt="$"} - sudo yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm + sudo yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm ``` 2. Enable the repository. ```{.bash data-prompt="$"} - sudo percona-release enable-only ppg-17.5 + sudo percona-release enable-only ppg-17.5 ``` ## Install pg_tde {.power-number} @@ -31,7 +31,7 @@ You need the `percona-release` repository management tool that enables the desir The use of the `CASCADE` parameter deletes all tables that were created in the database with `pg_tde` enabled and also all dependencies upon the encrypted table (e.g. foreign keys in a non-encrypted table used in the encrypted one). ```sql - DROP EXTENSION pg_tde CASCADE + DROP EXTENSION pg_tde CASCADE; ``` 2. Uninstall the `percona-pg_tde_17` package. @@ -39,7 +39,7 @@ You need the `percona-release` repository management tool that enables the desir Run the following command to install `pg_tde`: ```{.bash data-prompt="$"} - sudo yum -y install percona-postgresql17-server percona-postgresql17-contrib +sudo yum -y install percona-postgresql17-server percona-postgresql17-contrib ``` ## Next steps From d83b36e58d3a75664c823506740344eca51ab35d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 12 Aug 2025 02:28:32 +0200 Subject: [PATCH 577/796] Remove merge separator from documentation page It was accidentally introduced in commit 8d88d3f28a060d060db0e1eedb5aa47ce3d1150f. --- contrib/pg_tde/documentation/docs/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 1a12073156673..b290d440ee7b7 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -288,7 +288,7 @@ SELECT pg_tde_set_server_key_using_global_key_provider( !!! warning The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. -======= + The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: * If set to `true`, a new key must be unique. From bccaa7ab159347c0f6152a5fa88d5d8dc1f842d2 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 6 Aug 2025 19:14:38 +0200 Subject: [PATCH 578/796] PG-1863 Do not try to delete keys or log WAL for temporary tables We forgot to have a check against trying to delete leftover SMGR keys for temporary tables which is a useless operation since they are store in memory. Additionally we forgot to prevent WAL from being written when creating or removing a key in smgrcreate() for temporary tables. --- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 68 +++++++++++++++------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 939b4ea552da2..5f38222694df1 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -70,6 +70,26 @@ static bool tde_smgr_has_temp_key(const RelFileLocator *rel); static void tde_smgr_remove_temp_key(const RelFileLocator *rel); static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); +static void +tde_smgr_log_create_key(const RelFileLocator *rlocator) +{ + XLogRelKey xlrec = {.rlocator = *rlocator}; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); +} + +static void +tde_smgr_log_delete_key(const RelFileLocator *rlocator) +{ + XLogRelKey xlrec = {.rlocator = *rlocator}; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_RELATION_KEY); +} + static InternalKey * tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) { @@ -80,23 +100,14 @@ tde_smgr_create_key(const RelFileLocatorBackend *smgr_rlocator) if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) tde_smgr_save_temp_key(&smgr_rlocator->locator, key); else + { pg_tde_save_smgr_key(smgr_rlocator->locator, key); + tde_smgr_log_create_key(&smgr_rlocator->locator); + } return key; } -static void -tde_smgr_log_create_key(const RelFileLocatorBackend *smgr_rlocator) -{ - XLogRelKey xlrec = { - .rlocator = smgr_rlocator->locator, - }; - - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); -} - void tde_smgr_create_key_redo(const RelFileLocator *rlocator) { @@ -111,17 +122,22 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) } static void -tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) +tde_smgr_remove_key(const RelFileLocatorBackend *smgr_rlocator) { - XLogRelKey xlrec = { - .rlocator = smgr_rlocator->locator, - }; - - pg_tde_free_key_map_entry(smgr_rlocator->locator); + if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) + tde_smgr_remove_temp_key(&smgr_rlocator->locator); + else + pg_tde_free_key_map_entry(smgr_rlocator->locator); +} - XLogBeginInsert(); - XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_DELETE_RELATION_KEY); +static void +tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) +{ + if (!RelFileLocatorBackendIsTemp(*smgr_rlocator)) + { + pg_tde_free_key_map_entry(smgr_rlocator->locator); + tde_smgr_log_delete_key(&smgr_rlocator->locator); + } } void @@ -148,15 +164,6 @@ tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) return pg_tde_get_smgr_key(smgr_rlocator->locator); } -static void -tde_smgr_remove_key(const RelFileLocatorBackend *smgr_rlocator) -{ - if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) - tde_smgr_remove_temp_key(&smgr_rlocator->locator); - else - pg_tde_free_key_map_entry(smgr_rlocator->locator); -} - static bool tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { @@ -393,7 +400,6 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool } key = tde_smgr_create_key(&reln->smgr_rlocator); - tde_smgr_log_create_key(&reln->smgr_rlocator); tdereln->encryption_status = RELATION_KEY_AVAILABLE; tdereln->relKey = *key; From 13c1038eeb6991ab66626af3b2a99064da44ee81 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 11 Aug 2025 16:27:05 +0200 Subject: [PATCH 579/796] PG-1863 Consistently use create/delete for keys We used a mix of create, add, delete and remove. We still use free and save in pg_tde_tdemap.c but that is soemthing we can fix later. --- contrib/pg_tde/src/access/pg_tde_xlog.c | 10 ++++----- .../pg_tde/src/include/access/pg_tde_xlog.h | 2 +- contrib/pg_tde/src/include/smgr/pg_tde_smgr.h | 2 +- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 22 +++++++++---------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog.c b/contrib/pg_tde/src/access/pg_tde_xlog.c index 2ffe99e7f4478..abace30143bac 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog.c @@ -43,7 +43,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) { uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - if (info == XLOG_TDE_ADD_RELATION_KEY) + if (info == XLOG_TDE_CREATE_RELATION_KEY) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); @@ -62,7 +62,7 @@ tdeheap_rmgr_redo(XLogReaderState *record) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); - tde_smgr_delete_key_redo(&xlrec->rlocator); + tde_smgr_delete_leftover_key_redo(&xlrec->rlocator); } else if (info == XLOG_TDE_ROTATE_PRINCIPAL_KEY) { @@ -99,7 +99,7 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) { uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; - if (info == XLOG_TDE_ADD_RELATION_KEY) + if (info == XLOG_TDE_CREATE_RELATION_KEY) { XLogRelKey *xlrec = (XLogRelKey *) XLogRecGetData(record); @@ -148,8 +148,8 @@ tdeheap_rmgr_identify(uint8 info) { switch (info & ~XLR_INFO_MASK) { - case XLOG_TDE_ADD_RELATION_KEY: - return "ADD_RELATION_KEY"; + case XLOG_TDE_CREATE_RELATION_KEY: + return "CREATE_RELATION_KEY"; case XLOG_TDE_ADD_PRINCIPAL_KEY: return "ADD_PRINCIPAL_KEY"; case XLOG_TDE_ROTATE_PRINCIPAL_KEY: diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog.h b/contrib/pg_tde/src/include/access/pg_tde_xlog.h index 03f8421ff9609..eba8319ab096c 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog.h @@ -8,7 +8,7 @@ #ifndef FRONTEND /* TDE XLOG record types */ -#define XLOG_TDE_ADD_RELATION_KEY 0x00 +#define XLOG_TDE_CREATE_RELATION_KEY 0x00 #define XLOG_TDE_ADD_PRINCIPAL_KEY 0x10 #define XLOG_TDE_ROTATE_PRINCIPAL_KEY 0x20 #define XLOG_TDE_WRITE_KEY_PROVIDER 0x30 diff --git a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h index a97421cfef914..023542f91d1fc 100644 --- a/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h +++ b/contrib/pg_tde/src/include/smgr/pg_tde_smgr.h @@ -6,7 +6,7 @@ extern void RegisterStorageMgr(void); extern void tde_smgr_create_key_redo(const RelFileLocator *rlocator); -extern void tde_smgr_delete_key_redo(const RelFileLocator *rlocator); +extern void tde_smgr_delete_leftover_key_redo(const RelFileLocator *rlocator); extern bool tde_smgr_rel_is_encrypted(SMgrRelation reln); #endif /* PG_TDE_SMGR_H */ diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 5f38222694df1..6645f2a0cb0a7 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -67,7 +67,7 @@ static SMgrId OurSMgrId = MaxSMgrId; static void tde_smgr_save_temp_key(const RelFileLocator *newrlocator, const InternalKey *key); static InternalKey *tde_smgr_get_temp_key(const RelFileLocator *rel); static bool tde_smgr_has_temp_key(const RelFileLocator *rel); -static void tde_smgr_remove_temp_key(const RelFileLocator *rel); +static void tde_smgr_delete_temp_key(const RelFileLocator *rel); static void CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, unsigned char *iv); static void @@ -77,11 +77,11 @@ tde_smgr_log_create_key(const RelFileLocator *rlocator) XLogBeginInsert(); XLogRegisterData((char *) &xlrec, sizeof(xlrec)); - XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_RELATION_KEY); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_CREATE_RELATION_KEY); } static void -tde_smgr_log_delete_key(const RelFileLocator *rlocator) +tde_smgr_log_delete_leftover_key(const RelFileLocator *rlocator) { XLogRelKey xlrec = {.rlocator = *rlocator}; @@ -122,26 +122,26 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) } static void -tde_smgr_remove_key(const RelFileLocatorBackend *smgr_rlocator) +tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) { if (RelFileLocatorBackendIsTemp(*smgr_rlocator)) - tde_smgr_remove_temp_key(&smgr_rlocator->locator); + tde_smgr_delete_temp_key(&smgr_rlocator->locator); else pg_tde_free_key_map_entry(smgr_rlocator->locator); } static void -tde_smgr_delete_key(const RelFileLocatorBackend *smgr_rlocator) +tde_smgr_delete_leftover_key(const RelFileLocatorBackend *smgr_rlocator) { if (!RelFileLocatorBackendIsTemp(*smgr_rlocator)) { pg_tde_free_key_map_entry(smgr_rlocator->locator); - tde_smgr_log_delete_key(&smgr_rlocator->locator); + tde_smgr_log_delete_leftover_key(&smgr_rlocator->locator); } } void -tde_smgr_delete_key_redo(const RelFileLocator *rlocator) +tde_smgr_delete_leftover_key_redo(const RelFileLocator *rlocator) { pg_tde_free_key_map_entry(*rlocator); } @@ -271,7 +271,7 @@ tde_mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo) if (forknum == MAIN_FORKNUM || forknum == InvalidForkNumber) { if (tde_smgr_is_encrypted(&rlocator)) - tde_smgr_remove_key(&rlocator); + tde_smgr_delete_key(&rlocator); } } @@ -391,7 +391,7 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool * If we're in redo, a separate WAL record will make sure the key is * removed. */ - tde_smgr_delete_key(&reln->smgr_rlocator); + tde_smgr_delete_leftover_key(&reln->smgr_rlocator); if (!tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) { @@ -514,7 +514,7 @@ tde_smgr_has_temp_key(const RelFileLocator *rel) } static void -tde_smgr_remove_temp_key(const RelFileLocator *rel) +tde_smgr_delete_temp_key(const RelFileLocator *rel) { Assert(TempRelKeys); hash_search(TempRelKeys, rel, HASH_REMOVE, NULL); From 2d91a891896af464039d5d6ef3c576eb76402748 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 6 Aug 2025 18:45:23 +0200 Subject: [PATCH 580/796] PG-1842 PG-1843 Optimize deletion of leftover relation keys Instead of first deleting any leftover key and then writing the new key we do a single pass through the file where we replace any old key that we find. To make this happen on redo too we need to stop generating a separate WAL record for the key deletion for encrypted tables and only generate that record for unencrypted tables where we still need a key deletion record. We except this optimization to primarily be visible on WAL replay where only a single backend is used to replay everything, but it also speeds up table creation in general on workloads with many tables. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 19 ++++++++++++------- contrib/pg_tde/src/smgr/pg_tde_smgr.c | 23 ++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 41984704f156e..1a9e118771c49 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -97,7 +97,7 @@ static void pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off static int pg_tde_file_header_write(const char *tde_filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, const InternalKey *rel_key_data); static int pg_tde_open_file_write(const char *tde_filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); -static void pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key); +static void pg_tde_replace_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key); void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) @@ -114,7 +114,7 @@ pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *rel_key_data) errhint("Use pg_tde_set_key_using_database_key_provider() or pg_tde_set_key_using_global_key_provider() to configure one.")); } - pg_tde_write_key_map_entry(&rel, rel_key_data, principal_key); + pg_tde_replace_key_map_entry(&rel, rel_key_data, principal_key); LWLockRelease(lock_pk); } @@ -452,11 +452,12 @@ pg_tde_write_one_map_entry(int fd, const TDEMapEntry *map_entry, off_t *offset, * concurrent in place updates leading to data conflicts. */ void -pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key) +pg_tde_replace_key_map_entry(const RelFileLocator *rlocator, const InternalKey *rel_key_data, const TDEPrincipalKey *principal_key) { char db_map_path[MAXPGPATH]; int map_fd; off_t curr_pos = 0; + off_t write_pos = 0; TDEMapEntry write_map_entry; TDESignedPrincipalKeyInfo signed_key_Info; @@ -481,22 +482,26 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, const InternalKey *re if (!pg_tde_read_one_map_entry(map_fd, &read_map_entry, &curr_pos)) { - curr_pos = prev_pos; + if (write_pos == 0) + write_pos = prev_pos; break; } - if (read_map_entry.type == MAP_ENTRY_TYPE_EMPTY) + if (read_map_entry.spcOid == rlocator->spcOid && read_map_entry.relNumber == rlocator->relNumber) { - curr_pos = prev_pos; + write_pos = prev_pos; break; } + + if (write_pos == 0 && read_map_entry.type == MAP_ENTRY_TYPE_EMPTY) + write_pos = prev_pos; } /* Initialize map entry and encrypt key */ pg_tde_initialize_map_entry(&write_map_entry, principal_key, rlocator, rel_key_data); /* Write the given entry at curr_pos; i.e. the free entry. */ - pg_tde_write_one_map_entry(map_fd, &write_map_entry, &curr_pos, db_map_path); + pg_tde_write_one_map_entry(map_fd, &write_map_entry, &write_pos, db_map_path); CloseTransientFile(map_fd); } diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index 6645f2a0cb0a7..1f55a57b6e937 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -113,9 +113,6 @@ tde_smgr_create_key_redo(const RelFileLocator *rlocator) { InternalKey key; - if (pg_tde_has_smgr_key(*rlocator)) - return; - pg_tde_generate_internal_key(&key); pg_tde_save_smgr_key(*rlocator, &key); @@ -383,18 +380,18 @@ tde_mdcreate(RelFileLocator relold, SMgrRelation reln, ForkNumber forknum, bool if (forknum != MAIN_FORKNUM) return; - /* - * If we have a key for this relation already, we need to remove it. This - * can happen if OID is re-used after a crash left a key for a - * non-existing relation in the key file. - * - * If we're in redo, a separate WAL record will make sure the key is - * removed. - */ - tde_smgr_delete_leftover_key(&reln->smgr_rlocator); - if (!tde_smgr_should_encrypt(&reln->smgr_rlocator, &relold)) { + /* + * If we have a key for this relation already, we need to remove it. + * This can happen if OID is re-used after a crash left a key for a + * non-existing relation in the key file. + * + * Old keys for encrypted tables are replace when creating the new + * key. + */ + tde_smgr_delete_leftover_key(&reln->smgr_rlocator); + tdereln->encryption_status = RELATION_NOT_ENCRYPTED; return; } From db41dae20176941f10e33af027ef69e43778c82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 11 Aug 2025 21:54:57 +0200 Subject: [PATCH 581/796] Fix typo in pg_tde_archive_decrypt It's decrypt, not deceypt. --- contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index f3d52d81cf0a2..3c7beccf9cc3d 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -149,7 +149,7 @@ main(int argc, char *argv[]) } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { - puts("pg_tde_archive_deceypt (PostgreSQL) " PG_VERSION); + puts("pg_tde_archive_decrypt (PostgreSQL) " PG_VERSION); exit(0); } } From f5082879dcff7bf4d2f6aead95a5cf5b13cafe03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 12 Aug 2025 17:43:58 +0200 Subject: [PATCH 582/796] PG-1862 Use single argument for wrapped command Use a single argument for the wrapped command in the archivation wrappers. Instead of giving all of the arguments of the command separately and trying to figure out which one should be replaced by the path to the unencrypted WAL segment, we take a single argument and do % parameter replacement similar to what postgres does with archive_command and restore_command. This also mean that we can simplify by using system() instead of exec(). We also clean up usage instructions and make the two wrappers more symmetrical by requiring the same parameters. Co-authored-by: Andreas Karlsson --- .../pg_tde/src/bin/pg_tde_archive_decrypt.c | 80 +++++++++---------- .../pg_tde/src/bin/pg_tde_restore_encrypt.c | 73 ++++++++--------- contrib/pg_tde/t/wal_archiving.pl | 6 +- 3 files changed, 76 insertions(+), 83 deletions(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 3c7beccf9cc3d..4dca62c84fcc6 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -1,12 +1,9 @@ #include "postgres_fe.h" -#include -#include -#include - #include "access/xlog_internal.h" #include "access/xlog_smgr.h" #include "common/logging.h" +#include "common/percentrepl.h" #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" @@ -116,26 +113,39 @@ write_decrypted_segment(const char *segpath, const char *segname, const char *tm static void usage(const char *progname) { - printf(_("%s wraps an archive command to make it archive unencrypted WAL.\n\n"), progname); - printf(_("Usage:\n %s %%p \n\n"), progname); - printf(_("Options:\n")); - printf(_(" -V, --version output version information, then exit\n")); - printf(_(" -?, --help show this help, then exit\n")); + printf(_("%s wraps an archive command to give the command unencrypted WAL.\n\n"), progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]\n"), progname); + printf(_(" %s DEST-NAME SOURCE-PATH ARCHIVE-COMMAND\n"), progname); + printf(_("\nOptions:\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_(" DEST-NAME name of the WAL file to send to archive\n")); + printf(_(" SOURCE-PATH path of the source WAL segment to decrypt\n")); + printf(_(" ARCHIVE-COMMAND archive command to wrap, %%p will be replaced with the\n" + " absolute path of the decrypted WAL segment, %%f with the name\n")); + printf(_("\n")); + printf(_("Note that any %%f or %%p parameter in ARCHIVE-COMMAND will have to be escaped\n" + "as %%%%f or %%%%p respectively if used as archive_command in postgresql.conf.\n" + "e.g.\n" + " archive_command='%s %%f %%p \"cp %%%%p /mnt/server/archivedir/%%%%f\"'\n" + "or\n" + " archive_command='%s %%f %%p \"pgbackrest --stanza=your_stanza archive-push %%%%p\"'\n" + "\n"), progname, progname); } int main(int argc, char *argv[]) { const char *progname; + char *targetname; char *sourcepath; + char *command; char *sep; char *sourcename; char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_archiveXXXXXX"; char tmppath[MAXPGPATH]; bool issegment; - pid_t child; - int status; - int r; pg_logging_init(argv[0]); progname = get_progname(argv[0]); @@ -154,14 +164,16 @@ main(int argc, char *argv[]) } } - if (argc < 3) + if (argc != 4) { - pg_log_error("too few arguments"); + pg_log_error("wrong number of arguments, 3 expected"); pg_log_error_detail("Try \"%s --help\" for more information.", progname); exit(1); } - sourcepath = argv[1]; + targetname = argv[1]; + sourcepath = argv[2]; + command = argv[3]; pg_tde_fe_init("pg_tde"); TDEXLogSmgrInit(); @@ -173,7 +185,7 @@ main(int argc, char *argv[]) else sourcename = sourcepath; - issegment = is_segment(sourcename); + issegment = is_segment(targetname); if (issegment) { @@ -186,35 +198,19 @@ main(int argc, char *argv[]) s = stpcpy(s, "/"); stpcpy(s, sourcename); - for (int i = 2; i < argc; i++) - if (strcmp(sourcepath, argv[i]) == 0) - argv[i] = tmppath; - - write_decrypted_segment(sourcepath, sourcename, tmppath); - } + command = replace_percent_placeholders(command, + "ARCHIVE-COMMAND", "fp", + targetname, tmppath); - child = fork(); - if (child == 0) - { - if (execvp(argv[2], argv + 2) < 0) - pg_fatal("exec failed: %m"); + write_decrypted_segment(sourcepath, targetname, tmppath); } - else if (child < 0) - pg_fatal("could not create background process: %m"); - - r = waitpid(child, &status, 0); - if (r == (pid_t) -1) - pg_fatal("could not wait for child process: %m"); - if (r != child) - pg_fatal("child %d died, expected %d", (int) r, (int) child); - if (status != 0) - { - char *reason = wait_result_to_str(status); + else + command = replace_percent_placeholders(command, + "ARCHIVE-COMMAND", "fp", + targetname, sourcepath); - pg_fatal("%s", reason); - /* keep lsan happy */ - free(reason); - } + if (system(command) != 0) + pg_fatal("ARCHIVE-COMMAND \"%s\" failed: %m", command); if (issegment) { diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index de808b4ef735e..91c9748180ed2 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -1,12 +1,9 @@ #include "postgres_fe.h" -#include -#include -#include - #include "access/xlog_internal.h" #include "access/xlog_smgr.h" #include "common/logging.h" +#include "common/percentrepl.h" #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" @@ -110,11 +107,26 @@ write_encrypted_segment(const char *segpath, const char *segname, const char *tm static void usage(const char *progname) { - printf(_("%s wraps a restore command to make it write encrypted WAL to pg_wal.\n\n"), progname); - printf(_("Usage:\n %s %%f %%p \n\n"), progname); - printf(_("Options:\n")); - printf(_(" -V, --version output version information, then exit\n")); - printf(_(" -?, --help show this help, then exit\n")); + printf(_("%s wraps a restore command to encrypt its returned WAL.\n\n"), progname); + printf(_("Usage:\n")); + printf(_(" %s [OPTION]\n"), progname); + printf(_(" %s SOURCE-NAME DEST-PATH RESTORE-COMMAND\n"), progname); + printf(_("\nOptions:\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_(" SOURCE-NAME name of the WAL file to retrieve from archive\n")); + printf(_(" DEST-PATH path where the encrypted WAL segment should be written\n")); + printf(_(" RESTORE-COMMAND restore command to wrap, %%p will be replaced with the path\n" + " where it should write the unencrypted WAL segment, %%f with\n" + " the WAL segment's name\n")); + printf(_("\n")); + printf(_("Note that any %%f or %%p parameter in RESTORE-COMMAND will have to be escaped\n" + "as %%%%f or %%%%p respectively if used as restore_command in postgresql.conf.\n" + "e.g.\n" + " restore_command='%s %%f %%p \"cp /mnt/server/archivedir/%%%%f %%%%p\"'\n" + "or\n" + " restore_command='%s %%f %%p \"pgbackrest --stanza=your_stanza archive-get %%%%f \\\"%%%%p\\\"\"'\n" + "\n"), progname, progname); } int @@ -123,14 +135,12 @@ main(int argc, char *argv[]) const char *progname; char *sourcename; char *targetpath; + char *command; char *sep; char *targetname; char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_restoreXXXXXX"; char tmppath[MAXPGPATH]; bool issegment; - pid_t child; - int status; - int r; pg_logging_init(argv[0]); progname = get_progname(argv[0]); @@ -149,15 +159,16 @@ main(int argc, char *argv[]) } } - if (argc < 4) + if (argc != 4) { - pg_log_error("too few arguments"); + pg_log_error("wrong number of arguments, 3 expected"); pg_log_error_detail("Try \"%s --help\" for more information.", progname); exit(1); } sourcename = argv[1]; targetpath = argv[2]; + command = argv[3]; pg_tde_fe_init("pg_tde"); TDEXLogSmgrInit(); @@ -182,33 +193,17 @@ main(int argc, char *argv[]) s = stpcpy(s, "/"); stpcpy(s, targetname); - for (int i = 2; i < argc; i++) - if (strcmp(targetpath, argv[i]) == 0) - argv[i] = tmppath; - } - - child = fork(); - if (child == 0) - { - if (execvp(argv[3], argv + 3) < 0) - pg_fatal("exec failed: %m"); + command = replace_percent_placeholders(command, + "RESTORE-COMMAND", "fp", + sourcename, tmppath); } - else if (child < 0) - pg_fatal("could not create background process: %m"); - - r = waitpid(child, &status, 0); - if (r == (pid_t) -1) - pg_fatal("could not wait for child process: %m"); - if (r != child) - pg_fatal("child %d died, expected %d", (int) r, (int) child); - if (status != 0) - { - char *reason = wait_result_to_str(status); + else + command = replace_percent_placeholders(command, + "RESTORE-COMMAND", "fp", + sourcename, targetpath); - pg_fatal("%s", reason); - /* keep lsan happy */ - free(reason); - } + if (system(command) != 0) + pg_fatal("RESTORE-COMMAND \"%s\" failed: %m", command); if (issegment) { diff --git a/contrib/pg_tde/t/wal_archiving.pl b/contrib/pg_tde/t/wal_archiving.pl index 9bd37904a0792..e3ac1ce4239ca 100644 --- a/contrib/pg_tde/t/wal_archiving.pl +++ b/contrib/pg_tde/t/wal_archiving.pl @@ -21,7 +21,8 @@ $primary->append_conf('postgresql.conf', "checkpoint_timeout = 1h"); $primary->append_conf('postgresql.conf', "archive_mode = on"); $primary->append_conf('postgresql.conf', - "archive_command = 'pg_tde_archive_decrypt %p cp %p $archive_dir/%f'"); + "archive_command = 'pg_tde_archive_decrypt %f %p \"cp %%p $archive_dir/%%f\"'" +); $primary->start; $primary->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); @@ -75,7 +76,8 @@ my $replica = PostgreSQL::Test::Cluster->new('replica'); $replica->init_from_backup($primary, 'backup'); $replica->append_conf('postgresql.conf', - "restore_command = 'pg_tde_restore_encrypt %f %p cp $archive_dir/%f %p'"); + "restore_command = 'pg_tde_restore_encrypt %f %p \"cp $archive_dir/%%f %%p\"'" +); $replica->append_conf('postgresql.conf', "recovery_target_action = promote"); $replica->set_recovery_mode; $replica->start; From 6ddc86c4e3967762df8a41a6a5998d5ad8b848f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 13 Aug 2025 11:13:53 +0200 Subject: [PATCH 583/796] Fix tabs in usage instructions These should be spaces inside the usage instruction string, not tabs. --- contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 91c9748180ed2..8decd4ab52dd9 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -118,7 +118,7 @@ usage(const char *progname) printf(_(" DEST-PATH path where the encrypted WAL segment should be written\n")); printf(_(" RESTORE-COMMAND restore command to wrap, %%p will be replaced with the path\n" " where it should write the unencrypted WAL segment, %%f with\n" - " the WAL segment's name\n")); + " the WAL segment's name\n")); printf(_("\n")); printf(_("Note that any %%f or %%p parameter in RESTORE-COMMAND will have to be escaped\n" "as %%%%f or %%%%p respectively if used as restore_command in postgresql.conf.\n" From c7e7dc52a7e6f62b4d7ecefe80ca1c4699eefd60 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Sun, 10 Aug 2025 22:06:27 +0100 Subject: [PATCH 584/796] Xlog encryption bugfix: offset calculation was off on TLI change The min/max comparisons of LSNs assumed that everyting is in the same timeline. In practice, with replication + recovery combinations, it is possible that keys span at least 3 timelines, which means that this has to be included in both combinations, as in other timelines, the restrictions are less strict. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 46 ++++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 7b79844276366..802dbe8901fc6 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -415,16 +415,54 @@ TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, if (wal_location_cmp(data_start, curr_key->end) < 0 && wal_location_cmp(data_end, curr_key->start) > 0) { char iv_prefix[16]; - off_t dec_off = XLogSegmentOffset(Max(data_start.lsn, curr_key->start.lsn), segSize); - off_t dec_end = XLogSegmentOffset(Min(data_end.lsn, curr_key->end.lsn), segSize); + + /* + * We want to calculate where to start / end encrypting. This + * depends on two factors: + * + * 1. Where does the key start / end + * + * 2. Where does the data start / end + * + * And this is complicated even more by the fact that keys can + * span multiple timelines: if a key starts at TLI 3 LSN 100, + * and ends at TLI 5 LSN 200 it means it is used for + * everything between two, including the entire TLI 4. For + * example, TLI 4 LSN 1 and TLI 4 LSN 400 are both encrypted + * with it, even through 1 is less than 100 and 400 is greater + * than 200. + * + * The below min/max calculations make sure that if the key + * and data are in the same timeline, we only encrypt/decrypt + * in the range of the current key - if the data is longer in + * some directions, we use multiple keys. But if the data + * starts/ends in a TLI "within" the key, we can safely + * decrypt/encrypt from the beginning / until the end, as it + * is part of the key. + */ + + + size_t end_lsn = + data_end.tli < curr_key->end.tli ? data_end.lsn : + Min(data_end.lsn, curr_key->end.lsn); + size_t start_lsn = + data_start.tli > curr_key->start.tli ? data_start.lsn : + Max(data_start.lsn, curr_key->start.lsn); + off_t dec_off = + XLogSegmentOffset(start_lsn, segSize); + off_t dec_end = + XLogSegmentOffset(end_lsn, segSize); size_t dec_sz; char *dec_buf = (char *) buf + (dec_off - offset); Assert(dec_off >= offset); - CalcXLogPageIVPrefix(tli, segno, curr_key->key.base_iv, iv_prefix); + CalcXLogPageIVPrefix(tli, segno, curr_key->key.base_iv, + iv_prefix); - /* We have reached the end of the segment */ + /* + * We have reached the end of the segment + */ if (dec_end == 0) { dec_end = offset + count; From 9dfed22f84b446499890b7077d4894fb45ce29e4 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Mon, 11 Aug 2025 19:52:52 +0100 Subject: [PATCH 585/796] PG-1604: Improve last key LSN calculation logic Previosly we simply set the LSN for the new key to the first write location. This is however not correct, as there are many corner cases around this: * recovery / replication might write old LSNs * we can't handle multiple keys with the same TLI/LSN, which can happen with quick restarts without writes To support this in this commit we modify the following: * We only activate new keys outside crash recovery, or immediately if encryption is turned off * We also take the already existing last key into account (if exists), and only activate a new key if we progressed past its start location The remaining changes are just support infrastructure for this: * Since we might rewrite old records, we use the already existing keys for those writes, not the active last keys * We prefetch existing keys during initialization, so it doesn't accidentally happen in the critical section during a write There is a remaining bug with stopping wal encryption, also mentioned in a TODO message in the code. This will be addressed in a later PR as this fix already took too long. --- contrib/pg_tde/meson.build | 2 + contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 103 ++- .../src/include/access/pg_tde_xlog_smgr.h | 2 +- contrib/pg_tde/t/2pc_replication.pl | 637 +++++++++++++++++ contrib/pg_tde/t/stream_rep.pl | 659 ++++++++++++++++++ src/bin/pg_basebackup/receivelog.c | 7 +- 6 files changed, 1395 insertions(+), 15 deletions(-) create mode 100644 contrib/pg_tde/t/2pc_replication.pl create mode 100644 contrib/pg_tde/t/stream_rep.pl diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 38ed4622671bc..e92891bb70ac5 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -128,6 +128,8 @@ tap_tests = [ 't/wal_archiving.pl', 't/wal_encrypt.pl', 't/wal_key_tli.pl', + 't/2pc_replication.pl', + 't/stream_rep.pl', ] tests += { diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 802dbe8901fc6..5b900e6cbe28b 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -226,6 +226,7 @@ void TDEXLogSmgrInitWrite(bool encrypt_xlog) { WalEncryptionKey *key = pg_tde_read_last_wal_key(); + WALKeyCacheRec *keys; /* * Always generate a new key on starting PostgreSQL to protect against @@ -246,6 +247,16 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); } + keys = pg_tde_get_wal_cache_keys(); + + if (keys == NULL) + { + WalLocation start = {.tli = 1,.lsn = 0}; + + /* cache is empty, prefetch keys from disk */ + pg_tde_fetch_wal_keys(start); + } + if (key) pfree(key); } @@ -263,6 +274,32 @@ TDEXLogSmgrInitWriteReuseKey() } } +/* + * Encrypt XLog page(s) from the buf and write to the segment file. + */ +static ssize_t +TDEXLogWriteEncryptedPagesOldKeys(int fd, const void *buf, size_t count, off_t offset, + TimeLineID tli, XLogSegNo segno, int segSize) +{ + char *enc_buff = EncryptionBuf; + +#ifndef FRONTEND + Assert(count <= TDEXLogEncryptBuffSize()); +#endif + + /* Copy the data as-is, as we might have unencrypted parts */ + memcpy(enc_buff, buf, count); + + /* + * This method potentially allocates, but only in very early execution + * Shouldn't happen in a write, where we are in a critical section + */ + TDEXLogCryptBuffer(buf, enc_buff, count, offset, tli, segno, segSize); + + return pg_pwrite(fd, enc_buff, count, offset); +} + + /* * Encrypt XLog page(s) from the buf and write to the segment file. */ @@ -284,6 +321,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #endif CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); + pg_tde_stream_crypt(iv_prefix, offset, (char *) buf, @@ -299,26 +337,64 @@ static ssize_t tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize) { + bool lastKeyUsable; + bool afterWriteKey; +#ifdef FRONTEND + bool crashRecovery = false; +#else + bool crashRecovery = GetRecoveryState() == RECOVERY_STATE_CRASH; +#endif + + WalLocation loc = {.tli = tli}; + WalLocation writeKeyLoc; + + XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); + /* * Set the last (most recent) key's start LSN if not set. * * This func called with WALWriteLock held, so no need in any extra sync. */ - if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && TDEXLogGetEncKeyLsn() == 0) - { - WalLocation loc = {.tli = tli}; - XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); + writeKeyLoc.lsn = TDEXLogGetEncKeyLsn(); + pg_read_barrier(); + writeKeyLoc.tli = TDEXLogGetEncKeyTli(); - pg_tde_wal_last_key_set_location(loc); - EncryptionKey.wal_start = loc; - TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); + lastKeyUsable = (writeKeyLoc.lsn != 0); + afterWriteKey = wal_location_cmp(writeKeyLoc, loc) <= 0; + + if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable) + { + WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); + + if (!crashRecovery || EncryptionKey.type == WAL_KEY_TYPE_UNENCRYPTED) + { + /* + * TODO: the unencrypted case is still not perfect, we need to + * report an error in some cornercases + */ + if (last_key == NULL || last_key->start.lsn < loc.lsn) + { + pg_tde_wal_last_key_set_location(loc); + EncryptionKey.wal_start = loc; + TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); + lastKeyUsable = true; + } + } } - if (EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) + if ((!afterWriteKey || !lastKeyUsable) && EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) + { + return TDEXLogWriteEncryptedPagesOldKeys(fd, buf, count, offset, tli, segno, segSize); + } + else if (EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) + { return TDEXLogWriteEncryptedPages(fd, buf, count, offset, tli, segno); + } else + { return pg_pwrite(fd, buf, count, offset); + } } /* @@ -340,7 +416,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, if (readsz <= 0) return readsz; - TDEXLogCryptBuffer(buf, count, offset, tli, segno, segSize); + TDEXLogCryptBuffer(buf, buf, count, offset, tli, segno, segSize); return readsz; } @@ -349,7 +425,7 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset, * [De]Crypt buffer if needed based on provided segment offset, number and TLI */ void -TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, +TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize) { WALKeyCacheRec *keys = pg_tde_get_wal_cache_keys(); @@ -357,7 +433,7 @@ TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, WalLocation data_end = {.tli = tli}; WalLocation data_start = {.tli = tli}; - if (!keys) + if (keys == NULL) { WalLocation start = {.tli = 1,.lsn = 0}; @@ -454,6 +530,7 @@ TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, XLogSegmentOffset(end_lsn, segSize); size_t dec_sz; char *dec_buf = (char *) buf + (dec_off - offset); + char *o_buf = (char *) out_buf + (dec_off - offset); Assert(dec_off >= offset); @@ -468,17 +545,19 @@ TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, dec_end = offset + count; } + Assert(dec_end > dec_off); dec_sz = dec_end - dec_off; #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %u_%X/%X", dec_off, dec_off - offset, dec_sz, curr_key->key.wal_start.tli, LSN_FORMAT_ARGS(curr_key->key.wal_start.lsn)); #endif + pg_tde_stream_crypt(iv_prefix, dec_off, dec_buf, dec_sz, - dec_buf, + o_buf, curr_key->key.key, &curr_key->crypt_ctx); } diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 8069db3c4c0ae..272880299f09a 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -13,7 +13,7 @@ extern void TDEXLogSmgrInit(void); extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); extern void TDEXLogSmgrInitWriteReuseKey(void); -extern void TDEXLogCryptBuffer(void *buf, size_t count, off_t offset, +extern void TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize); #endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/t/2pc_replication.pl b/contrib/pg_tde/t/2pc_replication.pl new file mode 100644 index 0000000000000..2d8286bf5cf58 --- /dev/null +++ b/contrib/pg_tde/t/2pc_replication.pl @@ -0,0 +1,637 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# Tests dedicated to two-phase commit in recovery +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $psql_out = ''; +my $psql_rc = ''; + +sub configure_and_reload +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $parameter) = @_; + my $name = $node->name; + + $node->append_conf( + 'postgresql.conf', qq( + $parameter + )); + $node->psql('postgres', "SELECT pg_reload_conf()", stdout => \$psql_out); + is($psql_out, 't', "reload node $name with $parameter"); + return; +} + +# Set up two nodes, which will alternately be primary and replication standby. + +# Setup london node +my $node_london = PostgreSQL::Test::Cluster->new("london"); +$node_london->init(allows_streaming => 1); +$node_london->append_conf( + 'postgresql.conf', qq( + max_prepared_transactions = 10 + log_checkpoints = true +)); +$node_london->append_conf('postgresql.conf', + "shared_preload_libraries = 'pg_tde'"); +$node_london->append_conf('postgresql.conf', + "default_table_access_method = 'tde_heap'"); +$node_london->start; + +# Create and enable tde extension +$node_london->safe_psql('postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +$node_london->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('global_key_provider', '/tmp/pg_global_keyring.file');" +); +$node_london->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('global_test_key', 'global_key_provider');" +); +$node_london->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('global_test_key', 'global_key_provider');" +); +$node_london->safe_psql('postgres', + "SELECT pg_tde_add_database_key_provider_file('local_key_provider', '/tmp/pg_local_keyring.file');" +); +$node_london->safe_psql('postgres', + "SELECT pg_tde_create_key_using_database_key_provider('local_test_key', 'local_key_provider');" +); +$node_london->safe_psql('postgres', + "SELECT pg_tde_set_key_using_database_key_provider('local_test_key', 'local_key_provider');" +); + +$node_london->append_conf( + 'postgresql.conf', qq( + pg_tde.wal_encrypt = on + log_min_messages = DEBUG2 + )); + +$node_london->restart; + +# if ($WAL_ENCRYPTION eq 'on'){ +# enable_wal_encryption($node_london); +# } + +# $node_london->safe_psql('postgres', 'ALTER SYSTEM SET pg_tde.wal_encrypt = on;'); +$node_london->restart; + +my $backup_dir = $node_london->backup_dir . '/london_backup'; +mkdir $backup_dir or die "mkdir($backup_dir) failed: $!"; +PostgreSQL::Test::RecursiveCopy::copypath($node_london->data_dir . '/pg_tde', + $backup_dir . '/pg_tde'); + +$node_london->backup('london_backup'); + +# Setup paris node +my $node_paris = PostgreSQL::Test::Cluster->new('paris'); +$node_paris->init_from_backup($node_london, 'london_backup', + has_streaming => 1); +$node_paris->append_conf( + 'postgresql.conf', qq( + subtransaction_buffers = 32 + pg_tde.wal_encrypt = on +)); +$node_paris->start; + +# Switch to synchronous replication in both directions +configure_and_reload($node_london, "synchronous_standby_names = 'paris'"); +configure_and_reload($node_paris, "synchronous_standby_names = 'london'"); + +# Set up nonce names for current primary and standby nodes +note "Initially, london is primary and paris is standby"; +my ($cur_primary, $cur_standby) = ($node_london, $node_paris); +my $cur_primary_name = $cur_primary->name; + +# Create table we'll use in the test transactions +$cur_primary->psql('postgres', "CREATE TABLE t_009_tbl (id int, msg text)"); + +############################################################################### +# Check that we can commit and abort transaction after soft restart. +# Here checkpoint happens before shutdown and no WAL replay will occur at next +# startup. In this case postgres re-creates shared-memory state from twophase +# files. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (1, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (2, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_1'; + BEGIN; + INSERT INTO t_009_tbl VALUES (3, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (4, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_2';"); +$cur_primary->stop; +$cur_primary->start; + +$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_1'"); +is($psql_rc, '0', 'Commit prepared transaction after restart'); + +$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_2'"); +is($psql_rc, '0', 'Rollback prepared transaction after restart'); + +is( $cur_primary->safe_psql( + 'postgres', "SELECT pg_tde_is_encrypted('t_009_tbl');"), + 't', + "Table t_009_tbl is encrypted on primary"); + +############################################################################### +# Check that we can commit and abort after a hard restart. +# At next startup, WAL replay will re-create shared memory state for prepared +# transaction using dedicated WAL records. +############################################################################### + +$cur_primary->psql( + 'postgres', " + CHECKPOINT; + BEGIN; + INSERT INTO t_009_tbl VALUES (5, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (6, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_3'; + BEGIN; + INSERT INTO t_009_tbl VALUES (7, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (8, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_4';"); +$cur_primary->teardown_node; +$cur_primary->start; + +$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_3'"); +is($psql_rc, '0', 'Commit prepared transaction after teardown'); + +$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_4'"); +is($psql_rc, '0', 'Rollback prepared transaction after teardown'); + +is( $cur_primary->safe_psql( + 'postgres', "SELECT pg_tde_is_encrypted('t_009_tbl');"), + 't', + "Table t_009_tbl is encrypted on primary"); + +############################################################################### +# Check that WAL replay can handle several transactions with same GID name. +############################################################################### + +$cur_primary->psql( + 'postgres', " + CHECKPOINT; + BEGIN; + INSERT INTO t_009_tbl VALUES (9, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (10, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_5'; + COMMIT PREPARED 'xact_009_5'; + BEGIN; + INSERT INTO t_009_tbl VALUES (11, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (12, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_5';"); +$cur_primary->teardown_node; +$cur_primary->start; + +$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_5'"); +is($psql_rc, '0', 'Replay several transactions with same GID'); + +is( $cur_primary->safe_psql( + 'postgres', "SELECT pg_tde_is_encrypted('t_009_tbl');"), + 't', + "Table t_009_tbl is encrypted on primary"); + +############################################################################### +# Check that WAL replay cleans up its shared memory state and releases locks +# while replaying transaction commits. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (13, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (14, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_6'; + COMMIT PREPARED 'xact_009_6';"); +$cur_primary->teardown_node; +$cur_primary->start; +$psql_rc = $cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (15, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (16, 'issued to ${cur_primary_name}'); + -- This prepare can fail due to conflicting GID or locks conflicts if + -- replay did not fully cleanup its state on previous commit. + PREPARE TRANSACTION 'xact_009_7';"); +is($psql_rc, '0', "Cleanup of shared memory state for 2PC commit"); + +$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_7'"); + +############################################################################### +# Check that WAL replay will cleanup its shared memory state on running standby. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (17, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (18, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_8'; + COMMIT PREPARED 'xact_009_8';"); +$cur_standby->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '0', + "Cleanup of shared memory state on running standby without checkpoint"); + +############################################################################### +# Same as in previous case, but let's force checkpoint on standby between +# prepare and commit to use on-disk twophase files. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (19, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (20, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_9';"); +$cur_standby->psql('postgres', "CHECKPOINT"); +$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_9'"); +$cur_standby->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '0', + "Cleanup of shared memory state on running standby after checkpoint"); + +############################################################################### +# Check that prepared transactions can be committed on promoted standby. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (21, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (22, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_10';"); +$cur_primary->teardown_node; +$cur_standby->promote; + +# change roles +note "Now paris is primary and london is standby"; +($cur_primary, $cur_standby) = ($node_paris, $node_london); +$cur_primary_name = $cur_primary->name; + +# because london is not running at this point, we can't use syncrep commit +# on this command +$psql_rc = $cur_primary->psql('postgres', + "SET synchronous_commit = off; COMMIT PREPARED 'xact_009_10'"); +is($psql_rc, '0', "Restore of prepared transaction on promoted standby"); + +# restart old primary as new standby +$cur_standby->enable_streaming($cur_primary); +$cur_standby->start; + +############################################################################### +# Check that prepared transactions are replayed after soft restart of standby +# while primary is down. Since standby knows that primary is down it uses a +# different code path on startup to ensure that the status of transactions is +# consistent. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (23, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (24, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_11';"); +$cur_primary->stop; +$cur_standby->restart; +$cur_standby->promote; + +# change roles +note "Now london is primary and paris is standby"; +($cur_primary, $cur_standby) = ($node_london, $node_paris); +$cur_primary_name = $cur_primary->name; + +$cur_primary->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '1', + "Restore prepared transactions from files with primary down"); + +# restart old primary as new standby +$cur_standby->enable_streaming($cur_primary); +$cur_standby->start; + +$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_11'"); + +############################################################################### +# Check that prepared transactions are correctly replayed after standby hard +# restart while primary is down. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + INSERT INTO t_009_tbl VALUES (25, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl VALUES (26, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_12'; + "); +$cur_primary->stop; +$cur_standby->teardown_node; +$cur_standby->start; +$cur_standby->promote; + +# change roles +note "Now paris is primary and london is standby"; +($cur_primary, $cur_standby) = ($node_paris, $node_london); +$cur_primary_name = $cur_primary->name; + +$cur_primary->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '1', + "Restore prepared transactions from records with primary down"); + +# restart old primary as new standby +$cur_standby->enable_streaming($cur_primary); +$cur_standby->start; + +$cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_12'"); + +############################################################################### +# Check visibility of prepared transactions in standby after a restart while +# primary is down. +############################################################################### + +$cur_primary->psql( + 'postgres', " + SET synchronous_commit='remote_apply'; -- To ensure the standby is caught up + CREATE TABLE t_009_tbl_standby_mvcc (id int, msg text); + BEGIN; + INSERT INTO t_009_tbl_standby_mvcc VALUES (1, 'issued to ${cur_primary_name}'); + SAVEPOINT s1; + INSERT INTO t_009_tbl_standby_mvcc VALUES (2, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_standby_mvcc'; + "); +$cur_primary->stop; +$cur_standby->restart; + +# Acquire a snapshot in standby, before we commit the prepared transaction +my $standby_session = + $cur_standby->background_psql('postgres', on_error_die => 1); +$standby_session->query_safe("BEGIN ISOLATION LEVEL REPEATABLE READ"); +$psql_out = + $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); +is($psql_out, '0', + "Prepared transaction not visible in standby before commit"); + +# Commit the transaction in primary +$cur_primary->start; +$cur_primary->psql( + 'postgres', " +SET synchronous_commit='remote_apply'; -- To ensure the standby is caught up +COMMIT PREPARED 'xact_009_standby_mvcc'; +"); + +# Still not visible to the old snapshot +$psql_out = + $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); +is($psql_out, '0', + "Committed prepared transaction not visible to old snapshot in standby"); + +# Is visible to a new snapshot +$standby_session->query_safe("COMMIT"); +$psql_out = + $standby_session->query_safe("SELECT count(*) FROM t_009_tbl_standby_mvcc"); +is($psql_out, '2', + "Committed prepared transaction is visible to new snapshot in standby"); +$standby_session->quit; + +############################################################################### +# Check for a lock conflict between prepared transaction with DDL inside and +# replay of XLOG_STANDBY_LOCK wal record. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + CREATE TABLE t_009_tbl2 (id int, msg text); + SAVEPOINT s1; + INSERT INTO t_009_tbl2 VALUES (27, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_13'; + -- checkpoint will issue XLOG_STANDBY_LOCK that can conflict with lock + -- held by 'create table' statement + CHECKPOINT; + COMMIT PREPARED 'xact_009_13';"); + +# Ensure that last transaction is replayed on standby. +my $cur_primary_lsn = + $cur_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn()"); +my $caughtup_query = + "SELECT '$cur_primary_lsn'::pg_lsn <= pg_last_wal_replay_lsn()"; +$cur_standby->poll_query_until('postgres', $caughtup_query) + or die "Timed out while waiting for standby to catch up"; + +$cur_standby->psql( + 'postgres', + "SELECT count(*) FROM t_009_tbl2", + stdout => \$psql_out); +is($psql_out, '1', "Replay prepared transaction with DDL"); + +############################################################################### +# Check recovery of prepared transaction with DDL inside after a hard restart +# of the primary. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + CREATE TABLE t_009_tbl3 (id int, msg text); + SAVEPOINT s1; + INSERT INTO t_009_tbl3 VALUES (28, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_14'; + BEGIN; + CREATE TABLE t_009_tbl4 (id int, msg text); + SAVEPOINT s1; + INSERT INTO t_009_tbl4 VALUES (29, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_15';"); + +$cur_primary->teardown_node; +$cur_primary->start; + +$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_14'"); +is($psql_rc, '0', 'Commit prepared transaction after teardown'); + +$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_15'"); +is($psql_rc, '0', 'Rollback prepared transaction after teardown'); + +############################################################################### +# Check recovery of prepared transaction with DDL inside after a soft restart +# of the primary. +############################################################################### + +$cur_primary->psql( + 'postgres', " + BEGIN; + CREATE TABLE t_009_tbl5 (id int, msg text); + SAVEPOINT s1; + INSERT INTO t_009_tbl5 VALUES (30, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_16'; + BEGIN; + CREATE TABLE t_009_tbl6 (id int, msg text); + SAVEPOINT s1; + INSERT INTO t_009_tbl6 VALUES (31, 'issued to ${cur_primary_name}'); + PREPARE TRANSACTION 'xact_009_17';"); + +$cur_primary->stop; +$cur_primary->start; + +$psql_rc = $cur_primary->psql('postgres', "COMMIT PREPARED 'xact_009_16'"); +is($psql_rc, '0', 'Commit prepared transaction after restart'); + +$psql_rc = $cur_primary->psql('postgres', "ROLLBACK PREPARED 'xact_009_17'"); +is($psql_rc, '0', 'Rollback prepared transaction after restart'); + +############################################################################### +# Verify expected data appears on both servers. +############################################################################### + +$cur_primary->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '0', "No uncommitted prepared transactions on primary"); + +$cur_primary->psql( + 'postgres', + "SELECT * FROM t_009_tbl ORDER BY id", + stdout => \$psql_out); +is( $psql_out, qq{1|issued to london +2|issued to london +5|issued to london +6|issued to london +9|issued to london +10|issued to london +11|issued to london +12|issued to london +13|issued to london +14|issued to london +15|issued to london +16|issued to london +17|issued to london +18|issued to london +19|issued to london +20|issued to london +21|issued to london +22|issued to london +23|issued to paris +24|issued to paris +25|issued to london +26|issued to london}, + "Check expected t_009_tbl data on primary"); + +$cur_primary->psql( + 'postgres', + "SELECT * FROM t_009_tbl2", + stdout => \$psql_out); +is( $psql_out, + qq{27|issued to paris}, + "Check expected t_009_tbl2 data on primary"); + +$cur_standby->psql( + 'postgres', + "SELECT count(*) FROM pg_prepared_xacts", + stdout => \$psql_out); +is($psql_out, '0', "No uncommitted prepared transactions on standby"); + +$cur_standby->psql( + 'postgres', + "SELECT * FROM t_009_tbl ORDER BY id", + stdout => \$psql_out); +is( $psql_out, qq{1|issued to london +2|issued to london +5|issued to london +6|issued to london +9|issued to london +10|issued to london +11|issued to london +12|issued to london +13|issued to london +14|issued to london +15|issued to london +16|issued to london +17|issued to london +18|issued to london +19|issued to london +20|issued to london +21|issued to london +22|issued to london +23|issued to paris +24|issued to paris +25|issued to london +26|issued to london}, + "Check expected t_009_tbl data on standby"); + +$cur_standby->psql( + 'postgres', + "SELECT * FROM t_009_tbl2", + stdout => \$psql_out); +is( $psql_out, + qq{27|issued to paris}, + "Check expected t_009_tbl2 data on standby"); + + +# Exercise the 2PC recovery code in StartupSUBTRANS, which is concerned with +# ensuring that enough pg_subtrans pages exist on disk to cover the range of +# prepared transactions at server start time. There's not much we can verify +# directly, but let's at least get the code to run. +$cur_standby->stop(); +configure_and_reload($cur_primary, "synchronous_standby_names = ''"); + +$cur_primary->safe_psql('postgres', "CHECKPOINT"); + +my $start_lsn = + $cur_primary->safe_psql('postgres', 'select pg_current_wal_insert_lsn()'); +$cur_primary->safe_psql('postgres', + "CREATE TABLE test(); BEGIN; CREATE TABLE test1(); PREPARE TRANSACTION 'foo';" +); +my $osubtrans = $cur_primary->safe_psql('postgres', + "select 'pg_subtrans/'||f, s.size from pg_ls_dir('pg_subtrans') f, pg_stat_file('pg_subtrans/'||f) s" +); +$cur_primary->pgbench( + '--no-vacuum --client=5 --transactions=1000', + 0, + [], + [], + 'pgbench run to cause pg_subtrans traffic', + { + '009_twophase.pgb' => 'insert into test default values' + }); +# StartupSUBTRANS is exercised with a wide range of visible XIDs in this +# stop/start sequence, because we left a prepared transaction open above. +# Also, setting subtransaction_buffers to 32 above causes to switch SLRU +# bank, for additional code coverage. +$cur_primary->stop; +$cur_primary->start; +my $nsubtrans = $cur_primary->safe_psql('postgres', + "select 'pg_subtrans/'||f, s.size from pg_ls_dir('pg_subtrans') f, pg_stat_file('pg_subtrans/'||f) s" +); +isnt($osubtrans, $nsubtrans, "contents of pg_subtrans/ have changed"); + +done_testing(); diff --git a/contrib/pg_tde/t/stream_rep.pl b/contrib/pg_tde/t/stream_rep.pl new file mode 100644 index 0000000000000..b58ce3c322194 --- /dev/null +++ b/contrib/pg_tde/t/stream_rep.pl @@ -0,0 +1,659 @@ +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# Minimal test testing streaming replication +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use lib 't'; +use pgtde; + +# Initialize primary node +my $node_primary = PostgreSQL::Test::Cluster->new('primary'); +# A specific role is created to perform some tests related to replication, +# and it needs proper authentication configuration. +$node_primary->init( + allows_streaming => 1, + auth_extra => [ '--create-role', 'repl_role' ]); +$node_primary->append_conf('postgresql.conf', + "shared_preload_libraries = 'pg_tde'"); +$node_primary->append_conf('postgresql.conf', + "default_table_access_method = 'tde_heap'"); +$node_primary->start; +my $backup_name = 'my_backup'; + +unlink('/tmp/global_keyring.file'); +unlink('/tmp/local_keyring.file'); +# Create and enable tde extension +$node_primary->safe_psql('postgres', + 'CREATE EXTENSION IF NOT EXISTS pg_tde;'); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('global_key_provider', '/tmp/global_keyring.file');" +); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('global_test_key_stream', 'global_key_provider');" +); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('global_test_key_stream', 'global_key_provider');" +); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_add_database_key_provider_file('local_key_provider', '/tmp/local_keyring.file');" +); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_create_key_using_database_key_provider('local_test_key_stream', 'local_key_provider');" +); +$node_primary->safe_psql('postgres', + "SELECT pg_tde_set_key_using_database_key_provider('local_test_key_stream', 'local_key_provider');" +); + +my $WAL_ENCRYPTION = $ENV{WAL_ENCRYPTION} // 'off'; + +if ($WAL_ENCRYPTION eq 'on') +{ + $node_primary->append_conf( + 'postgresql.conf', qq( + pg_tde.wal_encrypt = on + )); +} + +$node_primary->restart; + +# Take backup +PGTDE::backup($node_primary, $backup_name); + +# Create streaming standby linking to primary +my $node_standby_1 = PostgreSQL::Test::Cluster->new('standby_1'); +$node_standby_1->init_from_backup($node_primary, $backup_name, + has_streaming => 1); +$node_standby_1->start; + +# Take backup of standby 1 (not mandatory, but useful to check if +# pg_basebackup works on a standby). +PGTDE::backup($node_standby_1, $backup_name); + +# Take a second backup of the standby while the primary is offline. +$node_primary->stop; +PGTDE::backup($node_standby_1, 'my_backup_2'); +$node_primary->start; + +# Create second standby node linking to standby 1 +my $node_standby_2 = PostgreSQL::Test::Cluster->new('standby_2'); +$node_standby_2->init_from_backup($node_standby_1, $backup_name, + has_streaming => 1); +$node_standby_2->start; + +# Create some content on primary and check its presence in standby nodes +$node_primary->safe_psql('postgres', + "CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a"); + +$node_primary->safe_psql( + 'postgres', q{ +CREATE TABLE user_logins(id serial, who text); + +CREATE FUNCTION on_login_proc() RETURNS EVENT_TRIGGER AS $$ +BEGIN + IF NOT pg_is_in_recovery() THEN + INSERT INTO user_logins (who) VALUES (session_user); + END IF; + IF session_user = 'regress_hacker' THEN + RAISE EXCEPTION 'You are not welcome!'; + END IF; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +CREATE EVENT TRIGGER on_login_trigger ON login EXECUTE FUNCTION on_login_proc(); +ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS; +}); + +# Wait for standbys to catch up +$node_primary->wait_for_replay_catchup($node_standby_1); +$node_standby_1->wait_for_replay_catchup($node_standby_2, $node_primary); + +my $result = + $node_standby_1->safe_psql('postgres', "SELECT count(*) FROM tab_int"); +print "standby 1: $result\n"; +is($result, qq(1002), 'check streamed content on standby 1'); + +$result = + $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int"); +print "standby 2: $result\n"; +is($result, qq(1002), 'check streamed content on standby 2'); + +# Likewise, but for a sequence +$node_primary->safe_psql('postgres', + "CREATE SEQUENCE seq1; SELECT nextval('seq1')"); + +# Wait for standbys to catch up +$node_primary->wait_for_replay_catchup($node_standby_1); +$node_standby_1->wait_for_replay_catchup($node_standby_2, $node_primary); + +$result = $node_standby_1->safe_psql('postgres', "SELECT * FROM seq1"); +print "standby 1: $result\n"; +is($result, qq(33|0|t), 'check streamed sequence content on standby 1'); + +$result = $node_standby_2->safe_psql('postgres', "SELECT * FROM seq1"); +print "standby 2: $result\n"; +is($result, qq(33|0|t), 'check streamed sequence content on standby 2'); + +# Check pg_sequence_last_value() returns NULL for unlogged sequence on standby +$node_primary->safe_psql('postgres', + "CREATE UNLOGGED SEQUENCE ulseq; SELECT nextval('ulseq')"); +$node_primary->wait_for_replay_catchup($node_standby_1); +is( $node_standby_1->safe_psql( + 'postgres', + "SELECT pg_sequence_last_value('ulseq'::regclass) IS NULL"), + 't', + 'pg_sequence_last_value() on unlogged sequence on standby 1'); + +# Check that only READ-only queries can run on standbys +is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'), + 3, 'read-only queries on standby 1'); +is($node_standby_2->psql('postgres', 'INSERT INTO tab_int VALUES (1)'), + 3, 'read-only queries on standby 2'); + +# Tests for connection parameter target_session_attrs +note "testing connection parameter \"target_session_attrs\""; + +# Attempt to connect to $node1, then $node2, using target_session_attrs=$mode. +# Expect to connect to $target_node (undef for failure) with given $status. +sub test_target_session_attrs +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my $node1 = shift; + my $node2 = shift; + my $target_node = shift; + my $mode = shift; + my $status = shift; + + my $node1_host = $node1->host; + my $node1_port = $node1->port; + my $node1_name = $node1->name; + my $node2_host = $node2->host; + my $node2_port = $node2->port; + my $node2_name = $node2->name; + my $target_port = undef; + $target_port = $target_node->port if (defined $target_node); + my $target_name = undef; + $target_name = $target_node->name if (defined $target_node); + + # Build connection string for connection attempt. + my $connstr = "host=$node1_host,$node2_host "; + $connstr .= "port=$node1_port,$node2_port "; + $connstr .= "target_session_attrs=$mode"; + + # Attempt to connect, and if successful, get the server port number + # we connected to. Note we must pass the SQL command via the command + # line not stdin, else Perl may spit up trying to write to stdin of + # an already-failed psql process. + my ($ret, $stdout, $stderr) = + $node1->psql('postgres', undef, + extra_params => [ '-d', $connstr, '-c', 'SHOW port;' ]); + if ($status == 0) + { + is( $status == $ret && $stdout eq $target_port, + 1, + "connect to node $target_name with mode \"$mode\" and $node1_name,$node2_name listed" + ); + } + else + { + print "status = $status\n"; + print "ret = $ret\n"; + print "stdout = $stdout\n"; + print "stderr = $stderr\n"; + + is( $status == $ret && !defined $target_node, + 1, + "fail to connect with mode \"$mode\" and $node1_name,$node2_name listed" + ); + } + + return; +} + +# Connect to primary in "read-write" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_primary, + "read-write", 0); + +# Connect to primary in "read-write" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_primary, + "read-write", 0); + +# Connect to primary in "any" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_primary, + "any", 0); + +# Connect to standby1 in "any" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "any", 0); + +# Connect to primary in "primary" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_primary, + "primary", 0); + +# Connect to primary in "primary" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_primary, + "primary", 0); + +# Connect to standby1 in "read-only" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "read-only", 0); + +# Connect to standby1 in "read-only" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "read-only", 0); + +# Connect to primary in "prefer-standby" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, $node_primary, + "prefer-standby", 0); + +# Connect to standby1 in "prefer-standby" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "prefer-standby", 0); + +# Connect to standby1 in "prefer-standby" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "prefer-standby", 0); + +# Connect to standby1 in "standby" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "standby", 0); + +# Connect to standby1 in "standby" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "standby", 0); + +# Fail to connect in "read-write" mode with standby1,standby2 list. +test_target_session_attrs($node_standby_1, $node_standby_2, undef, + "read-write", 2); + +# Fail to connect in "primary" mode with standby1,standby2 list. +test_target_session_attrs($node_standby_1, $node_standby_2, undef, + "primary", 2); + +# Fail to connect in "read-only" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, undef, + "read-only", 2); + +# Fail to connect in "standby" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, undef, "standby", 2); + +# Test for SHOW commands using a WAL sender connection with a replication +# role. +note "testing SHOW commands for replication connection"; + +$node_primary->psql( + 'postgres', " +CREATE ROLE repl_role REPLICATION LOGIN; +GRANT pg_read_all_settings TO repl_role;"); +my $primary_host = $node_primary->host; +my $primary_port = $node_primary->port; +my $connstr_common = "host=$primary_host port=$primary_port user=repl_role"; +my $connstr_rep = "$connstr_common replication=1"; +my $connstr_db = "$connstr_common replication=database dbname=postgres"; + +# Test SHOW ALL +my ($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW ALL;', + on_error_die => 1, + extra_params => [ '-d', $connstr_rep ]); +ok($ret == 0, "SHOW ALL with replication role and physical replication"); +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW ALL;', + on_error_die => 1, + extra_params => [ '-d', $connstr_db ]); +ok($ret == 0, "SHOW ALL with replication role and logical replication"); + +# Test SHOW with a user-settable parameter +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW work_mem;', + on_error_die => 1, + extra_params => [ '-d', $connstr_rep ]); +ok( $ret == 0, + "SHOW with user-settable parameter, replication role and physical replication" +); +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW work_mem;', + on_error_die => 1, + extra_params => [ '-d', $connstr_db ]); +ok( $ret == 0, + "SHOW with user-settable parameter, replication role and logical replication" +); + +# Test SHOW with a superuser-settable parameter +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW primary_conninfo;', + on_error_die => 1, + extra_params => [ '-d', $connstr_rep ]); +ok( $ret == 0, + "SHOW with superuser-settable parameter, replication role and physical replication" +); +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', 'SHOW primary_conninfo;', + on_error_die => 1, + extra_params => [ '-d', $connstr_db ]); +ok( $ret == 0, + "SHOW with superuser-settable parameter, replication role and logical replication" +); + +note "testing READ_REPLICATION_SLOT command for replication connection"; + +my $slotname = 'test_read_replication_slot_physical'; + +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', + 'READ_REPLICATION_SLOT non_existent_slot;', + extra_params => [ '-d', $connstr_rep ]); +ok($ret == 0, "READ_REPLICATION_SLOT exit code 0 on success"); +like($stdout, qr/^\|\|$/, + "READ_REPLICATION_SLOT returns NULL values if slot does not exist"); + +$node_primary->psql( + 'postgres', + "CREATE_REPLICATION_SLOT $slotname PHYSICAL RESERVE_WAL;", + extra_params => [ '-d', $connstr_rep ]); + +($ret, $stdout, $stderr) = $node_primary->psql( + 'postgres', + "READ_REPLICATION_SLOT $slotname;", + extra_params => [ '-d', $connstr_rep ]); +ok($ret == 0, "READ_REPLICATION_SLOT success with existing slot"); +like($stdout, qr/^physical\|[^|]*\|1$/, + "READ_REPLICATION_SLOT returns tuple with slot information"); + +$node_primary->psql( + 'postgres', + "DROP_REPLICATION_SLOT $slotname;", + extra_params => [ '-d', $connstr_rep ]); + +note "switching to physical replication slot"; + +# Switch to using a physical replication slot. We can do this without a new +# backup since physical slots can go backwards if needed. Do so on both +# standbys. Since we're going to be testing things that affect the slot state, +# also increase the standby feedback interval to ensure timely updates. +my ($slotname_1, $slotname_2) = ('standby_1', 'standby_2'); +$node_primary->append_conf('postgresql.conf', "max_replication_slots = 4"); +$node_primary->restart; +is( $node_primary->psql( + 'postgres', + qq[SELECT pg_create_physical_replication_slot('$slotname_1');]), + 0, + 'physical slot created on primary'); +$node_standby_1->append_conf('postgresql.conf', + "primary_slot_name = $slotname_1"); +$node_standby_1->append_conf('postgresql.conf', + "wal_receiver_status_interval = 1"); +$node_standby_1->append_conf('postgresql.conf', "max_replication_slots = 4"); +$node_standby_1->restart; +is( $node_standby_1->psql( + 'postgres', + qq[SELECT pg_create_physical_replication_slot('$slotname_2');]), + 0, + 'physical slot created on intermediate replica'); +$node_standby_2->append_conf('postgresql.conf', + "primary_slot_name = $slotname_2"); +$node_standby_2->append_conf('postgresql.conf', + "wal_receiver_status_interval = 1"); +# should be able change primary_slot_name without restart +# will wait effect in get_slot_xmins above +$node_standby_2->reload; + +# Fetch xmin columns from slot's pg_replication_slots row, after waiting for +# given boolean condition to be true to ensure we've reached a quiescent state +sub get_slot_xmins +{ + my ($node, $slotname, $check_expr) = @_; + + $node->poll_query_until( + 'postgres', qq[ + SELECT $check_expr + FROM pg_catalog.pg_replication_slots + WHERE slot_name = '$slotname'; + ]) or die "Timed out waiting for slot xmins to advance"; + + my $slotinfo = $node->slot($slotname); + return ($slotinfo->{'xmin'}, $slotinfo->{'catalog_xmin'}); +} + +# There's no hot standby feedback and there are no logical slots on either peer +# so xmin and catalog_xmin should be null on both slots. +my ($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1, + "xmin IS NULL AND catalog_xmin IS NULL"); +is($xmin, '', 'xmin of non-cascaded slot null with no hs_feedback'); +is($catalog_xmin, '', + 'catalog xmin of non-cascaded slot null with no hs_feedback'); + +($xmin, $catalog_xmin) = get_slot_xmins($node_standby_1, $slotname_2, + "xmin IS NULL AND catalog_xmin IS NULL"); +is($xmin, '', 'xmin of cascaded slot null with no hs_feedback'); +is($catalog_xmin, '', + 'catalog xmin of cascaded slot null with no hs_feedback'); + +# Replication still works? +$node_primary->safe_psql('postgres', 'CREATE TABLE replayed(val integer);'); + +sub replay_check +{ + my $newval = $node_primary->safe_psql('postgres', + 'INSERT INTO replayed(val) SELECT coalesce(max(val),0) + 1 AS newval FROM replayed RETURNING val' + ); + $node_primary->wait_for_replay_catchup($node_standby_1); + $node_standby_1->wait_for_replay_catchup($node_standby_2, $node_primary); + + $node_standby_1->safe_psql('postgres', + qq[SELECT 1 FROM replayed WHERE val = $newval]) + or die "standby_1 didn't replay primary value $newval"; + $node_standby_2->safe_psql('postgres', + qq[SELECT 1 FROM replayed WHERE val = $newval]) + or die "standby_2 didn't replay standby_1 value $newval"; + return; +} + +replay_check(); + +my $evttrig = $node_standby_1->safe_psql('postgres', + "SELECT evtname FROM pg_event_trigger WHERE evtevent = 'login'"); +is($evttrig, 'on_login_trigger', 'Name of login trigger'); +$evttrig = $node_standby_2->safe_psql('postgres', + "SELECT evtname FROM pg_event_trigger WHERE evtevent = 'login'"); +is($evttrig, 'on_login_trigger', 'Name of login trigger'); + +note "enabling hot_standby_feedback"; + +# Enable hs_feedback. The slot should gain an xmin. We set the status interval +# so we'll see the results promptly. +$node_standby_1->safe_psql('postgres', + 'ALTER SYSTEM SET hot_standby_feedback = on;'); +$node_standby_1->reload; +$node_standby_2->safe_psql('postgres', + 'ALTER SYSTEM SET hot_standby_feedback = on;'); +$node_standby_2->reload; +replay_check(); + +($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1, + "xmin IS NOT NULL AND catalog_xmin IS NULL"); +isnt($xmin, '', 'xmin of non-cascaded slot non-null with hs feedback'); +is($catalog_xmin, '', + 'catalog xmin of non-cascaded slot still null with hs_feedback'); + +my ($xmin1, $catalog_xmin1) = get_slot_xmins($node_standby_1, $slotname_2, + "xmin IS NOT NULL AND catalog_xmin IS NULL"); +isnt($xmin1, '', 'xmin of cascaded slot non-null with hs feedback'); +is($catalog_xmin1, '', + 'catalog xmin of cascaded slot still null with hs_feedback'); + +note "doing some work to advance xmin"; +$node_primary->safe_psql( + 'postgres', q{ +do $$ +begin + for i in 10000..11000 loop + -- use an exception block so that each iteration eats an XID + begin + insert into tab_int values (i); + exception + when division_by_zero then null; + end; + end loop; +end$$; +}); + +$node_primary->safe_psql('postgres', 'VACUUM;'); +$node_primary->safe_psql('postgres', 'CHECKPOINT;'); + +my ($xmin2, $catalog_xmin2) = + get_slot_xmins($node_primary, $slotname_1, "xmin <> '$xmin'"); +note "primary slot's new xmin $xmin2, old xmin $xmin"; +isnt($xmin2, $xmin, 'xmin of non-cascaded slot with hs feedback has changed'); +is($catalog_xmin2, '', + 'catalog xmin of non-cascaded slot still null with hs_feedback unchanged' +); + +($xmin2, $catalog_xmin2) = + get_slot_xmins($node_standby_1, $slotname_2, "xmin <> '$xmin1'"); +note "standby_1 slot's new xmin $xmin2, old xmin $xmin1"; +isnt($xmin2, $xmin1, 'xmin of cascaded slot with hs feedback has changed'); +is($catalog_xmin2, '', + 'catalog xmin of cascaded slot still null with hs_feedback unchanged'); + +note "disabling hot_standby_feedback"; + +# Disable hs_feedback. Xmin should be cleared. +$node_standby_1->safe_psql('postgres', + 'ALTER SYSTEM SET hot_standby_feedback = off;'); +$node_standby_1->reload; +$node_standby_2->safe_psql('postgres', + 'ALTER SYSTEM SET hot_standby_feedback = off;'); +$node_standby_2->reload; +replay_check(); + +($xmin, $catalog_xmin) = get_slot_xmins($node_primary, $slotname_1, + "xmin IS NULL AND catalog_xmin IS NULL"); +is($xmin, '', 'xmin of non-cascaded slot null with hs feedback reset'); +is($catalog_xmin, '', + 'catalog xmin of non-cascaded slot still null with hs_feedback reset'); + +($xmin, $catalog_xmin) = get_slot_xmins($node_standby_1, $slotname_2, + "xmin IS NULL AND catalog_xmin IS NULL"); +is($xmin, '', 'xmin of cascaded slot null with hs feedback reset'); +is($catalog_xmin, '', + 'catalog xmin of cascaded slot still null with hs_feedback reset'); + +note "check change primary_conninfo without restart"; +$node_standby_2->append_conf('postgresql.conf', "primary_slot_name = ''"); +$node_standby_2->enable_streaming($node_primary); +$node_standby_2->reload; + +# be sure do not streaming from cascade +$node_standby_1->stop; + +my $newval = $node_primary->safe_psql('postgres', + 'INSERT INTO replayed(val) SELECT coalesce(max(val),0) + 1 AS newval FROM replayed RETURNING val' +); +$node_primary->wait_for_catchup($node_standby_2); +my $is_replayed = $node_standby_2->safe_psql('postgres', + qq[SELECT 1 FROM replayed WHERE val = $newval]); +is($is_replayed, qq(1), "standby_2 didn't replay primary value $newval"); + +# Drop any existing slots on the primary, for the follow-up tests. +$node_primary->safe_psql('postgres', + "SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots;"); + +# Test physical slot advancing and its durability. Create a new slot on +# the primary, not used by any of the standbys. This reserves WAL at creation. +my $phys_slot = 'phys_slot'; +$node_primary->safe_psql('postgres', + "SELECT pg_create_physical_replication_slot('$phys_slot', true);"); +# Generate some WAL, and switch to a new segment, used to check that +# the previous segment is correctly getting recycled as the slot advancing +# would recompute the minimum LSN calculated across all slots. +my $segment_removed = $node_primary->safe_psql('postgres', + 'SELECT pg_walfile_name(pg_current_wal_lsn())'); +chomp($segment_removed); +$node_primary->advance_wal(1); +my $current_lsn = + $node_primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();"); +chomp($current_lsn); +my $psql_rc = $node_primary->psql('postgres', + "SELECT pg_replication_slot_advance('$phys_slot', '$current_lsn'::pg_lsn);" +); +is($psql_rc, '0', 'slot advancing with physical slot'); +my $phys_restart_lsn_pre = $node_primary->safe_psql('postgres', + "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$phys_slot';" +); +chomp($phys_restart_lsn_pre); +# Slot advance should persist across clean restarts. +$node_primary->restart; +my $phys_restart_lsn_post = $node_primary->safe_psql('postgres', + "SELECT restart_lsn from pg_replication_slots WHERE slot_name = '$phys_slot';" +); +chomp($phys_restart_lsn_post); +ok( ($phys_restart_lsn_pre cmp $phys_restart_lsn_post) == 0, + "physical slot advance persists across restarts"); + +# Check if the previous segment gets correctly recycled after the +# server stopped cleanly, causing a shutdown checkpoint to be generated. +my $primary_data = $node_primary->data_dir; +ok(!-f "$primary_data/pg_wal/$segment_removed", + "WAL segment $segment_removed recycled after physical slot advancing"); + +note "testing pg_backup_start() followed by BASE_BACKUP"; +my $connstr = $node_primary->connstr('postgres') . " replication=database"; + +# This test requires a replication connection with a database, as it mixes +# a replication command and a SQL command. +$node_primary->command_fails_like( + [ + 'psql', '-X', '-c', "SELECT pg_backup_start('backup', true)", + '-c', 'BASE_BACKUP', '-d', $connstr + ], + qr/a backup is already in progress in this session/, + 'BASE_BACKUP cannot run in session already running backup'); + +note "testing BASE_BACKUP cancellation"; + +my $sigchld_bb_timeout = + IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default); + +# This test requires a replication connection with a database, as it mixes +# a replication command and a SQL command. The first BASE_BACKUP is throttled +# to give enough room for the cancellation running below. The second command +# for pg_backup_stop() should fail. +my ($sigchld_bb_stdin, $sigchld_bb_stdout, $sigchld_bb_stderr) = ('', '', ''); +my $sigchld_bb = IPC::Run::start( + [ + 'psql', '-X', '-c', "BASE_BACKUP (CHECKPOINT 'fast', MAX_RATE 32);", + '-c', 'SELECT pg_backup_stop()', + '-d', $connstr + ], + '<', + \$sigchld_bb_stdin, + '>', + \$sigchld_bb_stdout, + '2>', + \$sigchld_bb_stderr, + $sigchld_bb_timeout); + +# The cancellation is issued once the database files are streamed and +# the checkpoint issued at backup start completes. +is( $node_primary->poll_query_until( + 'postgres', + "SELECT pg_cancel_backend(a.pid) FROM " + . "pg_stat_activity a, pg_stat_progress_basebackup b WHERE " + . "a.pid = b.pid AND a.query ~ 'BASE_BACKUP' AND " + . "b.phase = 'streaming database files';"), + "1", + "WAL sender sending base backup killed"); + +# The psql command should fail on pg_backup_stop(). +ok( pump_until( + $sigchld_bb, $sigchld_bb_timeout, + \$sigchld_bb_stderr, qr/backup is not in progress/), + 'base backup cleanly canceled'); +$sigchld_bb->finish(); + +done_testing(); + diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c index 2c4ce11a50a6c..6664cd14d0109 100644 --- a/src/bin/pg_basebackup/receivelog.c +++ b/src/bin/pg_basebackup/receivelog.c @@ -1131,8 +1131,11 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len, } #ifdef PERCONA_EXT - TDEXLogCryptBuffer(copybuf + hdr_len + bytes_written, bytes_to_write, - xlogoff, stream->timeline, segno, WalSegSz); + { + void* enc_buf = copybuf + hdr_len + bytes_written; + TDEXLogCryptBuffer(enc_buf, enc_buf, bytes_to_write, + xlogoff, stream->timeline, segno, WalSegSz); + } #endif if (stream->walmethod->ops->write(walfile, From f96ade0f2d4bf17c960462b8bb528ef4778b8169 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 14 Aug 2025 18:40:57 +0200 Subject: [PATCH 586/796] PG-1605 Fix encryption with old keys with disabled WAL encryption To not break recovery when we replay encrypted WAL but WAL encryption is disabled the simplest way is to treat disabled WAL encryption just like enabled WAL encryption. The issue is not big in practice since it should only hit users who disable WAL encryption and then crash the database but treating both cases the same way makes the code simple to understand. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 23 +++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 5b900e6cbe28b..2b25bcb71356c 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -23,7 +23,6 @@ #ifdef FRONTEND #include "pg_tde_fe.h" #else -#include "pg_tde_guc.h" #include "port/atomics.h" #endif @@ -138,11 +137,8 @@ TDEXLogEncryptStateSize(void) Size sz; sz = sizeof(EncryptionStateData); - if (EncryptXLog) - { - sz = add_size(sz, TDEXLogEncryptBuffSize()); - sz = add_size(sz, PG_IO_ALIGN_SIZE); - } + sz = add_size(sz, TDEXLogEncryptBuffSize()); + sz = add_size(sz, PG_IO_ALIGN_SIZE); return sz; } @@ -169,12 +165,9 @@ TDEXLogShmemInit(void) memset(EncryptionState, 0, sizeof(EncryptionStateData)); - if (EncryptXLog) - { - EncryptionBuf = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, ((char *) EncryptionState) + sizeof(EncryptionStateData)); + EncryptionBuf = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, ((char *) EncryptionState) + sizeof(EncryptionStateData)); - Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionBuf + TDEXLogEncryptBuffSize()); - } + Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionBuf + TDEXLogEncryptBuffSize()); pg_atomic_init_u64(&EncryptionState->enc_key_lsn, 0); @@ -367,12 +360,8 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); - if (!crashRecovery || EncryptionKey.type == WAL_KEY_TYPE_UNENCRYPTED) + if (!crashRecovery) { - /* - * TODO: the unencrypted case is still not perfect, we need to - * report an error in some cornercases - */ if (last_key == NULL || last_key->start.lsn < loc.lsn) { pg_tde_wal_last_key_set_location(loc); @@ -383,7 +372,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, } } - if ((!afterWriteKey || !lastKeyUsable) && EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) + if ((!afterWriteKey || !lastKeyUsable) && EncryptionKey.type != WAL_KEY_TYPE_INVALID) { return TDEXLogWriteEncryptedPagesOldKeys(fd, buf, count, offset, tli, segno, segSize); } From 167aef2ba2c39e6175f6ea86c97e3f50a3dfa233 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Thu, 14 Aug 2025 18:49:50 +0200 Subject: [PATCH 587/796] PG-1605 Fix issue with test which crashes when re-run We make sure to delete the keyring files before running the test. --- contrib/pg_tde/t/2pc_replication.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/pg_tde/t/2pc_replication.pl b/contrib/pg_tde/t/2pc_replication.pl index 2d8286bf5cf58..aaaee5ad2f71d 100644 --- a/contrib/pg_tde/t/2pc_replication.pl +++ b/contrib/pg_tde/t/2pc_replication.pl @@ -28,6 +28,9 @@ sub configure_and_reload return; } +unlink('/tmp/pg_global_keyring.file'); +unlink('/tmp/pg_local_keyring.file'); + # Set up two nodes, which will alternately be primary and replication standby. # Setup london node From ff8a389bfdb32ec4451c4be2f5a1431818cbdb5f Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Mon, 18 Aug 2025 08:11:49 +0100 Subject: [PATCH 588/796] PG-1604 fix: preallocate one more record for the cache There is at lesat one corner case scenario where we have to load the last record into the cache during a write: * replica crashes, receives last segment from primary * replica replays last segment, reaches end * replica activtes new key * replica replays prepared transaction, has to use old keys again * old key write function sees that we generated a new key, tries to load it In this scenario we could get away by detecting that we are in a write, and asserting if we tried to use the last key. But in a release build assertions are not fired, and we would end up writing some non encrypted data to disk, and later if we have to run recovery failing. It could be a FATAL, but that would still crash the server, and the next startup would crash again and again... Instead, to properly avoid this situation we preallocate memory for one more key in the cache during initialization. Since we can only add one extra key to the cache during the servers run, this means we no longer try to allocate in the critical section in any corner case. While this is not the nicest solution, it is simple and keeps the current cache and decrypt/encrypt logic the same as before. Any other solution would be more complex, and even more of a hack, as it would require dealing with a possibly out of date cache. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 38 ++++++++++++++++++- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 11 +++--- .../src/include/access/pg_tde_xlog_keys.h | 1 + 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 460fced80269d..9486dfe4ec99b 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -16,6 +16,7 @@ #include "common/pg_tde_utils.h" #include "encryption/enc_aes.h" #include "encryption/enc_tde.h" +#include "utils/palloc.h" #ifdef FRONTEND #include "pg_tde_fe.h" @@ -43,7 +44,9 @@ typedef struct WalKeyFileEntry } WalKeyFileEntry; static WALKeyCacheRec *tde_wal_key_cache = NULL; +static WALKeyCacheRec *tde_wal_prealloc_record = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; +static WalEncryptionKey *tde_wal_prealloc_key = NULL; static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key); static WalEncryptionKey *pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); @@ -326,6 +329,34 @@ pg_tde_fetch_wal_keys(WalLocation start) return return_wal_rec; } +/* + * In special cases, we have to add one more record to the WAL key cache during write (in the critical section, when we can't allocate). + * This method is a helper to deal with that: when adding a single key, we potentially allocate 2 records. + * These variables help preallocate them, so in the critical section we can just use the already allocated objects, and update the cache with them. + * + * While this is somewhat a hack, it is also simple, safe, reliable, and way easier to implement than to refactor the cache or the decryption/encryption loop. + */ +void +pg_tde_wal_cache_extra_palloc(void) +{ +#ifndef FRONTEND + MemoryContext oldCtx; + + oldCtx = MemoryContextSwitchTo(TopMemoryContext); +#endif + if (tde_wal_prealloc_record == NULL) + { + tde_wal_prealloc_record = palloc0_object(WALKeyCacheRec); + } + if (tde_wal_prealloc_key == NULL) + { + tde_wal_prealloc_key = palloc0_object(WalEncryptionKey); + } +#ifndef FRONTEND + MemoryContextSwitchTo(oldCtx); +#endif +} + static WALKeyCacheRec * pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) { @@ -335,7 +366,8 @@ pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) oldCtx = MemoryContextSwitchTo(TopMemoryContext); #endif - wal_rec = palloc0_object(WALKeyCacheRec); + wal_rec = tde_wal_prealloc_record == NULL ? palloc0_object(WALKeyCacheRec) : tde_wal_prealloc_record; + tde_wal_prealloc_record = NULL; #ifndef FRONTEND MemoryContextSwitchTo(oldCtx); #endif @@ -553,7 +585,9 @@ pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, static WalEncryptionKey * pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) { - WalEncryptionKey *key = palloc_object(WalEncryptionKey); + WalEncryptionKey *key = tde_wal_prealloc_key == NULL ? palloc_object(WalEncryptionKey) : tde_wal_prealloc_key; + + tde_wal_prealloc_key = NULL; Assert(principal_key); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 2b25bcb71356c..abf86c1ccba17 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -248,6 +248,7 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) /* cache is empty, prefetch keys from disk */ pg_tde_fetch_wal_keys(start); + pg_tde_wal_cache_extra_palloc(); } if (key) @@ -284,8 +285,8 @@ TDEXLogWriteEncryptedPagesOldKeys(int fd, const void *buf, size_t count, off_t o memcpy(enc_buff, buf, count); /* - * This method potentially allocates, but only in very early execution - * Shouldn't happen in a write, where we are in a critical section + * This method potentially allocates, but only in very early execution Can + * happen during a write, but we have one more cache entry preallocated. */ TDEXLogCryptBuffer(buf, enc_buff, count, offset, tli, segno, segSize); @@ -356,7 +357,7 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, lastKeyUsable = (writeKeyLoc.lsn != 0); afterWriteKey = wal_location_cmp(writeKeyLoc, loc) <= 0; - if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable) + if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable && afterWriteKey) { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); @@ -442,10 +443,8 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); WalLocation write_loc = {.tli = TDEXLogGetEncKeyTli(),.lsn = write_key_lsn}; - Assert(last_key); - /* write has generated a new key, need to fetch it */ - if (wal_location_cmp(last_key->start, write_loc) < 0) + if (last_key != NULL && wal_location_cmp(last_key->start, write_loc) < 0) { pg_tde_fetch_wal_keys(write_loc); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index e36e7e59a85b7..ca00cb45a036f 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -82,5 +82,6 @@ extern WalEncryptionKey *pg_tde_read_last_wal_key(void); extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); extern void pg_tde_wal_last_key_set_location(WalLocation loc); +extern void pg_tde_wal_cache_extra_palloc(void); #endif /* PG_TDE_XLOG_KEYS_H */ From b709662c25e13896387d5e7966119e425d4808ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 18 Aug 2025 17:48:46 +0200 Subject: [PATCH 589/796] Hold required lock when initializing shmem According to the documentation, each backend is supposed to hold AddinShmemInitLock when calling ShmemInitStruct. We only did that for half of our calls before this patch. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 2 ++ contrib/pg_tde/src/catalog/tde_principal_key.c | 4 +--- contrib/pg_tde/src/pg_tde.c | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index abf86c1ccba17..01b368d55e0d5 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -158,6 +158,8 @@ TDEXLogShmemInit(void) { bool foundBuf; + Assert(LWLockHeldByMeInMode(AddinShmemInitLock, LW_EXCLUSIVE)); + EncryptionState = (EncryptionStateData *) ShmemInitStruct("TDE XLog Encryption State", TDEXLogEncryptStateSize(), diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index e44cabfea44ca..abb160f47e209 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -127,7 +127,7 @@ PrincipalKeyShmemInit(void) char *free_start; Size required_shmem_size = PrincipalKeyShmemSize(); - LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + Assert(LWLockHeldByMeInMode(AddinShmemInitLock, LW_EXCLUSIVE)); /* Create or attach to the shared memory state */ ereport(NOTICE, errmsg("PrincipalKeyShmemInit: requested %ld bytes", required_shmem_size)); @@ -175,8 +175,6 @@ PrincipalKeyShmemInit(void) dshash_detach(dsh); } - - LWLockRelease(AddinShmemInitLock); } /* diff --git a/contrib/pg_tde/src/pg_tde.c b/contrib/pg_tde/src/pg_tde.c index 977ba0ccd35c8..e6f32f099bbe0 100644 --- a/contrib/pg_tde/src/pg_tde.c +++ b/contrib/pg_tde/src/pg_tde.c @@ -64,11 +64,15 @@ tde_shmem_startup(void) if (prev_shmem_startup_hook) prev_shmem_startup_hook(); + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + KeyProviderShmemInit(); PrincipalKeyShmemInit(); TDEXLogShmemInit(); TDEXLogSmgrInit(); TDEXLogSmgrInitWrite(EncryptXLog); + + LWLockRelease(AddinShmemInitLock); } void From dbaeda163dd53382c5ffa7b8d716b21d6d7cb0bf Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 18 Aug 2025 16:09:54 +0300 Subject: [PATCH 590/796] Fix leaked var in tde archiver tools aka make sanitizers happy --- contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c | 2 ++ contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 4dca62c84fcc6..3afe254b491f9 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -212,6 +212,8 @@ main(int argc, char *argv[]) if (system(command) != 0) pg_fatal("ARCHIVE-COMMAND \"%s\" failed: %m", command); + free(command); + if (issegment) { if (unlink(tmppath) < 0) diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 8decd4ab52dd9..a02519c787b6f 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -205,6 +205,8 @@ main(int argc, char *argv[]) if (system(command) != 0) pg_fatal("RESTORE-COMMAND \"%s\" failed: %m", command); + free(command); + if (issegment) { write_encrypted_segment(targetpath, sourcename, tmppath); From 621c3f8d3dfaadac261d90b3a8cb592b6d2b6f3e Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 18 Aug 2025 19:58:17 +0300 Subject: [PATCH 591/796] Suppress LSAN complaints on pgbench --- ci_scripts/suppressions/lsan.supp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci_scripts/suppressions/lsan.supp b/ci_scripts/suppressions/lsan.supp index 3b7d93af62dda..0ec6d73cbca46 100644 --- a/ci_scripts/suppressions/lsan.supp +++ b/ci_scripts/suppressions/lsan.supp @@ -59,3 +59,8 @@ leak:GenerateRecoveryConfig # Returns strdup'd string which never gets freed in frontend tools. Trying to # free it leads to comiler complains that it discards 'const' qualifier. leak:get_progname + +# A buch of not freed allocations in putVariableInt right before exit(1). And +# malloced `threads` ("Thread state") never gets released (allocated in main(), +# once, and not in loop). +leak:bin/pgbench/pgbench.c From aed49c084785dc647fcc40cb89bac13d633fdb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 18 Aug 2025 18:23:25 +0200 Subject: [PATCH 592/796] PG-1866 Reset WAL key cache on shmem init It seems like there are cases when the postmaster have "restarted" after a backend crash where the wal cache inherited from the postmaster is wrong. I'm not at all sure exactly how and why this happens, but this patch fixes a bug with this and allows recovery/013_crash_restart to pass with WAL encryption enabled. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 17 +++++++++++++++++ contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 11 ++++++++++- .../src/include/access/pg_tde_xlog_keys.h | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 9486dfe4ec99b..0d785e2d13bb8 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -72,6 +72,23 @@ get_wal_key_file_path(void) return wal_key_file_path; } +void +pg_tde_free_wal_key_cache(void) +{ + WALKeyCacheRec *rec = tde_wal_key_cache; + + while (rec != NULL) + { + WALKeyCacheRec *next = rec->next; + + pfree(rec); + rec = next; + } + + tde_wal_key_cache = NULL; + tde_wal_key_last_rec = NULL; +} + void pg_tde_wal_last_key_set_location(WalLocation loc) { diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 01b368d55e0d5..5e71b277a9264 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -220,9 +220,18 @@ TDEXLogSmgrInit() void TDEXLogSmgrInitWrite(bool encrypt_xlog) { - WalEncryptionKey *key = pg_tde_read_last_wal_key(); + WalEncryptionKey *key; WALKeyCacheRec *keys; + /* + * If the postmaster have done a "soft" restart after a backend crash, we + * may have inherited the cache in a weird state. Clearing the cache here + * ensures we reinitialize all keys from disk. + */ + pg_tde_free_wal_key_cache(); + + key = pg_tde_read_last_wal_key(); + /* * Always generate a new key on starting PostgreSQL to protect against * attacks on CTR ciphers based on comparing the WAL generated by two diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index ca00cb45a036f..0ee39585339ac 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -74,6 +74,7 @@ extern int pg_tde_count_wal_keys_in_file(void); extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry_type); extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); +extern void pg_tde_free_wal_key_cache(void); extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(void); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); From 1338ceb1377b38170baee6bffae72d2fdb87b991 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 19 Aug 2025 11:24:50 +0200 Subject: [PATCH 593/796] Do not try to fetch the last key when we do not have to This is likely a leftover from when the logic for unencrypted keys was different from the one for real encryption keys. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 5e71b277a9264..78c830e002216 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -368,19 +368,16 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, lastKeyUsable = (writeKeyLoc.lsn != 0); afterWriteKey = wal_location_cmp(writeKeyLoc, loc) <= 0; - if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable && afterWriteKey) + if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable && afterWriteKey && !crashRecovery) { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); - if (!crashRecovery) + if (last_key == NULL || last_key->start.lsn < loc.lsn) { - if (last_key == NULL || last_key->start.lsn < loc.lsn) - { - pg_tde_wal_last_key_set_location(loc); - EncryptionKey.wal_start = loc; - TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); - lastKeyUsable = true; - } + pg_tde_wal_last_key_set_location(loc); + EncryptionKey.wal_start = loc; + TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); + lastKeyUsable = true; } } From 80e0bb0b56a2d64cdced770ded2da19003eef5ca Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 19 Aug 2025 10:48:51 +0200 Subject: [PATCH 594/796] PG-1867 Make pg_tde_restore_encrypt re-use old keys Unfortunately the logic for generating a new key to protect the stream cipher used to encrypt the WAL stream in our restore command was based on totally incorrect assumptions due to how the recovery is implemented. Recovery is a state machine which can go back and forward between one mode where it streams from a primary and another where it first tries to fetch WAL from the archive and if that fails from the pg_wal directory, and in the pg_wal directory we may have files which are encrypted with whatever keys were there originally. To handle all the possible scenarios we remove the ability of pg_tde_restore_encrypt to generate new keys and just has it use whatever keys there are in the key file. This unfortunately means we open ourselves to some attacks on the stream cipher if the system is tricked into encrypting a different WAL stream at the same TLI and LSN as we already have encrypted. As far as I know this should be rare under normal operations since normally e.g. the WAL should be the same in the archive as the one in pg_wal or which we receive through streaming. Ideally we would want to fix this but for now it is better to have WAL encryption with this weakness than to not have it at all. This also incidentally fixes a bug we discovered caused by generating a new key only invalidating one key rather than all keys which should have become invalid, since we no longer generate a new key. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 29 +++++++++++++++---- .../pg_tde/src/bin/pg_tde_restore_encrypt.c | 2 +- .../src/include/access/pg_tde_xlog_smgr.h | 2 +- contrib/pg_tde/t/wal_archiving.pl | 6 ++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 78c830e002216..6e075dadf14b5 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -266,16 +266,33 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) pfree(key); } +/* + * Used by pg_tde_restore_encrypt to simulate being constantly in recovery + * since the command does not have access to any information about if we are in + * recovery or not. + * + * Creates a dummy key which points at the very end of the WAL stream. + */ void -TDEXLogSmgrInitWriteReuseKey() +TDEXLogSmgrInitWriteOldKeys() { - WalEncryptionKey *key = pg_tde_read_last_wal_key(); + WALKeyCacheRec *keys; + WalEncryptionKey dummy = { + .type = WAL_KEY_TYPE_UNENCRYPTED, + .wal_start = {.tli = -1,.lsn = -1} + }; - if (key) + EncryptionKey = dummy; + TDEXLogSetEncKeyLocation(dummy.wal_start); + + keys = pg_tde_get_wal_cache_keys(); + + if (keys == NULL) { - EncryptionKey = *key; - TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); - pfree(key); + WalLocation start = {.tli = 1,.lsn = 0}; + + /* cache is empty, prefetch keys from disk */ + pg_tde_fetch_wal_keys(start); } } diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index a02519c787b6f..03f73cc1e6a98 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -68,7 +68,7 @@ write_encrypted_segment(const char *segpath, const char *segname, const char *tm XLogFromFileName(segname, &tli, &segno, walsegsz); - TDEXLogSmgrInitWriteReuseKey(); + TDEXLogSmgrInitWriteOldKeys(); w = xlog_smgr->seg_write(segfd, buf.data, r, pos, tli, segno, walsegsz); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 272880299f09a..16a0ba3cd7b37 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -11,7 +11,7 @@ extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); extern void TDEXLogSmgrInitWrite(bool encrypt_xlog); -extern void TDEXLogSmgrInitWriteReuseKey(void); +extern void TDEXLogSmgrInitWriteOldKeys(void); extern void TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize); diff --git a/contrib/pg_tde/t/wal_archiving.pl b/contrib/pg_tde/t/wal_archiving.pl index e3ac1ce4239ca..f35f5d6aa0720 100644 --- a/contrib/pg_tde/t/wal_archiving.pl +++ b/contrib/pg_tde/t/wal_archiving.pl @@ -58,7 +58,7 @@ unlike( `strings $data_dir/pg_wal/0000000100000000000000??`, qr/foobar_enc/, - 'should not find foobar_enc in WAL'); + 'should not find foobar_enc in WAL since it is encrypted'); $primary->stop; @@ -84,10 +84,10 @@ $data_dir = $replica->data_dir; -unlike( +like( `strings $data_dir/pg_wal/0000000100000000000000??`, qr/foobar_plain/, - 'should not find foobar_plain in WAL since it is encrypted'); + 'should find foobar_plain in WAL since we use the same key file'); unlike( `strings $data_dir/pg_wal/0000000100000000000000??`, qr/foobar_enc/, From 6df714b25c168efbd171352d0021a420f31d1297 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 19 Aug 2025 14:40:23 +0200 Subject: [PATCH 595/796] PG-1867 Improve archiving test and fix race condition There was a race condition in the WAL archiving tests where if the end-of-recovery checkpoint had completed the tests for the WAL contents were non-sensical and racy. Solve this by explicilty promoting the server first after we have looked at the WAL contents but still making sure to wait until all WAL has been replayed. Additionally improve the tests by actually making sure the replica starts in a good state where all WAL is encrypted and testing both the plaintext and the encrypted scenarios. --- contrib/pg_tde/t/wal_archiving.pl | 77 +++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/contrib/pg_tde/t/wal_archiving.pl b/contrib/pg_tde/t/wal_archiving.pl index f35f5d6aa0720..3bcbdf51bb108 100644 --- a/contrib/pg_tde/t/wal_archiving.pl +++ b/contrib/pg_tde/t/wal_archiving.pl @@ -37,9 +37,25 @@ "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'keyring');" ); +# This is a quite ugly dance to make sure we have a replica starting in a stats +# with encrypted WAL and without. We do this by taking a base backup while +# encryption is disabled and one where it is enabled. +# +# We also generate some plaintext WAL and some ecrnypted WAL. + +$primary->backup('plain_wal', backup_options => [ '-X', 'none' ]); + $primary->append_conf('postgresql.conf', "pg_tde.wal_encrypt = on"); -$primary->backup('backup', backup_options => [ '-X', 'none' ]); +$primary->restart; + +$primary->backup('enc_wal', backup_options => [ '-X', 'none' ]); + +$primary->append_conf('postgresql.conf', "pg_tde.wal_encrypt = off"); + +$primary->restart; + +$primary->append_conf('postgresql.conf', "pg_tde.wal_encrypt = on"); $primary->safe_psql('postgres', "CREATE TABLE t1 AS SELECT 'foobar_plain' AS x"); @@ -71,33 +87,68 @@ qr/foobar_enc/, 'should find foobar_enc in archive'); -# Test restore_command +# Test restore_command with encrypted WAL -my $replica = PostgreSQL::Test::Cluster->new('replica'); -$replica->init_from_backup($primary, 'backup'); -$replica->append_conf('postgresql.conf', +my $replica_enc = PostgreSQL::Test::Cluster->new('replica_enc'); +$replica_enc->init_from_backup($primary, 'enc_wal'); +$replica_enc->append_conf('postgresql.conf', "restore_command = 'pg_tde_restore_encrypt %f %p \"cp $archive_dir/%%f %%p\"'" ); -$replica->append_conf('postgresql.conf', "recovery_target_action = promote"); -$replica->set_recovery_mode; -$replica->start; +$replica_enc->set_standby_mode; +$replica_enc->start; -$data_dir = $replica->data_dir; +$replica_enc->wait_for_log("waiting for WAL to become available"); -like( +$data_dir = $replica_enc->data_dir; + +unlike( `strings $data_dir/pg_wal/0000000100000000000000??`, qr/foobar_plain/, - 'should find foobar_plain in WAL since we use the same key file'); + 'should not find foobar_plain in WAL since it is encrypted'); unlike( `strings $data_dir/pg_wal/0000000100000000000000??`, qr/foobar_enc/, 'should not find foobar_enc in WAL since it is encrypted'); -my $result = $replica->safe_psql('postgres', +$replica_enc->promote; + +my $result = $replica_enc->safe_psql('postgres', + 'SELECT * FROM t1 UNION ALL SELECT * FROM t2'); + +is($result, "foobar_plain\nfoobar_enc", 'b'); + +$replica_enc->stop; + +# Test restore_command with plain WAL + +my $replica_plain = PostgreSQL::Test::Cluster->new('replica_plain'); +$replica_plain->init_from_backup($primary, 'plain_wal'); +$replica_plain->append_conf('postgresql.conf', + "restore_command = 'pg_tde_restore_encrypt %f %p \"cp $archive_dir/%%f %%p\"'" +); +$replica_plain->set_standby_mode; +$replica_plain->start; + +$replica_plain->wait_for_log("waiting for WAL to become available"); + +$data_dir = $replica_plain->data_dir; + +like( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_plain/, + 'should find foobar_plain in WAL since it is not encrypted'); +like( + `strings $data_dir/pg_wal/0000000100000000000000??`, + qr/foobar_enc/, + 'should find foobar_enc in WAL since it is not encrypted'); + +$replica_plain->promote; + +$result = $replica_plain->safe_psql('postgres', 'SELECT * FROM t1 UNION ALL SELECT * FROM t2'); is($result, "foobar_plain\nfoobar_enc", 'b'); -$replica->stop; +$replica_plain->stop; done_testing(); From 481030de9af0edf174c623dc6f3d98c6bafaa6ad Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Thu, 14 Aug 2025 21:13:11 +0300 Subject: [PATCH 596/796] pg_basebackup: encrypt streamed WAL with new key Before, pg_basebackup would encrypt streamed WAL according to the keys in pg_tde/wal_keys in the destination dir. This commit introduces the number of changes: pg_basebackup encrypts WAL only if the "-E --encrypt-wal" flag is provided. In such a case, it would extract the principal key, truncate pg_tde/wal_keys and encrypt WAL with a newly generated WAL key. We still expect pg_tde/wal_keys and pg_tde/1664_providers in the destination dir. In case these files are not provided, but "-E" is specified, it fails with an error. We also throw a warning if pg_basebackup runs w/o -E, but there is wal_keys on the source as WAL might be compromised, and the backup is broken For PG-1603, PG-1857 --- contrib/pg_tde/meson.build | 1 + contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 4 +- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 35 +++++++----- .../src/include/access/pg_tde_xlog_smgr.h | 4 ++ contrib/pg_tde/t/pg_basebackup.pl | 49 +++++++++++++++++ contrib/pg_tde/t/pgtde.pm | 2 + src/bin/pg_basebackup/bbstreamer.h | 3 +- src/bin/pg_basebackup/bbstreamer_file.c | 25 ++++++++- src/bin/pg_basebackup/pg_basebackup.c | 55 ++++++++++++++++++- src/bin/pg_basebackup/receivelog.c | 6 ++ src/bin/pg_basebackup/receivelog.h | 1 + 11 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 contrib/pg_tde/t/pg_basebackup.pl diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index e92891bb70ac5..0494fda2796e5 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -109,6 +109,7 @@ tap_tests = [ 't/key_rotate_tablespace.pl', 't/key_validation.pl', 't/multiple_extensions.pl', + 't/pg_basebackup.pl', 't/pg_tde_change_key_provider.pl', 't/pg_rewind_basic.pl', 't/pg_rewind_databases.pl', diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 0d785e2d13bb8..4c14625e0080f 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -766,7 +766,6 @@ pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info) } #endif -#ifndef FRONTEND /* * Creates the key file and saves the principal key information. * @@ -788,17 +787,18 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog) pg_tde_sign_principal_key_info(&signed_key_Info, principal_key); +#ifndef FRONTEND if (write_xlog) { XLogBeginInsert(); XLogRegisterData((char *) &signed_key_Info, sizeof(TDESignedPrincipalKeyInfo)); XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY); } +#endif fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_Info, true, &curr_pos); CloseTransientFile(fd); } -#endif /* * Get the principal key from the key file. The caller must hold diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 6e075dadf14b5..c47e7843e5284 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -355,29 +355,25 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, return pg_pwrite(fd, enc_buff, count, offset); } -static ssize_t -tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, - TimeLineID tli, XLogSegNo segno, int segSize) +/* + * Set the last (most recent) key's start location if not set. + */ +bool +tde_ensure_xlog_key_location(WalLocation loc) { bool lastKeyUsable; bool afterWriteKey; + WalLocation writeKeyLoc; #ifdef FRONTEND bool crashRecovery = false; #else bool crashRecovery = GetRecoveryState() == RECOVERY_STATE_CRASH; #endif - WalLocation loc = {.tli = tli}; - WalLocation writeKeyLoc; - - XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); - /* - * Set the last (most recent) key's start LSN if not set. - * - * This func called with WALWriteLock held, so no need in any extra sync. + * On backend this called with WALWriteLock held, so no need in any extra + * sync. */ - writeKeyLoc.lsn = TDEXLogGetEncKeyLsn(); pg_read_barrier(); writeKeyLoc.tli = TDEXLogGetEncKeyTli(); @@ -398,7 +394,20 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, } } - if ((!afterWriteKey || !lastKeyUsable) && EncryptionKey.type != WAL_KEY_TYPE_INVALID) + return lastKeyUsable && afterWriteKey; +} + +static ssize_t +tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, + TimeLineID tli, XLogSegNo segno, int segSize) +{ + bool lastKeyUsable; + WalLocation loc = {.tli = tli}; + + XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); + lastKeyUsable = tde_ensure_xlog_key_location(loc); + + if (!lastKeyUsable && EncryptionKey.type != WAL_KEY_TYPE_INVALID) { return TDEXLogWriteEncryptedPagesOldKeys(fd, buf, count, offset, tli, segno, segSize); } diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h index 16a0ba3cd7b37..8d5ec4bc08b42 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h @@ -7,6 +7,8 @@ #include "postgres.h" +#include "access/pg_tde_xlog_keys.h" + extern Size TDEXLogEncryptStateSize(void); extern void TDEXLogShmemInit(void); extern void TDEXLogSmgrInit(void); @@ -16,4 +18,6 @@ extern void TDEXLogSmgrInitWriteOldKeys(void); extern void TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno, int segSize); +extern bool tde_ensure_xlog_key_location(WalLocation loc); + #endif /* PG_TDE_XLOGSMGR_H */ diff --git a/contrib/pg_tde/t/pg_basebackup.pl b/contrib/pg_tde/t/pg_basebackup.pl new file mode 100644 index 0000000000000..22a55b5be3f57 --- /dev/null +++ b/contrib/pg_tde/t/pg_basebackup.pl @@ -0,0 +1,49 @@ +use strict; +use warnings FATAL => 'all'; +use Config; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +program_help_ok('pg_basebackup'); +program_version_ok('pg_basebackup'); +program_options_handling_ok('pg_basebackup'); + +my $tempdir = PostgreSQL::Test::Utils::tempdir; + +my $node = PostgreSQL::Test::Cluster->new('main'); + +# Initialize node without replication settings +$node->init( + allows_streaming => 1, + extra => ['--data-checksums'], + auth_extra => [ '--create-role', 'backupuser' ]); +$node->start; + +# Sanity checks for options with WAL encryption +$node->command_fails_like( + [ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-Ft' ], + qr/can not encrypt WAL in tar mode/, + 'encryption in tar mode'); + +$node->command_fails_like( + [ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-X', 'fetch' ], + qr/WAL encryption can only be used with WAL streaming/, + 'encryption with WAL fetch'); + +$node->command_fails_like( + [ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-X', 'none' ], + qr/WAL encryption can only be used with WAL streaming/, + 'encryption with WAL none'); + +$node->command_fails_like( + [ 'pg_basebackup', '-D', "$tempdir/backup", '-E' ], + qr/could not find server principal key/, + 'encryption with no pg_tde dir'); + +$node->command_fails_like( + [ 'pg_basebackup', '-D', "$tempdir/backup", '--encrypt-wal' ], + qr/could not find server principal key/, + 'encryption with no pg_tde dir long flag'); + +done_testing(); diff --git a/contrib/pg_tde/t/pgtde.pm b/contrib/pg_tde/t/pgtde.pm index fa86d954b7701..2117134108432 100644 --- a/contrib/pg_tde/t/pgtde.pm +++ b/contrib/pg_tde/t/pgtde.pm @@ -119,6 +119,8 @@ sub backup PostgreSQL::Test::RecursiveCopy::copypath($node->data_dir . '/pg_tde', $backup_dir . '/pg_tde'); + push @{ $params{backup_options} }, "-E"; + $node->backup($backup_name, %params); } diff --git a/src/bin/pg_basebackup/bbstreamer.h b/src/bin/pg_basebackup/bbstreamer.h index 3b820f13b519a..7e63a7b1a1f4f 100644 --- a/src/bin/pg_basebackup/bbstreamer.h +++ b/src/bin/pg_basebackup/bbstreamer.h @@ -204,7 +204,8 @@ extern bbstreamer *bbstreamer_gzip_writer_new(char *pathname, FILE *file, pg_compress_specification *compress); extern bbstreamer *bbstreamer_extractor_new(const char *basepath, const char *(*link_map) (const char *), - void (*report_output_file) (const char *)); + void (*report_output_file) (const char *), + bool encrypted_wal); extern bbstreamer *bbstreamer_gzip_decompressor_new(bbstreamer *next); extern bbstreamer *bbstreamer_lz4_compressor_new(bbstreamer *next, diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c index b58d8ab9160dd..210550e1bf1c3 100644 --- a/src/bin/pg_basebackup/bbstreamer_file.c +++ b/src/bin/pg_basebackup/bbstreamer_file.c @@ -38,6 +38,7 @@ typedef struct bbstreamer_extractor void (*report_output_file) (const char *); char filename[MAXPGPATH]; FILE *file; + bool encryped_wal; } bbstreamer_extractor; static void bbstreamer_plain_writer_content(bbstreamer *streamer, @@ -186,7 +187,8 @@ bbstreamer_plain_writer_free(bbstreamer *streamer) bbstreamer * bbstreamer_extractor_new(const char *basepath, const char *(*link_map) (const char *), - void (*report_output_file) (const char *)) + void (*report_output_file) (const char *), + bool encrypted_wal) { bbstreamer_extractor *streamer; @@ -196,6 +198,7 @@ bbstreamer_extractor_new(const char *basepath, streamer->basepath = pstrdup(basepath); streamer->link_map = link_map; streamer->report_output_file = report_output_file; + streamer->encryped_wal = encrypted_wal; return &streamer->base; } @@ -240,9 +243,29 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member, extract_link(mystreamer->filename, linktarget); } else + { +#ifdef PERCONA_EXT + /* + * A streamed WAL is encrypted with the newly generated WAL key, + * hence we have to prevent these files from rewriting. + */ + if (mystreamer->encryped_wal) + { + if (strcmp(member->pathname, "pg_tde/wal_keys") == 0 || + strcmp(member->pathname, "pg_tde/1664_providers") == 0) + break; + } + else if (strcmp(member->pathname, "pg_tde/wal_keys") == 0) + { + pg_log_warning("the source has WAL keys, but no WAL encryption configured for the target backups"); + pg_log_warning_detail("This may lead to exposed data and broken backup."); + pg_log_warning_hint("Run pg_basebackup with -E to encrypt streamed WAL."); + } +#endif mystreamer->file = create_file_for_extract(mystreamer->filename, member->mode); + } /* Report output file change. */ if (mystreamer->report_output_file) diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c index 1916ec9c805f3..e45d09f7151ea 100644 --- a/src/bin/pg_basebackup/pg_basebackup.c +++ b/src/bin/pg_basebackup/pg_basebackup.c @@ -41,8 +41,12 @@ #ifdef PERCONA_EXT #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" +#include "access/pg_tde_xlog_keys.h" #include "access/xlog_smgr.h" +#include "catalog/tde_principal_key.h" #include "pg_tde.h" + +#define GLOBAL_DATA_TDE_OID 1664 #endif #define ERRCODE_DATA_CORRUPTED_BCP "XX001" @@ -145,6 +149,7 @@ static bool showprogress = false; static bool estimatesize = true; static int verbose = 0; static IncludeWal includewal = STREAM_WAL; +static bool encrypt_wal = false; static bool fastcheckpoint = false; static bool writerecoveryconf = false; static bool do_sync = true; @@ -416,6 +421,9 @@ usage(void) printf(_(" --waldir=WALDIR location for the write-ahead log directory\n")); printf(_(" -X, --wal-method=none|fetch|stream\n" " include required WAL files with specified method\n")); +#ifdef PERCONA_EXT + printf(_(" -E, --encrypt-wal encrypt streamed WAL\n")); +#endif printf(_(" -z, --gzip compress tar output\n")); printf(_(" -Z, --compress=[{client|server}-]METHOD[:DETAIL]\n" " compress on client or server as specified\n")); @@ -568,6 +576,7 @@ LogStreamerMain(logstreamer_param *param) stream.synchronous = false; /* fsync happens at the end of pg_basebackup for all data */ stream.do_sync = false; + stream.encrypt = encrypt_wal; stream.mark_done = true; stream.partial_suffix = NULL; stream.replication_slot = replication_slot; @@ -662,12 +671,23 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier, "pg_xlog" : "pg_wal"); #ifdef PERCONA_EXT - { + if (encrypt_wal) { char tdedir[MAXPGPATH]; + TDEPrincipalKey *principalKey; snprintf(tdedir, sizeof(tdedir), "%s/%s", basedir, PG_TDE_DATA_DIR); pg_tde_fe_init(tdedir); TDEXLogSmgrInit(); + + principalKey = GetPrincipalKey(GLOBAL_DATA_TDE_OID, NULL); + if (!principalKey) + { + pg_log_error("could not find server principal key"); + pg_log_error_hint("Copy PGDATA/pg_tde from the source to the backup destination dir."); + exit(1); + } + pg_tde_save_server_key(principalKey, false); + TDEXLogSmgrInitWrite(true); } #endif @@ -1187,7 +1207,8 @@ CreateBackupStreamer(char *archive_name, char *spclocation, directory = get_tablespace_mapping(spclocation); streamer = bbstreamer_extractor_new(directory, get_tablespace_mapping, - progress_update_filename); + progress_update_filename, + encrypt_wal); } else { @@ -2393,6 +2414,9 @@ main(int argc, char **argv) {"target", required_argument, NULL, 't'}, {"tablespace-mapping", required_argument, NULL, 'T'}, {"wal-method", required_argument, NULL, 'X'}, +#ifdef PERCONA_EXT + {"encrypt-wal", no_argument, NULL, 'E'}, +#endif {"gzip", no_argument, NULL, 'z'}, {"compress", required_argument, NULL, 'Z'}, {"label", required_argument, NULL, 'l'}, @@ -2447,7 +2471,7 @@ main(int argc, char **argv) atexit(cleanup_directories_atexit); - while ((c = getopt_long(argc, argv, "c:Cd:D:F:h:i:l:nNp:Pr:Rs:S:t:T:U:vwWX:zZ:", + while ((c = getopt_long(argc, argv, "c:Cd:D:EF:h:i:l:nNp:Pr:Rs:S:t:T:U:vwWX:zZ:", long_options, &option_index)) != -1) { switch (c) @@ -2560,6 +2584,11 @@ main(int argc, char **argv) pg_fatal("invalid wal-method option \"%s\", must be \"fetch\", \"stream\", or \"none\"", optarg); break; +#ifdef PERCONA_EXT + case 'E': + encrypt_wal = true; + break; +#endif case 'z': compression_algorithm = "gzip"; compression_detail = NULL; @@ -2738,6 +2767,26 @@ main(int argc, char **argv) exit(1); } + /* + * Sanity checks for WAL encryption. + */ + if (encrypt_wal) + { + if (includewal != STREAM_WAL) + { + pg_log_error("WAL encryption can only be used with WAL streaming"); + pg_log_error_hint("Use -X stream with -E."); + exit(1); + } + + if (format != 'p') + { + pg_log_error("can not encrypt WAL in tar mode"); + pg_log_error_hint("Use -Fp with -E."); + exit(1); + } + } + /* * Sanity checks for replication slot options. */ diff --git a/src/bin/pg_basebackup/receivelog.c b/src/bin/pg_basebackup/receivelog.c index 6664cd14d0109..62d7b91bbab26 100644 --- a/src/bin/pg_basebackup/receivelog.c +++ b/src/bin/pg_basebackup/receivelog.c @@ -28,6 +28,7 @@ #ifdef PERCONA_EXT #include "access/pg_tde_fe_init.h" #include "access/pg_tde_xlog_smgr.h" +#include "access/xlog_smgr.h" #include "catalog/tde_global_space.h" #endif @@ -1131,8 +1132,13 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len, } #ifdef PERCONA_EXT + if (stream->encrypt) { void* enc_buf = copybuf + hdr_len + bytes_written; + WalLocation loc = {.tli = stream->timeline}; + + XLogSegNoOffsetToRecPtr(segno, xlogoff, WalSegSz, loc.lsn); + tde_ensure_xlog_key_location(loc); TDEXLogCryptBuffer(enc_buf, enc_buf, bytes_to_write, xlogoff, stream->timeline, segno, WalSegSz); } diff --git a/src/bin/pg_basebackup/receivelog.h b/src/bin/pg_basebackup/receivelog.h index 0a18f897964f6..fa4469f772990 100644 --- a/src/bin/pg_basebackup/receivelog.h +++ b/src/bin/pg_basebackup/receivelog.h @@ -37,6 +37,7 @@ typedef struct StreamCtl bool mark_done; /* Mark segment as done in generated archive */ bool do_sync; /* Flush to disk to ensure consistent state of * data */ + bool encrypt; /* Encrypt WAL */ stream_stop_callback stream_stop; /* Stop streaming when returns true */ From 415cb8dd5bbb589a928c5c542fcc38a720b3e502 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 19 Aug 2025 15:32:54 +0200 Subject: [PATCH 597/796] Properly print errors from system() in archive and restore commands We used to assume that the only errors which could happen were ones which set the errno, but that is not the case. We also want to give nice output on non-zero return values and if the process was killed by a signal. --- .../pg_tde/src/bin/pg_tde_archive_decrypt.c | 18 ++++++++++++++++-- .../pg_tde/src/bin/pg_tde_restore_encrypt.c | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c index 3afe254b491f9..786919a3150ce 100644 --- a/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c @@ -146,6 +146,7 @@ main(int argc, char *argv[]) char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_archiveXXXXXX"; char tmppath[MAXPGPATH]; bool issegment; + int rc; pg_logging_init(argv[0]); progname = get_progname(argv[0]); @@ -208,9 +209,22 @@ main(int argc, char *argv[]) command = replace_percent_placeholders(command, "ARCHIVE-COMMAND", "fp", targetname, sourcepath); + rc = system(command); - if (system(command) != 0) - pg_fatal("ARCHIVE-COMMAND \"%s\" failed: %m", command); + if (rc != 0) + { + if (rc == -1) + pg_fatal("ARCHIVE-COMMAND \"%s\" failed: %m", command); + else if (WIFEXITED(rc)) + pg_fatal("ARCHIVE-COMMAND \"%s\" failed with exit code %d", + command, WEXITSTATUS(rc)); + else if (WIFSIGNALED(rc)) + pg_fatal("ARCHIVE-COMMAND \"%s\" was terminated by signal %d: %s", + command, WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))); + else + pg_fatal("ARCHIVE-COMMAND \"%s\" exited with unrecognized status %d", + command, rc); + } free(command); diff --git a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c index 03f73cc1e6a98..1d5c51f4a7bda 100644 --- a/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c +++ b/contrib/pg_tde/src/bin/pg_tde_restore_encrypt.c @@ -141,6 +141,7 @@ main(int argc, char *argv[]) char tmpdir[MAXPGPATH] = TMPFS_DIRECTORY "/pg_tde_restoreXXXXXX"; char tmppath[MAXPGPATH]; bool issegment; + int rc; pg_logging_init(argv[0]); progname = get_progname(argv[0]); @@ -201,9 +202,22 @@ main(int argc, char *argv[]) command = replace_percent_placeholders(command, "RESTORE-COMMAND", "fp", sourcename, targetpath); + rc = system(command); - if (system(command) != 0) - pg_fatal("RESTORE-COMMAND \"%s\" failed: %m", command); + if (rc != 0) + { + if (rc == -1) + pg_fatal("RESTORE-COMMAND \"%s\" failed: %m", command); + else if (WIFEXITED(rc)) + pg_fatal("RESTORE-COMMAND \"%s\" failed with exit code %d", + command, WEXITSTATUS(rc)); + else if (WIFSIGNALED(rc)) + pg_fatal("RESTORE-COMMAND \"%s\" was terminated by signal %d: %s", + command, WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))); + else + pg_fatal("RESTORE-COMMAND \"%s\" exited with unrecognized status %d", + command, rc); + } free(command); From d55240d24ce231fa61abf5945cb9122e81ca08ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 19 Aug 2025 20:20:52 +0200 Subject: [PATCH 598/796] Bump pg_tde version to 2.0 This release is supposed to be 2.0. The SQL upgrade file is a dummy, but I believe it's required. --- contrib/pg_tde/Makefile | 2 +- contrib/pg_tde/expected/version.out | 2 +- contrib/pg_tde/meson.build | 1 + contrib/pg_tde/pg_tde--1.0--2.0.sql | 4 ++++ contrib/pg_tde/pg_tde.control | 2 +- contrib/pg_tde/src/include/pg_tde.h | 2 +- contrib/pg_tde/t/expected/basic.out | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 contrib/pg_tde/pg_tde--1.0--2.0.sql diff --git a/contrib/pg_tde/Makefile b/contrib/pg_tde/Makefile index 25726c461125e..f691f3d400302 100644 --- a/contrib/pg_tde/Makefile +++ b/contrib/pg_tde/Makefile @@ -1,7 +1,7 @@ PGFILEDESC = "pg_tde access method" MODULE_big = pg_tde EXTENSION = pg_tde -DATA = pg_tde--1.0.sql +DATA = pg_tde--1.0--2.0.sql pg_tde--1.0.sql # Since meson supports skipping test suites this is a make only feature ifndef TDE_MODE diff --git a/contrib/pg_tde/expected/version.out b/contrib/pg_tde/expected/version.out index 44ebea8d04d32..54e00d133bf6a 100644 --- a/contrib/pg_tde/expected/version.out +++ b/contrib/pg_tde/expected/version.out @@ -2,7 +2,7 @@ CREATE EXTENSION pg_tde; SELECT pg_tde_version(); pg_tde_version ---------------- - pg_tde 1.0.0 + pg_tde 2.0.0 (1 row) DROP EXTENSION pg_tde; diff --git a/contrib/pg_tde/meson.build b/contrib/pg_tde/meson.build index 0494fda2796e5..0899f2ebb618f 100644 --- a/contrib/pg_tde/meson.build +++ b/contrib/pg_tde/meson.build @@ -78,6 +78,7 @@ endif install_data( 'pg_tde.control', 'pg_tde--1.0.sql', + 'pg_tde--1.0--2.0.sql', kwargs: contrib_data_args, ) diff --git a/contrib/pg_tde/pg_tde--1.0--2.0.sql b/contrib/pg_tde/pg_tde--1.0--2.0.sql new file mode 100644 index 0000000000000..04d243f79afc2 --- /dev/null +++ b/contrib/pg_tde/pg_tde--1.0--2.0.sql @@ -0,0 +1,4 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION pg_tde UPDATE TO '2.0'" to load this file. \quit + +-- This file is a dummy, because no SQL changed between 1.0 and 2.0 diff --git a/contrib/pg_tde/pg_tde.control b/contrib/pg_tde/pg_tde.control index 9ea82992d7490..5496ed190ecc4 100644 --- a/contrib/pg_tde/pg_tde.control +++ b/contrib/pg_tde/pg_tde.control @@ -1,4 +1,4 @@ comment = 'pg_tde access method' -default_version = '1.0' +default_version = '2.0' module_pathname = '$libdir/pg_tde' relocatable = false diff --git a/contrib/pg_tde/src/include/pg_tde.h b/contrib/pg_tde/src/include/pg_tde.h index 4b6bb94d6d8f4..46e622d26a91c 100644 --- a/contrib/pg_tde/src/include/pg_tde.h +++ b/contrib/pg_tde/src/include/pg_tde.h @@ -2,7 +2,7 @@ #define PG_TDE_H #define PG_TDE_NAME "pg_tde" -#define PG_TDE_VERSION "1.0.0" +#define PG_TDE_VERSION "2.0.0" #define PG_TDE_VERSION_STRING PG_TDE_NAME " " PG_TDE_VERSION #define PG_TDE_DATA_DIR "pg_tde" diff --git a/contrib/pg_tde/t/expected/basic.out b/contrib/pg_tde/t/expected/basic.out index 3947c0988f891..abeba5d06e232 100644 --- a/contrib/pg_tde/t/expected/basic.out +++ b/contrib/pg_tde/t/expected/basic.out @@ -20,7 +20,7 @@ CREATE EXTENSION pg_tde; SELECT extname, extversion FROM pg_extension WHERE extname = 'pg_tde'; extname | extversion ---------+------------ - pg_tde | 1.0 + pg_tde | 2.0 (1 row) CREATE TABLE test_enc (id SERIAL, k INTEGER, PRIMARY KEY (id)) USING tde_heap; From 50b3ec8e97b310dc63242190ca174ac0858b8e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Tue, 19 Aug 2025 20:26:11 +0200 Subject: [PATCH 599/796] Bump percona version to 17.5.3 This is the version we're about to release. --- ci_scripts/env.sh | 2 +- configure | 2 +- configure.ac | 2 +- meson.build | 2 +- src/test/modules/test_misc/t/008_percona_server_version.pl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci_scripts/env.sh b/ci_scripts/env.sh index ec1cff4f8f2a1..11cf83eeed38e 100644 --- a/ci_scripts/env.sh +++ b/ci_scripts/env.sh @@ -1,3 +1,3 @@ #!/bin/bash -export PERCONA_SERVER_VERSION=17.5.2 +export PERCONA_SERVER_VERSION=17.5.3 diff --git a/configure b/configure index 7960612b26b5c..542e748217370 100755 --- a/configure +++ b/configure @@ -2856,7 +2856,7 @@ cat >>confdefs.h <<_ACEOF _ACEOF -PG_PERCONAVERSION=2 +PG_PERCONAVERSION=3 cat >>confdefs.h <<_ACEOF #define PG_PERCONAVERSION "$PG_PERCONAVERSION" diff --git a/configure.ac b/configure.ac index 70fa3619d4bad..d3f805585017a 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AC_DEFINE_UNQUOTED(PG_MAJORVERSION, "$PG_MAJORVERSION", [PostgreSQL major versio AC_DEFINE_UNQUOTED(PG_MAJORVERSION_NUM, $PG_MAJORVERSION, [PostgreSQL major version number]) AC_DEFINE_UNQUOTED(PG_MINORVERSION_NUM, $PG_MINORVERSION, [PostgreSQL minor version number]) -[PG_PERCONAVERSION=2] +[PG_PERCONAVERSION=3] AC_DEFINE_UNQUOTED(PG_PERCONAVERSION, "$PG_PERCONAVERSION", [PostgreSQL Percona version as a string]) PGAC_ARG_REQ(with, extra-version, [STRING], [append STRING to version], diff --git a/meson.build b/meson.build index c054200660486..8ea9d294a00ec 100644 --- a/meson.build +++ b/meson.build @@ -134,7 +134,7 @@ endif pg_version_major = pg_version_arr[0].to_int() pg_version_minor = pg_version_arr[1].to_int() pg_version_num = (pg_version_major * 10000) + pg_version_minor -pg_percona_ver = '2' +pg_percona_ver = '3' pg_url = 'https://www.postgresql.org/' diff --git a/src/test/modules/test_misc/t/008_percona_server_version.pl b/src/test/modules/test_misc/t/008_percona_server_version.pl index 347e173b523ed..8dc0ff0621d8d 100644 --- a/src/test/modules/test_misc/t/008_percona_server_version.pl +++ b/src/test/modules/test_misc/t/008_percona_server_version.pl @@ -17,7 +17,7 @@ # To make this testcase work, PERCONA_SERVER_VERSION variable should be available in environment. # If you are using ci_scripts it is already declated in ci_scripts/env.sh # If you are using command line make for regression then export like: -# export PERCONA_SERVER_VERSION=17.5.2 +# export PERCONA_SERVER_VERSION=17.5.3 if (!defined($ENV{PERCONA_SERVER_VERSION})) { From 9d6297fa3053ebcfff616a35a574f90ad6390bd8 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Wed, 20 Aug 2025 10:58:39 +0300 Subject: [PATCH 600/796] Update the Features topic buttons for better clarity (#508) --- contrib/pg_tde/documentation/docs/features.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index aa8703fc42bca..3f51c25aceaac 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -19,4 +19,8 @@ The following features are available for the extension: * Table-level granularity for encryption and access control * Multiple [Key management options](global-key-provider-configuration/index.md) -[Learn more about TDE and pg_tde :material-arrow-right:](index/about-tde.md){.md-button} [Get started with installation :material-arrow-right:](install.md){.md-button} +## Next steps + +Learn more about how `pg_tde` implements Transparent Data Encryption: + +[About Transparent Data Encryption :material-arrow-right:](index/about-tde.md){.md-button} From c0ad12b50fca12db1700e37acfa5ff6904314648 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 22 Aug 2025 13:08:36 +0300 Subject: [PATCH 601/796] PG-1832 Document the archive and restore commands cont (#531) Continued from #523 - add pg_tde archive and restore commands - update cli-tools.md with paragraphs explaining New and extended tools - update pg-tde-restore-encrypt tool with new information and better descriptions for clarity - update the Features topic button for better clarity --- .../docs/command-line-tools/cli-tools.md | 19 +++++-- .../pg-tde-archive-decrypt.md | 55 +++++++++++++++++++ .../pg-tde-restore-encrypt.md | 49 +++++++++++++++++ contrib/pg_tde/documentation/mkdocs.yml | 6 +- 4 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md create mode 100644 contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md b/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md index e0edeb4b5de32..aac0c8d2f0680 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/cli-tools.md @@ -1,7 +1,18 @@ # Overview of pg_tde CLI tools -The `pg_tde` extension introduces new command-line utilities and extends some existing PostgreSQL tools to support encrypted WAL and tables. These include: +The `pg_tde` extension introduces new command-line utilities and extends some existing PostgreSQL tools to support encrypted WAL and tables. -* [pg_tde_change_key_provider](../command-line-tools/pg-tde-change-key-provider.md): change encryption key provider for a database -* [pg_waldump](../command-line-tools/pg-waldump.md): inspect and decrypt WAL files -* [pg_checksums](../command-line-tools/pg-tde-checksums.md): verify data checksums (non-encrypted files only) +## New tools + +These tools are introduced by `pg_tde` to support key rotation and WAL encryption workflows: + +* [pg_tde_change_key_provider](./pg-tde-change-key-provider.md): change the encryption key provider for a database +* [pg_tde_archive_decrypt](./pg-tde-archive-decrypt.md): decrypts WAL before archiving +* [pg_tde_restore_encrypt](./pg-tde-restore-encrypt.md): a custom restore command for making sure the restored WAL is encrypted + +## Extended tools + +These existing PostgreSQL tools are enhanced to support `pg_tde`: + +* [pg_checksums](./pg-tde-checksums.md): verify data checksums (non-encrypted files only) +* [pg_waldump](./pg-waldump.md): inspect and decrypt WAL files diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md new file mode 100644 index 0000000000000..0b63ab5212dcf --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md @@ -0,0 +1,55 @@ +# pg_tde_archive_decrypt + +The `pg_tde_archive_decrypt` tool wraps an archive command and decrypts WAL files before archiving. It allows external tools to access unencrypted WAL data, which is required because WAL encryption keys in the two-key hierarchy are host-specific and may not be available on the replay host. + +!!! tip + + For more information on the encryption architecture and key hierarchy, see [Architecture](../architecture/architcture.md). + +This tool is often used in conjunction with [pg_tde_restore_encrypt](./pg-tde-restore-encrypt.md) to support WAL archive. + +## How it works + +1. Decrypts the WAL segment to a temporary file on a RAM disk (`/dev/shm`) +2. Replaces `%p` and `%f` in the archive command with the path and name of the decrypted file +3. Executes the archive command + +!!! note + + To ensure security, encrypt the files stored in your WAL archive using tools like `PgBackRest`. + +## Usage + +```bash +pg_tde_archive_decrypt [OPTION] +pg_tde_archive_decrypt DEST-NAME SOURCE-PATH ARCHIVE-COMMAND +``` + +## Parameter descriptions + +* `DEST-NAME`: name of the WAL file to send to the archive +* `SOURCE-PATH`: path to the original encrypted WAL file +* `ARCHIVE-COMMAND`: archive command to wrap. `%p` and `%f` are replaced with the decrypted WAL file path and WAL file name, respectively. + +## Options + +* `-V, --version`: show version information, then exit +* `-?, --help`: show help information, then exit + +!!! note + + Any `%f` or `%p` parameter in `ARCHIVE-COMMAND` has to be escaped as `%%f` or `%%p` respectively if used as `archive_command` in `postgresql.conf`. + +## Examples + +### Using `cp` + +```ini +archive_command='pg_tde_archive_decrypt %f %p "cp %%p /mnt/server/archivedir/%%f"' +``` + +### Using `PgBackRest` + +```ini +archive_command='pg_tde_archive_decrypt %f %p "pgbackrest --stanza=your_stanza archive-push %%p"' +``` diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md new file mode 100644 index 0000000000000..7435de2d0b761 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md @@ -0,0 +1,49 @@ +# pg_tde_restore_encrypt + +The `pg_tde_restore_encrypt` tool wraps a normal restore command from the WAL archive and writes them to disk in a format compatible with `pg_tde`. + +!!! note + + This command is often use together with [pg_tde_archive_decrypt](./pg-tde-archive-decrypt.md). + +## How it works + +1. Replaces `%f` and `%p` in the restore command with the WAL file name and temporary file path (in `/dev/shm`) +2. Runs the restore command to fetch the unencrypted WAL from the archive and write it to the temp file +3. Encrypts the temp file and writes it to the destination path in PostgreSQL’s data directory + +## Usage + +```bash +pg_tde_restore_encrypt [OPTION] +pg_tde_restore_encrypt SOURCE-NAME DEST-PATH RESTORE-COMMAND +``` + +## Parameter descriptions + +* `SOURCE-NAME`: name of the WAL file to retrieve from the archive +* `DEST-PATH`: path where the encrypted WAL file should be written +* `RESTORE-COMMAND`: restore command to wrap; `%p` and `%f` are replaced with the WAL file name and path to write the unencrypted WAL, respectively + +## Options + +* `-V, --version`: show version information, then exit +* `-?, --help`: show help information, then exit + +!!! note + + Any `%f` or `%p` parameter in `RESTORE-COMMAND` has to be escaped as `%%f` or `%%p` respectively if used as `restore_command` in `postgresql.conf`. + +## Examples + +### Using `cp` + +```ini +restore_command='pg_tde_restore_encrypt %f %p "cp /mnt/server/archivedir/%%f %%p"' +``` + +### Using `PgBackRest` + +```ini +restore_command='pg_tde_restore_encrypt %f %p "pgbackrest --stanza=your_stanza archive-get %%f \"%%p\""' +``` diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 43725bb5de5e6..39c1c4eec0330 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -193,11 +193,13 @@ nav: - "Functions": functions.md - "Streaming Replication with tde_heap": replication.md - "TDE operations": - - "pg_tde CLI Tools": + - "pg_tde CLI tools": - "Overview": command-line-tools/cli-tools.md - "pg_tde_change_key_provider": command-line-tools/pg-tde-change-key-provider.md - - "pg_waldump": command-line-tools/pg-waldump.md + - "pg_tde_archive_decrypt": command-line-tools/pg-tde-archive-decrypt.md + - "pg_tde_restore_encrypt": command-line-tools/pg-tde-restore-encrypt.md - "pg_checksums": command-line-tools/pg-tde-checksums.md + - "pg_waldump": command-line-tools/pg-waldump.md - "Uninstall pg_tde": how-to/uninstall.md - "Configure Multi-tenancy": how-to/multi-tenant-setup.md - "Encryption Enforcement": how-to/enforcement.md From 307b33d65625e98afd5f19841691071522a6cde5 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 22 Aug 2025 13:11:22 +0300 Subject: [PATCH 602/796] Add WAL content for 2.0 release (#499) - remove (tech preview) - remove mentions of WAL being BETA and warning notes - add WAL tool support to limitations, improve flow, add button to setup - add limitation regarding WAL shipping standy not supported with WAL encryption - add mention of open source and enterprise ed being supported for pg_tde - add none method to basebackup and link to topic - add Example Patroni configuration for Patroni tool - improve supported vs unsupported tools section in Limitations --- .../docs/architecture/architecture.md | 5 +- .../docs/command-line-tools/pg-waldump.md | 3 - contrib/pg_tde/documentation/docs/faq.md | 5 +- .../pg_tde/documentation/docs/functions.md | 5 +- .../overview.md | 15 ++- .../set-principal-key.md | 12 ++- .../vault.md | 2 +- .../docs/index/how-does-tde-work.md | 10 +- .../docs/index/tde-limitations.md | 97 ++++++++++++++++++- contrib/pg_tde/documentation/docs/test.md | 2 +- .../documentation/docs/wal-encryption.md | 7 +- contrib/pg_tde/documentation/mkdocs.yml | 2 +- 12 files changed, 126 insertions(+), 39 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/architecture/architecture.md b/contrib/pg_tde/documentation/docs/architecture/architecture.md index c06dcb55161f7..aee77772ece98 100644 --- a/contrib/pg_tde/documentation/docs/architecture/architecture.md +++ b/contrib/pg_tde/documentation/docs/architecture/architecture.md @@ -20,7 +20,7 @@ The following sections break down the key architectural components of this desig * Indexes * Sequences * Temporary tables -* Write Ahead Log (WAL), still in beta. **Do not enable this feature in production environments**. +* Write Ahead Log (WAL) **Extension** means that `pg_tde` should be implemented only as an extension, possibly compatible with any PostgreSQL distribution, including the open source community version. This requires changes in the PostgreSQL core to make it more extensible. Therefore, `pg_tde` currently works only with the [Percona Server for PostgreSQL](https://docs.percona.com/postgresql/17/index.html) - a binary replacement of community PostgreSQL and included in Percona Distribution for PostgreSQL. @@ -82,9 +82,6 @@ Later decisions are made using a slightly modified Storage Manager (SMGR) API: w ### WAL encryption -!!! note - The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. - WAL encryption is controlled globally via a global GUC variable, `pg_tde.wal_encrypt`, that requires a server restart. WAL keys also contain the [LSN](https://www.postgresql.org/docs/17/wal-internals.html) of the first WAL write after key creation. This allows `pg_tde` to know which WAL ranges are encrypted or not and with which key. diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md index 0362eadcef7f0..a3ac50df4df02 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-waldump.md @@ -2,9 +2,6 @@ [`pg_waldump` :octicons-link-external-16:](https://www.postgresql.org/docs/current/pgwaldump.html) is a tool to display a human-readable rendering of the Write-Ahead Log (WAL) of a PostgreSQL database cluster. -!!! warning - The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. - To read encrypted WAL records, `pg_waldump` supports the following additional arguments: * `keyring_path` is the directory where the keyring configuration files for WAL are stored. The following files are included: diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index b244818371144..9e9c88f181a65 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -94,9 +94,6 @@ The principal key is used to encrypt the internal keys. The principal key is sto ### WAL encryption -!!! note - WAL encryption is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. - WAL encryption is done globally for the entire database cluster. All modifications to any database within a PostgreSQL cluster are written to the same WAL to maintain data consistency and integrity and ensure that PostgreSQL cluster can be restored to a consistent state. Therefore, WAL is encrypted globally. When you turn on WAL encryption, `pg_tde` encrypts entire WAL files starting from the first WAL write after the server was started with the encryption turned on. @@ -140,7 +137,7 @@ Since the `SET ACCESS METHOD` command drops hint bits and this may affect the pe You must restart the database in the following cases to apply the changes: * after you enabled the `pg_tde` extension -* when enabling WAL encryption, which is currently in beta. **Do not enable this feature in production environments**. +* when enabling WAL encryption After that, no database restart is required. When you create or alter the table using the `tde_heap` access method, the files are marked as those that require encryption. The encryption happens at the storage manager level, before a transaction is written to disk. Read more about [how tde_heap works](index/table-access-method.md#how-tde_heap-works-with-pg_tde). diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index b290d440ee7b7..82e6e3449a2de 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -242,7 +242,7 @@ SELECT pg_tde_create_key_using_database_key_provider( ### pg_tde_create_key_using_global_key_provider -Creates a principal key at a global key provider with the given name. Use this key later with the `pg_tde_set_` series of functions. +Creates a principal key at a global key provider with the given name. Use this key later with the `pg_tde_set_*` series of functions. ```sql SELECT pg_tde_create_key_using_global_key_provider( @@ -286,9 +286,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider( ); ``` -!!! warning - The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. - The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: * If set to `true`, a new key must be unique. diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md index 800f505925715..14aa13e569c6b 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md @@ -9,10 +9,15 @@ To use an external KMS with `pg_tde`, follow these two steps: 2. Set the [Global Principal Key](set-principal-key.md) !!! note - While keyfiles may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. + While key files may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. -Select your prefered configuration from the links below: +`pg_tde` has been tested with the following key providers: -[KMIP Configuration :material-arrow-right:](kmip-server.md){.md-button} -[Vault Configuration :material-arrow-right:](vault.md){.md-button} -[Keyring File Configuration (not recommended) :material-arrow-right:](keyring.md){.md-button} +| KMS Provider | Description | Documentation | +|--------------------|-------------------------------------------------------|---------------| +| **KMIP** | Standard Key Management Interoperability Protocol. | [Configure KMIP →](kmip-server.md) | +| **Vault** | HashiCorp Vault integration (KV v2 API, KMIP engine). | [Configure Vault →](vault.md) | +| **Fortanix** | Fortanix DSM key management. | [Configure Fortanix →](kmip-fortanix.md) | +| **Thales** | Thales CipherTrust Manager and DSM. | [Configure Thales →](kmip-thales.md) | +| **OpenBao** | Community fork of Vault, supporting KV v2. | [Configure OpenBao →](kmip-openbao.md) | +| **Keyring file** *(not recommended)* | Local key file for dev/test only. | [Configure keyring file →](keyring.md) | diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md index 1d4e55788d5df..781ce5869ddba 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/set-principal-key.md @@ -1,10 +1,16 @@ # Global Principal Key configuration -You can configure a default principal key using a global key provider. This key will be used by all databases that do not have their own encryption keys configured. The function **both** sets the principal key and rotates internal keys as needed. +You can configure a default principal key using a global key provider. This key is used by all databases that do not have their own encryption keys configured. + +There are two main functions for this: + +- [pg_tde_create_key_using_global_key_provider()](../functions.md#pg_tde_create_key_using_global_key_provider) creates a principal key at a global key provider +- [pg_tde_set_default_key_using_global_key_provider()](../functions.md#pg_tde_set_default_key_using_global_key_provider) sets the default principal key and rotates the internal encryption key if one is already configured ## Create a default principal key !!! note + The sample output below is for demonstration purposes only. Be sure to replace the key name and provider with your actual values. To create a global principal key, run: @@ -57,6 +63,7 @@ SELECT pg_tde_set_default_key_using_global_key_provider( * `global_vault_provider` is the name of the global key provider you previously configured. !!! note + If no error is reported, the action completed successfully. ## How key generation works @@ -64,8 +71,11 @@ SELECT pg_tde_set_default_key_using_global_key_provider( The key material (actual cryptographic key) is auto-generated by `pg_tde` and stored securely by the configured provider. !!! note + This process sets the **default principal key for the entire server**. Any database without a key explicitly configured will fall back to this key. ## Next steps +To confirm that encryption is working as expected, follow the validation steps: + [Validate Encryption with pg_tde :material-arrow-right:](../test.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md index 00bac8074d457..ee913bc991d6d 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/vault.md @@ -1,6 +1,6 @@ # Vault configuration -You can configure `pg_tde` to use HashiCorp Vault as a global key provider for managing encryption keys securely. +You can configure `pg_tde` to use HashiCorp Vault as a global key provider for managing encryption keys securely. Both the open source and enterprise editions are supported. !!! note This guide assumes that your Vault server is already set up and accessible. Vault configuration is outside the scope of this document, see [Vault's official documentation](https://developer.hashicorp.com/vault/docs) for more information. diff --git a/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md index 8bce6c474c017..64e422e485633 100644 --- a/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md +++ b/contrib/pg_tde/documentation/docs/index/how-does-tde-work.md @@ -6,15 +6,9 @@ To encrypt the data, two types of keys are used: * The **principal key** to encrypt database keys. It is kept separately from the database keys and is managed externally in the key management store. !!! note + For more information on managing and storing principal keys externally, including supported key management systems and the local keyring option, see [Key management overview](../global-key-provider-configuration/overview.md). - For more information on managing and storing principal keys externally, see [Configure Global Key Provider](../global-key-provider-configuration/overview.md). - -You have the following options to store and manage principal keys externally: - -* Use the HashiCorp Vault server. Only the back end KV Secrets Engine - Version 2 (API) is supported. -* Use the KMIP-compatible server. `pg_tde` has been tested with the [PyKMIP](https://pykmip.readthedocs.io/en/latest/server.html) server and [the HashiCorp Vault Enterprise KMIP Secrets Engine](https://www.vaultproject.io/docs/secrets/kmip). - -The encryption process is the following: +The encryption process works as follows: ![image](../_images/tde-flow.png) diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index 8e3376671aea7..5b4e432872220 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -1,8 +1,99 @@ # Limitations of pg_tde -The following are current limitations of `pg_tde`: +Limitations of `pg_tde` {{release}}: -* System tables, which include statistics data and database statistics, are currently **not encrypted**. -* The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. +* PostgreSQL’s internal system tables, which include statistics and metadata, are not encrypted. +* Temporary files created when queries exceed `work_mem` are not encrypted. These files may persist during long-running queries or after a server crash which can expose sensitive data in plaintext on disk. + +## Currently unsupported WAL tools + +The following tools are currently unsupported with `pg_tde` WAL encryption: + +* `pg_createsubscriber` +* `pg_verifybackup` (checksum mismatch with encrypted WAL) + +The following tools and extensions in Percona Distribution for PostgreSQL have been tested and verified to work with `pg_tde` WAL encryption: + +## Supported WAL tools + +The following tools have been tested and verified by Percona to work with `pg_tde` WAL encryption: + +* Patroni, for an example configuration see the following [Patroni configuration file](#example-patroni-configuration) +* `pg_basebackup` (with `--wal-method=stream` or `--wal-method=none`), for details on using `pg_basebackup` with WAL encryption, see [Backup with WAL encryption enabled](../how-to/backup-wal-enabled.md) +* `pg_resetwal` +* `pg_rewind` +* `pg_upgrade` +* `pg_waldump` +* pgBackRest + +## Example Patroni configuration + +The following is a Percona-tested example configuration. + +??? example "Click to expand the Percona-tested Patroni configuration" + ```yaml + # Example Patroni configuration file maintained by Percona + # Source: https://github.com/jobinau/pgscripts/blob/main/patroni/patroni.yml + scope: postgres + namespace: /db/ + name: postgresql0 + + restapi: + listen: 0.0.0.0:8008 + connect_address: 127.0.0.1:8008 + + etcd: + host: 127.0.0.1:2379 + + bootstrap: + dcs: + ttl: 30 + loop_wait: 10 + retry_timeout: 10 + maximum_lag_on_failover: 1048576 + postgresql: + use_pg_rewind: true + use_slots: true + parameters: + max_connections: 100 + shared_buffers: 1GB + wal_level: replica + hot_standby: "on" + wal_keep_size: 256MB + max_wal_senders: 10 + max_replication_slots: 10 + + initdb: + - encoding: UTF8 + - data-checksums + + pg_hba: + - host replication replicator 127.0.0.1/32 md5 + - host all all 0.0.0.0/0 md5 + + postgresql: + listen: 0.0.0.0:5432 + connect_address: 127.0.0.1:5432 + data_dir: /var/lib/postgresql/data + bin_dir: /usr/lib/postgresql/14/bin + authentication: + replication: + username: replicator + password: rep-pass + superuser: + username: postgres + password: secretpassword + ``` + +!!! warning + The above example is Percona-tested, but Patroni versions differ, especially with discovery backends such as `etcd`. Ensure you adjust the configuration to match your environment, version, and security requirements. + +## Next steps + +Check which PostgreSQL versions and deployment types are compatible with `pg_tde` before planning your installation. [View the versions and supported deployments :material-arrow-right:](supported-versions.md){.md-button} + +Begin the installation process when you're ready to set up encryption. + +[Start installing `pg_tde`](../install.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/test.md b/contrib/pg_tde/documentation/docs/test.md index be73e73863e76..e4578e6bf6ed3 100644 --- a/contrib/pg_tde/documentation/docs/test.md +++ b/contrib/pg_tde/documentation/docs/test.md @@ -59,4 +59,4 @@ ALTER TABLE table_name SET ACCESS METHOD tde_heap; ## Next steps -[Configure WAL Encryption (tech preview) :material-arrow-right:](wal-encryption.md){.md-button} +[Configure WAL encryption :material-arrow-right:](wal-encryption.md){.md-button} diff --git a/contrib/pg_tde/documentation/docs/wal-encryption.md b/contrib/pg_tde/documentation/docs/wal-encryption.md index 33547d0e359c4..6de83230c11de 100644 --- a/contrib/pg_tde/documentation/docs/wal-encryption.md +++ b/contrib/pg_tde/documentation/docs/wal-encryption.md @@ -1,7 +1,4 @@ -# Configure WAL Encryption (tech preview) - -!!! warning - The WAL encryption feature is currently in beta and is not effective unless explicitly enabled. It is not yet production ready. **Do not enable this feature in production environments**. +# Configure WAL encryption Before enabling WAL encryption, follow the steps below to create a principal key and configure it for WAL: @@ -118,3 +115,5 @@ Now WAL files start to be encrypted for both encrypted and unencrypted tables. For more technical references related to architecture, variables or functions, see: [Technical Reference](advanced-topics/tech-reference.md){.md-button} + +💬 Need help customizing this for your infrastructure? [Contact Percona support :octicons-link-external-16:](get-help.md) \ No newline at end of file diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 39c1c4eec0330..388d3672fd983 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -185,7 +185,7 @@ nav: - "Keyring file configuration": global-key-provider-configuration/keyring.md - "2.2 Global Principal Key configuration": global-key-provider-configuration/set-principal-key.md - "3. Validate encryption with pg_tde": test.md - - "4. Configure WAL encryption (tech preview)": wal-encryption.md + - "4. Configure WAL encryption": wal-encryption.md - "Technical reference": - "Overview": advanced-topics/tech-reference.md - "Architecture": architecture/architecture.md From acaddab9ab83da479589ecef49892c5823042770 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 22 Aug 2025 13:40:27 +0300 Subject: [PATCH 603/796] PG-1858 Document backing up with WAL with encrypt enabled (#534) - add new topic called # Backing up with WAL encryption enabled - add two suptopics for other wal methods and restore backup created with wal encrypt - reword to short form option flags --- .../docs/how-to/backup-wal-enabled.md | 44 +++++++++++++++++++ contrib/pg_tde/documentation/mkdocs.yml | 1 + 2 files changed, 45 insertions(+) create mode 100644 contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md diff --git a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md new file mode 100644 index 0000000000000..25a5342b1b442 --- /dev/null +++ b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md @@ -0,0 +1,44 @@ +# Backup with WAL encryption enabled + +To create a backup with WAL encryption enabled: + +1. Copy the `pg_tde` directory from the source server’s data directory, for example `/var/lib/postgresql/data/pg_tde/`, including the `wal_keys` and `1664_providers` files, to the backup destination directory where `pg_basebackup` will write the backup. + +Also copy any external files referenced by your providers configuration (such as certificate or key files) into the same relative paths under the backup destination, so that they are located and validated by `pg_basebackup -E`. + +2. Run: + + ```bash + pg_basebackup -D /path/to/backup -E + ``` + + Where: + + - `-D /path/to/backup` specifies the backup location where you have to copy `pg_tde` + - `-E` (or `--encrypt-wal`) enables WAL encryption and validates that the copied `pg_tde` and provider files are present and that the server key is accessible (required) + +!!! note + - The `-E` flag only works with the `-X stream` option (default). It is not compatible with `-X none` or `-X fetch`. For more information, see [the other WAL methods topic](#other-wal-methods). + - The `-E` flag is only supported with the plain output format (`-F p`). It cannot be used with the tar output format (`-F t`). + +## Restore a backup created with WAL encryption + +When you want to restore a backup created with `pg_basebackup -E`: + +1. Ensure all external files referenced by your providers configuration (such as certificates or key files) are also present and accessible at the same relative paths. +2. Start PostgreSQL with the restored data directory. + +## Other WAL methods + +The `-X fetch` option works with encrypted WAL without requiring any additional flags. +The `-X none` option excludes WAL from the backup and is unaffected by WAL encryption. + +If the source server has `pg_tde/wal_keys`, running `pg_basebackup` with `-X none` or `-X fetch` produces warnings such as: + +```sql +pg_basebackup: warning: the source has WAL keys, but no WAL encryption configured for the target backups +pg_basebackup: detail: This may lead to exposed data and broken backup +pg_basebackup: hint: Run pg_basebackup with -E to encrypt streamed WAL +``` + +You can safely ignore these warnings when using `-X none` or `-X fetch`, since in both cases WAL is not streamed. diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 388d3672fd983..5ffd25e3236ba 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -204,6 +204,7 @@ nav: - "Configure Multi-tenancy": how-to/multi-tenant-setup.md - "Encryption Enforcement": how-to/enforcement.md - "Decrypt an Encrypted Table": how-to/decrypt.md + - "Backup with WAL encryption enabled": how-to/backup-wal-enabled.md - "Restore an encrypted pg_tde backup": how-to/restore-backups.md - faq.md - "Release notes": From 6719db5704a43e2274cb9d0e965142e5391b42fe Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 25 Aug 2025 19:55:25 +0300 Subject: [PATCH 604/796] Update WAL backup topic regarding TAR not supporting `-X stream` when WAL encryption is enabled (#548) - renamed title, reorg content for easier scanning - turned into note the `pg_tde/wal_keys` at the end --- .../pg-tde-archive-decrypt.md | 6 ++- .../pg-tde-restore-encrypt.md | 3 ++ .../docs/how-to/backup-wal-enabled.md | 41 +++++++++++++------ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md index 0b63ab5212dcf..26a80eda80558 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-archive-decrypt.md @@ -3,8 +3,7 @@ The `pg_tde_archive_decrypt` tool wraps an archive command and decrypts WAL files before archiving. It allows external tools to access unencrypted WAL data, which is required because WAL encryption keys in the two-key hierarchy are host-specific and may not be available on the replay host. !!! tip - - For more information on the encryption architecture and key hierarchy, see [Architecture](../architecture/architcture.md). + For more information on the encryption architecture and key hierarchy, see [Architecture](../architecture/architecture.md). This tool is often used in conjunction with [pg_tde_restore_encrypt](./pg-tde-restore-encrypt.md) to support WAL archive. @@ -53,3 +52,6 @@ archive_command='pg_tde_archive_decrypt %f %p "cp %%p /mnt/server/archivedir/%%f ```ini archive_command='pg_tde_archive_decrypt %f %p "pgbackrest --stanza=your_stanza archive-push %%p"' ``` + +!!! warning + When using PgBackRest with WAL encryption, disable PostgreSQL data checksums. Otherwise, PgBackRest may spam error messages, and in some package builds the log statement can cause crashes. diff --git a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md index 7435de2d0b761..8ea0993404c47 100644 --- a/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md +++ b/contrib/pg_tde/documentation/docs/command-line-tools/pg-tde-restore-encrypt.md @@ -47,3 +47,6 @@ restore_command='pg_tde_restore_encrypt %f %p "cp /mnt/server/archivedir/%%f %%p ```ini restore_command='pg_tde_restore_encrypt %f %p "pgbackrest --stanza=your_stanza archive-get %%f \"%%p\""' ``` + +!!! warning + When using PgBackRest with WAL encryption, disable PostgreSQL data checksums. Otherwise, PgBackRest may spam error messages, and in some package builds the log statement can cause crashes. diff --git a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md index 25a5342b1b442..a3214d9a6a39c 100644 --- a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md +++ b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md @@ -14,8 +14,8 @@ Also copy any external files referenced by your providers configuration (such as Where: - - `-D /path/to/backup` specifies the backup location where you have to copy `pg_tde` - - `-E` (or `--encrypt-wal`) enables WAL encryption and validates that the copied `pg_tde` and provider files are present and that the server key is accessible (required) + - `-D /path/to/backup` specifies the backup location where you have to copy `pg_tde`. + - `-E` (or `--encrypt-wal`) enables WAL encryption and validates that the copied `pg_tde` and provider files are present and that the server key is accessible (required). !!! note - The `-E` flag only works with the `-X stream` option (default). It is not compatible with `-X none` or `-X fetch`. For more information, see [the other WAL methods topic](#other-wal-methods). @@ -28,17 +28,34 @@ When you want to restore a backup created with `pg_basebackup -E`: 1. Ensure all external files referenced by your providers configuration (such as certificates or key files) are also present and accessible at the same relative paths. 2. Start PostgreSQL with the restored data directory. -## Other WAL methods +## Backup method compatibility with WAL encryption -The `-X fetch` option works with encrypted WAL without requiring any additional flags. -The `-X none` option excludes WAL from the backup and is unaffected by WAL encryption. +Tar format (`-F t`): -If the source server has `pg_tde/wal_keys`, running `pg_basebackup` with `-X none` or `-X fetch` produces warnings such as: +* Works with `-X fetch`. +* Does not support `-X stream` when WAL encryption is enabled. Using `pg_basebackup -F t -X stream` will create a broken replica. -```sql -pg_basebackup: warning: the source has WAL keys, but no WAL encryption configured for the target backups -pg_basebackup: detail: This may lead to exposed data and broken backup -pg_basebackup: hint: Run pg_basebackup with -E to encrypt streamed WAL -``` +Streaming mode (`-X stream`): -You can safely ignore these warnings when using `-X none` or `-X fetch`, since in both cases WAL is not streamed. +* **Must** specify `-E` (`--encrypt-wal`). +* Without `-E`, backups may contain decrypted WAL while `wal_encryption=on` remains in `postgresql.conf` and `pg_tde/wal_keys` are copied. This leads to **startup failures and compromised data in the backup**. + +Fetch mode (`-X fetch`): + +* Compatible with encrypted WAL without requiring any additional flags. + +None (`-X none`): + +* Excludes WAL from the backup and is unaffected by WAL encryption. + + +!!! note + If the source server has `pg_tde/wal_keys`, running `pg_basebackup` with `-X none` or `-X fetch` produces warnings such as: + + ```sql + pg_basebackup: warning: the source has WAL keys, but no WAL encryption configured for the target backups + pg_basebackup: detail: This may lead to exposed data and broken backup + pg_basebackup: hint: Run pg_basebackup with -E to encrypt streamed WAL + ``` + + You can safely ignore the warnings with `-X none` or `-X fetch`, since no WAL streaming occurs. From fb543801dc978f61bb39bfbaadf1990751f0a5ec Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Tue, 26 Aug 2025 11:19:29 +0300 Subject: [PATCH 605/796] Add WAL release note for 2.0 release (#482) - add new date variable for 2.0 release - populated with feedback after code freeze and team comments --- .../docs/release-notes/release-notes-v1.0.md | 6 +- .../docs/release-notes/release-notes-v2.0.md | 74 +++++++++++++++++++ .../docs/release-notes/release-notes.md | 1 + .../docs/templates/pdf_cover_page.tpl | 2 +- contrib/pg_tde/documentation/mkdocs.yml | 8 +- contrib/pg_tde/documentation/variables.yml | 7 +- 6 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md index e5035a8a1d66e..8a62726818ccf 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v1.0.md @@ -1,6 +1,6 @@ # pg_tde 1.0 ({{date.GA10}}) -The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/index.md) to PostgreSQL and enables you to keep sensitive data safe and secure. +The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. [Get Started](../install.md){.md-button} @@ -8,7 +8,7 @@ The `pg_tde` by Percona extension brings in [Transparent Data Encryption (TDE) : * **`pg_tde` 1.0 is now GA (Generally Available)** -And **stable** for encrypting relational data in PostgreSQL using [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/index.md). This milestone brings production-level data protection to PostgreSQL workloads. +And **stable** for encrypting relational data in PostgreSQL using [Transparent Data Encryption (TDE) :octicons-link-external-16:](../index/about-tde.md). This milestone brings production-level data protection to PostgreSQL workloads. * **WAL encryption is still in Beta** @@ -16,7 +16,7 @@ The WAL encryption feature is currently still in beta and is not effective unles ## Upgrade considerations -`pg_tde` ({{tdeversion}}) is **not** backward compatible with previous `pg_tde` versions, like Release Candidate 2, due to significant changes in code. This means you **cannot** directly upgrade from one version to another. You must do **a clean installation** of `pg_tde`. +`pg_tde` 1.0 is **not** backward compatible with previous `pg_tde` versions, like Release Candidate 2, due to significant changes in code. This means you **cannot** directly upgrade from one version to another. You must do **a clean installation** of `pg_tde`. ## Known issues diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md new file mode 100644 index 0000000000000..b70e1765ec15c --- /dev/null +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md @@ -0,0 +1,74 @@ +# pg_tde 2.0 ({{date.GA20}}) + +The `pg_tde` by Percona extension brings [Transparent Data Encryption (TDE)](../index/about-tde.md) to PostgreSQL and enables you to keep sensitive data safe and secure. + +[Get Started](../install.md){.md-button} + +## Release Highlights + +### WAL encryption is now generally available + +The WAL (Write-Ahead Logging) encryption feature is now fully supported and production-ready, it adds secure write-ahead logging to `pg_tde`, expanding Percona's PostgreSQL encryption coverage by enabling secure, transparent encryption of write-ahead logs using the same key infrastructure as data encryption. + +### WAL encryption upgrade limitation + +Clusters that used WAL encryption in the beta release (`pg_tde` 1.0 or older) cannot be upgraded to `pg_tde` 2.0. The following error indicates that WAL encryption was enabled: + +```sql +FATAL: principal key not configured +HINT: Use pg_tde_set_server_key_using_global_key_provider() to configure one. +``` + +Clusters that did not use WAL encryption in beta can be upgraded normally. + +### Documentation updates + +* Updated the [Limitations](../index/tde-limitations.md) topic, it now includes WAL encryption limitations and both supported and unsupported WAL tools +* [PG-1858 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1858) - Added a new topic for [Backup with WAL encryption enabled](../how-to/backup-wal-enabled.md) that includes restoring a backup created with WAL encryption +* [PG-1832 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1858) - Added documentation for using the `pg_tde_archive_decrypt` and `pg_tde_restore_encrypt` utilities. These tools are now covered in [CLI Tools](../command-line-tools/cli-tools.md) to guide users on how to archive and restore encrypted WAL segments securely +* [PG-1740 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1740) - Updated documentation for [uninstalling `pg_tde`](../how-to/uninstall.md) with WAL encryption enabled and improved the uninstall instructions to cover cases where TDE is disabled while WAL encryption remains active + +## Known issues + +* The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. + +To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: + +* temporarily for the current session using the `ulimit -l ` command. +* set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + +Adjust the limits with caution since it affects other processes running in your system. + +## Changelog + +### New Features + +* [PG-1497 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1497) WAL encryption is now generally available (GA) +* [PG-1037 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1037) - Added support for `pg_rewind` with encrypted WAL +* [PG-1411 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1497) - Added support for `pg_resetwal` with encrypted WAL +* [PG-1603 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1603) - Added support for `pg_basebackup` with encrypted WAL +* [PG-1710 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1710) - Added support for WAL archiving with encrypted WAL +* [PG-1711 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1711) - Added support for incremental backups with encrypted WAL, compatibility has been verified with `pg_combinebackup` and the WAL summarizer tool. +* [PG-1712 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1712) - Added support for `pg_createsubscriber` with encrypted WAL +* [PG-1833 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1833) - Added verified support for using `pg_waldump` with encrypted WAL +* [PG-1834 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1834) - Verified `pg_upgrade` with encryption + +### Improvements + +* [PG-1661 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1661) - Added validation for key material received from providers +* [PG-1667 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1667) - Validated Vault keyring engine type + +### Bugs Fixed + +* [PG-1391 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1391) - Fixed unencrypted checkpoint segment on replica with encrypted key +* [PG-1412 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1412) – Fixed an issue where `XLogFileCopy` failed with encrypted WAL during PITR and `pg_rewind` +* [PG-1452 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1452) - Fixed an issue where `pg_tde_change_key_provider` did not work without the `-D` flag even if `PGDATA` was set +* [PG-1485 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1485) - Fixed an issue where streaming replication failed with an invalid magic number in WAL when `wal_encryption` was enabled +* [PG-1604 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1604) - Fixed a crash during standby promotion caused by an invalid magic number when replaying two-phase transactions from WAL +* [PG-1658 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1658) - Fixed an issue where the global key provider could not be deleted after server restart +* [PG-1835 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1835) - Fixed an issue where `pg_resetwal` corrupted encrypted WAL, causing PostgreSQL to fail at startup with an invalid checkpoint +* [PG-1842 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1842) - Fixed a delay in replica startup with encrypted tables in streaming replication setups +* [PG-1843 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1843) - Fixed performance issues when creating encrypted tables +* [PG-1863 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1863) - Fixed an issue where unnecessary WAL was generated when creating temporary tables +* [PG-1866 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1866) - Fixed an issue where automatic restart after crash sometimes failed with WAL encryption enabled +* [PG-1867 :octicons-link-external-16:](https://perconadev.atlassian.net/browse/PG-1867) - Fixed archive recovery with encrypted WAL diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md index 38d8536e2ba4f..9f50fcba58c75 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes.md @@ -4,6 +4,7 @@ This page lists all release notes for `pg_tde`, organized by year and version. U ## 2025 +* [2.0](release-notes-v2.0.md) ({{date.GA20}}) * [1.0](release-notes-v1.0.md) ({{date.GA10}}) * [Release Candidate 2 (RC2)](rc2.md) ({{date.RC2}}) * [Release Candidate 1 (RC1)](rc.md) ({{date.RC}}) diff --git a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl index 9f4fb22ba71de..28d0a506f1b1f 100644 --- a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl +++ b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl @@ -7,5 +7,5 @@ {% if config.site_description %}

{{ config.site_description }}

{% endif %} -

1.0 (2025-06-30)

+

2.0 (2025-08-30)

\ No newline at end of file diff --git a/contrib/pg_tde/documentation/mkdocs.yml b/contrib/pg_tde/documentation/mkdocs.yml index 5ffd25e3236ba..bdb43ccb5ed75 100644 --- a/contrib/pg_tde/documentation/mkdocs.yml +++ b/contrib/pg_tde/documentation/mkdocs.yml @@ -209,10 +209,10 @@ nav: - faq.md - "Release notes": - "Release notes index": release-notes/release-notes.md - - "2025": - - "1.0": release-notes/release-notes-v1.0.md - - "Release Candidate 2": release-notes/rc2.md - - "Release Candidate 1": release-notes/rc.md + - "2.0": release-notes/release-notes-v2.0.md + - "1.0": release-notes/release-notes-v1.0.md + - "Release Candidate 2": release-notes/rc2.md + - "Release Candidate 1": release-notes/rc.md - "2024 (Alpha 1 - Beta 2)": - "Beta 2": release-notes/beta2.md - "Beta": release-notes/beta.md diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index bf698a07c2272..a61f6cb088753 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -1,12 +1,13 @@ #Variables used throughout the docs -latestreleasenotes: 'release-notes-v1.0' -tdeversion: '1.0' -release: '1.0' +latestreleasenotes: 'release-notes-v2.0' +tdeversion: '2.0' +release: '2.0' pgversion17: '17.5' tdebranch: release-17.5.2 date: + GA20: '2025-08-30' GA10: '2025-06-30' RC2: '2025-05-29' RC: '2025-03-27' From a711d8befab9f99cf5f14fb930eb7170360ea489 Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Mon, 25 Aug 2025 20:42:29 +0300 Subject: [PATCH 606/796] Fix possible _keys file loss during key rotation There is no reason to do durable_unlink before durable_rename. Rename can handle existing file. But with this sequence, the cluster may endup in unrecoverable state should server crash in-between this two ops, as there is going to be no "_keys" at all. The current sequence may also cause an issue the backup: , , . And no "_keys" file in the backup as the result. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 3 +-- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 1a9e118771c49..082a9ad8f2fd5 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -297,10 +297,9 @@ pg_tde_perform_rotate_key(const TDEPrincipalKey *principal_key, const TDEPrincip CloseTransientFile(new_fd); /* - * Do the final steps - replace the current _map with the file with new + * Do the final step - replace the current _keys with the file with new * data */ - durable_unlink(old_path, ERROR); durable_rename(new_path, old_path, ERROR); /* diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 4c14625e0080f..f1c64aa5a32b2 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -718,10 +718,9 @@ pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, CloseTransientFile(new_fd); /* - * Do the final steps - replace the current WAL key file with the file - * with new data. + * Do the final step - replace the current WAL key file with the file with + * new data. */ - durable_unlink(get_wal_key_file_path(), ERROR); durable_rename(tmp_path, get_wal_key_file_path(), ERROR); /* From 2364be29cc8ae5b538adcb2bc2d87870e2b70ecb Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Tue, 26 Aug 2025 16:37:35 +0300 Subject: [PATCH 607/796] Fix XLogging of rotated key Before this commit, we XLogged the provider ID (keyringId) of the old key. Yet, we then attempt to fetch the new key from the old provider during the Redo, which obviously fails and crashes the recovery. So the next steps lead to the recovery stalemate: - Create new provider (with new destination - mount_path, url etc). - Create new server/global key. - Rotate key. - This commit fixes it by Xlogging the new key's provider ID. For: PG-1895 --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 4 ++-- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 082a9ad8f2fd5..1a910f19d39b7 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -317,8 +317,8 @@ pg_tde_perform_rotate_key(const TDEPrincipalKey *principal_key, const TDEPrincip { XLogPrincipalKeyRotate xlrec; - xlrec.databaseId = principal_key->keyInfo.databaseId; - xlrec.keyringId = principal_key->keyInfo.keyringId; + xlrec.databaseId = new_principal_key->keyInfo.databaseId; + xlrec.keyringId = new_principal_key->keyInfo.keyringId; memcpy(xlrec.keyName, new_principal_key->keyInfo.name, sizeof(new_principal_key->keyInfo.name)); XLogBeginInsert(); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index f1c64aa5a32b2..fc558b8479817 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -738,8 +738,8 @@ pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, { XLogPrincipalKeyRotate xlrec; - xlrec.databaseId = principal_key->keyInfo.databaseId; - xlrec.keyringId = principal_key->keyInfo.keyringId; + xlrec.databaseId = new_principal_key->keyInfo.databaseId; + xlrec.keyringId = new_principal_key->keyInfo.keyringId; memcpy(xlrec.keyName, new_principal_key->keyInfo.name, sizeof(new_principal_key->keyInfo.name)); XLogBeginInsert(); From 532d264054184751dd3cdb24c28f0e633643fe3f Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 28 Aug 2025 09:29:51 +0300 Subject: [PATCH 608/796] Remove an extra s from param and remove ensure_new_key param (#555) --- contrib/pg_tde/documentation/docs/functions.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/functions.md b/contrib/pg_tde/documentation/docs/functions.md index 82e6e3449a2de..2452674a67086 100644 --- a/contrib/pg_tde/documentation/docs/functions.md +++ b/contrib/pg_tde/documentation/docs/functions.md @@ -229,7 +229,7 @@ Use these functions to create a new principal key at a given keyprover, and to u Principal keys are stored on key providers by the name specified in this function - for example, when using the Vault provider, after creating a key named "foo", a key named "foo" will be visible on the Vault server at the specified mount point. -### pg_tde_creates_key_using_database_key_provider +### pg_tde_create_key_using_database_key_provider Creates a principal key using the database-local key provider with the specified name. Use this key later with [`pg_tde_set_key_using_database_key_provider()`](#pg_tde_set_key_using_database_key_provider). @@ -286,12 +286,6 @@ SELECT pg_tde_set_server_key_using_global_key_provider( ); ``` -The `ensure_new_key` parameter instructs the function how to handle a principal key during key rotation: - -* If set to `true`, a new key must be unique. - If the provider already stores a key by that name, the function returns an error. -* If set to `false` (default), an existing principal key may be reused. - ### pg_tde_set_default_key_using_global_key_provider Sets or rotates the default principal key for the server using the specified global key provider. From afdbffb422bf3e5f28d988ae86ce16b16600924e Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 28 Aug 2025 10:31:44 +0300 Subject: [PATCH 609/796] Add information regarding key rotation during backups for pg_basebackup making servers fail to start (#550) - add as known issue in release notes - fix a broken link in features.md (not related to issue...) - add to global key providers a warning about keyring provider with WAL encrypt - add new subtopic in Backup WAL about key rotations during backups for file-based key providers Based on PG-1895 description. --- contrib/pg_tde/documentation/docs/features.md | 2 +- .../overview.md | 3 +++ .../docs/how-to/backup-wal-enabled.md | 16 ++++++++++++++++ .../docs/release-notes/release-notes-v2.0.md | 16 ++++++++++++---- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/features.md b/contrib/pg_tde/documentation/docs/features.md index 3f51c25aceaac..1db6fc24f09a1 100644 --- a/contrib/pg_tde/documentation/docs/features.md +++ b/contrib/pg_tde/documentation/docs/features.md @@ -17,7 +17,7 @@ The following features are available for the extension: * Single-tenancy support via a [global keyring provider](global-key-provider-configuration/set-principal-key.md) * [Multi-tenancy support](how-to/multi-tenant-setup.md) * Table-level granularity for encryption and access control -* Multiple [Key management options](global-key-provider-configuration/index.md) +* Multiple [Key management options](global-key-provider-configuration/overview.md) ## Next steps diff --git a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md index 14aa13e569c6b..a6c7e4703fd3e 100644 --- a/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md +++ b/contrib/pg_tde/documentation/docs/global-key-provider-configuration/overview.md @@ -11,6 +11,9 @@ To use an external KMS with `pg_tde`, follow these two steps: !!! note While key files may be acceptable for **local** or **testing environments**, KMS integration is the recommended approach for production deployments. +!!! warning + Do not rotate encryption keys while `pg_basebackup` is running. Standbys or standalone clusters created from such backups may fail to start during WAL replay. Schedule rotations outside your backup windows and run a new full backup afterward. + `pg_tde` has been tested with the following key providers: | KMS Provider | Description | Documentation | diff --git a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md index a3214d9a6a39c..97c2804b40ff2 100644 --- a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md +++ b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md @@ -21,6 +21,22 @@ Also copy any external files referenced by your providers configuration (such as - The `-E` flag only works with the `-X stream` option (default). It is not compatible with `-X none` or `-X fetch`. For more information, see [the other WAL methods topic](#other-wal-methods). - The `-E` flag is only supported with the plain output format (`-F p`). It cannot be used with the tar output format (`-F t`). +## Key rotation during backups + +!!! warning + Do not rotate SMGR or WAL encryption keys while `pg_basebackup` is running. Standbys or standalone clusters created from such backups may fail to start during WAL replay. + +Rotations during a base backup can leave the standby in an inconsistent state where it cannot retrieve the correct key history. + +For example, you may see errors such as: + +```sql +FATAL: failed to retrieve principal key "database_keyXXXX" from key provider "providerYYYY" +CONTEXT: WAL redo at ... ROTATE_PRINCIPAL_KEY ... +``` + +To ensure standby recoverability, plan key rotations outside backup windows or take a new full backup after rotation completes. + ## Restore a backup created with WAL encryption When you want to restore a backup created with `pg_basebackup -E`: diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md index b70e1765ec15c..22c74ba52d346 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md @@ -30,14 +30,22 @@ Clusters that did not use WAL encryption in beta can be upgraded normally. ## Known issues +* Rotating encryption keys while `pg_basebackup` is running may cause standbys or standalone clusters initialized from the backup to fail during WAL replay. + + Avoid key rotations during backups. Run a new full backup after completing a rotation. + +* Using `pg_basebackup` with `--wal-method=fetch` produces warnings. + + This behavior is expected and will be addressed in a future release. + * The default `mlock` limit on Rocky Linux 8 for ARM64-based architectures equals the memory page size and is 64 Kb. This results in the child process with `pg_tde` failing to allocate another memory page because the max memory limit is reached by the parent process. -To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: + To prevent this, you can change the `mlock` limit to be at least twice bigger than the memory page size: -* temporarily for the current session using the `ulimit -l ` command. -* set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. + * temporarily for the current session using the `ulimit -l ` command. + * set a new hard limit in the `/etc/security/limits.conf` file. To do so, you require the superuser privileges. -Adjust the limits with caution since it affects other processes running in your system. + Adjust the limits with caution since it affects other processes running in your system. ## Changelog From 89f5000235e0e260e8c2fbf1a42825cf5b0bd11b Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 28 Aug 2025 15:13:19 +0300 Subject: [PATCH 610/796] Update Patroni config file (#551) The original config file was taken from [here](https://github.com/jobinau/pgscripts/blob/main/patroni/patroni.yml) and it is currently replaced with a more "up to date" version [here](https://github.com/Percona-Lab/pg_tde-demo-cluster/blob/bbb1df011f79667e7d8960191a775da90574e2c5/templates/patroni.yml.j2). --- .../docs/index/tde-limitations.md | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index 5b4e432872220..fb9e5de0b4920 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -34,17 +34,13 @@ The following is a Percona-tested example configuration. ```yaml # Example Patroni configuration file maintained by Percona # Source: https://github.com/jobinau/pgscripts/blob/main/patroni/patroni.yml - scope: postgres - namespace: /db/ - name: postgresql0 - + scope: tde + name: pg1 restapi: listen: 0.0.0.0:8008 - connect_address: 127.0.0.1:8008 - - etcd: - host: 127.0.0.1:2379 - + connect_address: pg1:8008 + etcd3: + host: etcd1:2379 bootstrap: dcs: ttl: 30 @@ -55,27 +51,29 @@ The following is a Percona-tested example configuration. use_pg_rewind: true use_slots: true parameters: - max_connections: 100 - shared_buffers: 1GB - wal_level: replica - hot_standby: "on" - wal_keep_size: 256MB - max_wal_senders: 10 - max_replication_slots: 10 - + archive_command: "/lib/postgresql/17/bin/pg_tde_archive_decrypt %f %p \"pgbackrest --stanza=tde archive-push %%p\"" + archive_timeout: 600s + archive_mode: "on" + logging_collector: "on" + restore_command: "/lib/postgresql/17/bin/pg_tde_restore_encrypt %f %p \"pgbackrest --stanza=tde archive-get %%f \\\"%%p\\\"\"" + pg_hba: + - local all all peer + - host all all 0.0.0.0/0 scram-sha-256 + - host all all ::/0 scram-sha-256 + - local replication all peer + - host replication all 0.0.0.0/0 scram-sha-256 + - host replication all ::/0 scram-sha-256 initdb: - - encoding: UTF8 - - data-checksums - - pg_hba: - - host replication replicator 127.0.0.1/32 md5 - - host all all 0.0.0.0/0 md5 - + - encoding: UTF8 + - data-checksums + - set: shared_preload_libraries=pg_tde + post_init: /usr/local/bin/setup_cluster.sh postgresql: listen: 0.0.0.0:5432 - connect_address: 127.0.0.1:5432 - data_dir: /var/lib/postgresql/data - bin_dir: /usr/lib/postgresql/14/bin + connect_address: pg1:5432 + data_dir: /var/lib/postgresql/patroni-17 + bin_dir: /lib/postgresql/17/bin + pgpass: /var/lib/postgresql/patronipass authentication: replication: username: replicator @@ -83,6 +81,16 @@ The following is a Percona-tested example configuration. superuser: username: postgres password: secretpassword + parameters: + unix_socket_directories: /tmp + # Use unix_socket_directories: /var/run/postgresql for Debian/Ubuntu distributions + watchdog: + mode: off + tags: + nofailover: false + noloadbalance: false + clonefrom: false + nosync: false ``` !!! warning From a3f36c89837c0881ee6813755325a1dd15c68b19 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Thu, 28 Aug 2025 15:37:16 +0300 Subject: [PATCH 611/796] Add pg_receivewal to list of unsupported tools with WAL encrypt (#556) --- contrib/pg_tde/documentation/docs/index/tde-limitations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index fb9e5de0b4920..3d0e95077b50f 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -10,6 +10,7 @@ Limitations of `pg_tde` {{release}}: The following tools are currently unsupported with `pg_tde` WAL encryption: * `pg_createsubscriber` +* `pg_receivewal` * `pg_verifybackup` (checksum mismatch with encrypted WAL) The following tools and extensions in Percona Distribution for PostgreSQL have been tested and verified to work with `pg_tde` WAL encryption: From d31c4892dc388e706e232244a5f06dcaa8b3a6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 25 Aug 2025 12:34:15 +0200 Subject: [PATCH 612/796] PG-1870 Load pg_tde and setup key in TAP tests This makes sure pg_tde is loaded and keys are setup when running postgresql TAP suite. No TDE features are enabled at this point. Single user mode is used to generate a template of pg_tde setup files which are then copied to each created cluster's data directory. --- src/bin/pg_rewind/t/002_databases.pl | 6 + src/bin/pg_upgrade/t/002_pg_upgrade.pl | 5 - src/test/perl/PostgreSQL/Test/Cluster.pm | 6 + src/test/perl/PostgreSQL/Test/TdeCluster.pm | 120 ++++++++++++++++++++ src/test/recovery/t/027_stream_regress.pl | 5 - 5 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 src/test/perl/PostgreSQL/Test/TdeCluster.pm diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl index 755ea80e332f9..cdc73b8e21069 100644 --- a/src/bin/pg_rewind/t/002_databases.pl +++ b/src/bin/pg_rewind/t/002_databases.pl @@ -11,6 +11,12 @@ use RewindTest; +if ($ENV{TDE_MODE} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_combinebackup doesn't set filemodes of pg_tde/ correctly?"; +} + sub run_test { my $test_mode = shift; diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 0431a7a977c42..78bd776f5be2f 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -15,11 +15,6 @@ use PostgreSQL::Test::AdjustUpgrade; use Test::More; -if (defined($ENV{TDE_MODE})) -{ - plan skip_all => "Running with TDE doesn't support special server starts yet"; -} - # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index f2d9afd398fbb..9db717dd90502 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -107,6 +107,7 @@ use File::Temp (); use IPC::Run; use PostgreSQL::Version; use PostgreSQL::Test::RecursiveCopy; +use PostgreSQL::Test::TdeCluster; use Socket; use Test::More; use PostgreSQL::Test::Utils (); @@ -1527,6 +1528,11 @@ sub new } } + if ($ENV{TDE_MODE}) + { + bless $node, 'PostgreSQL::Test::TdeCluster'; + } + # Add node to list of nodes push(@all_nodes, $node); diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm new file mode 100644 index 0000000000000..33cf5b9dfcc0c --- /dev/null +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -0,0 +1,120 @@ +package PostgreSQL::Test::TdeCluster; + +use parent 'PostgreSQL::Test::Cluster'; + +use strict; +use warnings FATAL => 'all'; + +use List::Util (); +use PostgreSQL::Test::RecursiveCopy (); +use PostgreSQL::Test::Utils (); + +our ($tde_template_dir); + +BEGIN +{ + $ENV{TDE_MODE_NOSKIP} = 0 unless defined($ENV{TDE_MODE_NOSKIP}); +} + +sub init +{ + my ($self, %params) = @_; + + $self->SUPER::init(%params); + + $self->SUPER::append_conf('postgresql.conf', + 'shared_preload_libraries = pg_tde'); + + $self->_tde_init_principal_key; + + return; +} + +sub append_conf +{ + my ($self, $filename, $str) = @_; + + if ($filename eq 'postgresql.conf' or $filename eq 'postgresql.auto.conf') + { + # TODO: Will not work with shared_preload_libraries= without any + # libraries, but no TAP test currently do that. + $str =~ + s/shared_preload_libraries *= *'?([^'\n]+)'?/shared_preload_libraries = 'pg_tde,$1'/; + } + + $self->SUPER::append_conf($filename, $str); +} + +sub pg_tde_dir +{ + my ($self) = @_; + return $self->data_dir . '/pg_tde'; +} + +sub _tde_init_principal_key +{ + my ($self) = @_; + + my $tde_template_dir = + $PostgreSQL::Test::Utils::tmp_check . '/pg_tde_template'; + + unless (-e $tde_template_dir) + { + my $temp_dir = PostgreSQL::Test::Utils::tempdir(); + mkdir $tde_template_dir; + + PostgreSQL::Test::Utils::system_log( + 'initdb', + '-D' => $temp_dir, + '--set' => 'shared_preload_libraries=pg_tde'); + + _tde_init_sql_command( + $temp_dir, 'postgres', qq( + CREATE EXTENSION pg_tde; + SELECT pg_tde_add_global_key_provider_file('global_test_provider', '$tde_template_dir/pg_tde_test_keys'); + SELECT pg_tde_create_key_using_global_key_provider('default_test_key', 'global_test_provider'); + SELECT pg_tde_set_default_key_using_global_key_provider('default_test_key', 'global_test_provider'); + )); + + PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', + $temp_dir . '/pg_tde', + $tde_template_dir); + } + + PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', + $tde_template_dir . '/pg_tde', + $self->pg_tde_dir); + + # We don't want clusters sharing the KMS file as any concurrent writes will + # mess it up. + PostgreSQL::Test::Utils::system_log( + 'cp', '-R', '-P', '-p', + $tde_template_dir . '/pg_tde_test_keys', + $self->basedir . '/pg_tde_test_keys'); + + PostgreSQL::Test::Utils::system_log( + 'pg_tde_change_key_provider', + '-D' => $self->data_dir, + '1664', + 'global_test_provider', + 'file', + $self->basedir . '/pg_tde_test_keys'); +} + +sub _tde_init_sql_command +{ + my ($datadir, $database, $sql) = @_; + PostgreSQL::Test::Utils::run_log( + [ + 'postgres', + '--single', '-j', '-F', + '-D' => $datadir, + '-c' => 'exit_on_error=true', + '-c' => 'log_checkpoints=false', + $database, + ], + '<', + \$sql); +} + +1; diff --git a/src/test/recovery/t/027_stream_regress.pl b/src/test/recovery/t/027_stream_regress.pl index 5178466fd0c4f..d1ae32d97d603 100644 --- a/src/test/recovery/t/027_stream_regress.pl +++ b/src/test/recovery/t/027_stream_regress.pl @@ -9,11 +9,6 @@ use Test::More; use File::Basename; -if (defined($ENV{TDE_MODE})) -{ - plan skip_all => "Running with TDE doesn't support special server starts yet"; -} - # Initialize primary node my $node_primary = PostgreSQL::Test::Cluster->new('primary'); $node_primary->init(allows_streaming => 1); From a2be026da62f729e31f0d1144b357c045e997d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 25 Aug 2025 12:55:38 +0200 Subject: [PATCH 613/796] PG-1870 Enable WAL encryption in TAP tests This enables WAL encryption by default when the TAP tests are run with TDE_MODE=1. Use TDE_MODE_WAL=0 to disable wal encryption while still having pg_tde enabled. --- src/bin/pg_basebackup/t/010_pg_basebackup.pl | 6 ++ src/bin/pg_combinebackup/t/003_timeline.pl | 6 ++ .../pg_combinebackup/t/006_db_file_copy.pl | 6 ++ src/bin/pg_combinebackup/t/008_promote.pl | 6 ++ src/bin/pg_rewind/t/001_basic.pl | 6 ++ src/bin/pg_verifybackup/t/009_extract.pl | 7 ++ src/bin/pg_waldump/t/001_basic.pl | 5 ++ src/bin/pg_waldump/t/002_save_fullpage.pl | 5 ++ src/test/perl/PostgreSQL/Test/TdeCluster.pm | 64 +++++++++++++++++++ src/test/recovery/t/039_end_of_wal.pl | 5 ++ src/test/recovery/t/042_low_level_backup.pl | 6 ++ .../recovery/t/043_no_contrecord_switch.pl | 5 ++ 12 files changed, 127 insertions(+) diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index 403acb6eb4115..54197a256fd4a 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -10,6 +10,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_basebackup without -E from server with encrypted WAL produces broken backups"; +} + program_help_ok('pg_basebackup'); program_version_ok('pg_basebackup'); program_options_handling_ok('pg_basebackup'); diff --git a/src/bin/pg_combinebackup/t/003_timeline.pl b/src/bin/pg_combinebackup/t/003_timeline.pl index b9c67341ad83a..c1ba2b323c303 100644 --- a/src/bin/pg_combinebackup/t/003_timeline.pl +++ b/src/bin/pg_combinebackup/t/003_timeline.pl @@ -10,6 +10,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_basebackup without -E from server with encrypted WAL produces broken backups"; +} + # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_combinebackup/t/006_db_file_copy.pl b/src/bin/pg_combinebackup/t/006_db_file_copy.pl index 60bdc8d00f79a..d2745b7cd0ecb 100644 --- a/src/bin/pg_combinebackup/t/006_db_file_copy.pl +++ b/src/bin/pg_combinebackup/t/006_db_file_copy.pl @@ -7,6 +7,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_basebackup without -E from server with encrypted WAL produces broken backups"; +} + # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_combinebackup/t/008_promote.pl b/src/bin/pg_combinebackup/t/008_promote.pl index 1154a5d8b2241..990af7fbf3873 100644 --- a/src/bin/pg_combinebackup/t/008_promote.pl +++ b/src/bin/pg_combinebackup/t/008_promote.pl @@ -10,6 +10,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_basebackup without -E from server with encrypted WAL produces broken backups"; +} + # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl index d2cc4d22f0ed4..ee9a2fc8aed2a 100644 --- a/src/bin/pg_rewind/t/001_basic.pl +++ b/src/bin/pg_rewind/t/001_basic.pl @@ -11,6 +11,12 @@ use RewindTest; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "copies WAL directly to archive without using archive_command"; +} + sub run_test { my $test_mode = shift; diff --git a/src/bin/pg_verifybackup/t/009_extract.pl b/src/bin/pg_verifybackup/t/009_extract.pl index 1d531dc20d52a..9e7395ba82300 100644 --- a/src/bin/pg_verifybackup/t/009_extract.pl +++ b/src/bin/pg_verifybackup/t/009_extract.pl @@ -10,6 +10,13 @@ use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; + +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + "pg_basebackup without -E from server with encrypted WAL produces broken backups"; +} + my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init(allows_streaming => 1); $primary->start; diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 578e473139432..48fcb08c6ce24 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -7,6 +7,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => "pg_waldump needs extra options for encrypted WAL"; +} + program_help_ok('pg_waldump'); program_version_ok('pg_waldump'); program_options_handling_ok('pg_waldump'); diff --git a/src/bin/pg_waldump/t/002_save_fullpage.pl b/src/bin/pg_waldump/t/002_save_fullpage.pl index 1f398b043a91f..363a733c0e53c 100644 --- a/src/bin/pg_waldump/t/002_save_fullpage.pl +++ b/src/bin/pg_waldump/t/002_save_fullpage.pl @@ -9,6 +9,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => "pg_waldump needs extra options for encrypted WAL"; +} + my ($blocksize, $walfile_name); # Function to extract the LSN from the given block structure diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index 33cf5b9dfcc0c..c88d9a20bf130 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -14,6 +14,7 @@ our ($tde_template_dir); BEGIN { $ENV{TDE_MODE_NOSKIP} = 0 unless defined($ENV{TDE_MODE_NOSKIP}); + $ENV{TDE_MODE_WAL} = 1 unless defined($ENV{TDE_MODE_WAL}); } sub init @@ -27,6 +28,12 @@ sub init $self->_tde_init_principal_key; + if ($ENV{TDE_MODE_WAL}) + { + $self->SUPER::append_conf('postgresql.conf', + 'pg_tde.wal_encrypt = on'); + } + return; } @@ -45,6 +52,63 @@ sub append_conf $self->SUPER::append_conf($filename, $str); } +sub backup +{ + my ($self, $backup_name, %params) = @_; + my $backup_dir = $self->backup_dir . '/' . $backup_name; + + mkdir $backup_dir or die "mkdir($backup_dir) failed: $!"; + + if ($ENV{TDE_MODE_WAL}) + { + PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', + $self->pg_tde_dir, $backup_dir . '/pg_tde',); + + # TODO: More thorough checking for options incompatible with --encrypt-wal + $params{backup_options} = [] unless defined $params{backup_options}; + unless ( + List::Util::any { $_ eq '-Ft' or $_ eq '-Xnone' } + @{ $params{backup_options} }) + { + push @{ $params{backup_options} }, '--encrypt-wal'; + } + } + + $self->SUPER::backup($backup_name, %params); +} + +sub enable_archiving +{ + my ($self) = @_; + my $path = $self->archive_dir; + + $self->SUPER::enable_archiving; + if ($ENV{TDE_MODE_WAL}) + { + $self->adjust_conf('postgresql.conf', 'archive_command', + qq('pg_tde_archive_decrypt %f %p "cp \\"%%p\\" \\"$path/%%f\\""') + ); + } + + return; +} + +sub enable_restoring +{ + my ($self, $root_node, $standby) = @_; + my $path = $root_node->archive_dir; + + $self->SUPER::enable_restoring($root_node, $standby); + if ($ENV{TDE_MODE_WAL}) + { + $self->adjust_conf('postgresql.conf', 'restore_command', + qq('pg_tde_restore_encrypt %f %p "cp \\"$path/%%f\\" \\"%%p\\""') + ); + } + + return; +} + sub pg_tde_dir { my ($self) = @_; diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl index eb1e051d43dab..665255ba14e58 100644 --- a/src/test/recovery/t/039_end_of_wal.pl +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -13,6 +13,11 @@ use integer; # causes / operator to use integer math +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'uses write_wal to hack wal directly'; +} + # Is this a big-endian system ("network" byte order)? We can't use 'Q' in # pack() calls because it's not available in some perl builds, so we need to # break 64 bit LSN values into two 'I' values. Fortunately we don't need to diff --git a/src/test/recovery/t/042_low_level_backup.pl b/src/test/recovery/t/042_low_level_backup.pl index 61d23187e0f90..c157aaf15ea18 100644 --- a/src/test/recovery/t/042_low_level_backup.pl +++ b/src/test/recovery/t/042_low_level_backup.pl @@ -13,6 +13,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'directly copies archived data without using restore_command'; +} + # Start primary node with archiving. my $node_primary = PostgreSQL::Test::Cluster->new('primary'); $node_primary->init(has_archiving => 1, allows_streaming => 1); diff --git a/src/test/recovery/t/043_no_contrecord_switch.pl b/src/test/recovery/t/043_no_contrecord_switch.pl index a473d3e7d3e63..bafc1c5f18de8 100644 --- a/src/test/recovery/t/043_no_contrecord_switch.pl +++ b/src/test/recovery/t/043_no_contrecord_switch.pl @@ -12,6 +12,11 @@ use integer; # causes / operator to use integer math +if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'uses write_wal to hack wal directly'; +} + # Values queried from the server my $WAL_SEGMENT_SIZE; my $WAL_BLOCK_SIZE; From 97972f81391bc67606b91fc04848031040f1ed7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 25 Aug 2025 13:01:31 +0200 Subject: [PATCH 614/796] PG-1870 Enable table encryption by default in TAP This enables table encryption by default in TAP tests when TDE_MODE=1. Use TDE_MODE_SMGR=0 to turn off table encryption when running with pg_tde loaded. The setup for running regress with tde turned on has been slightly modified to match what is done for TAP tests to let tests that run the regress suite under TAP work. --- ci_scripts/tde_setup.sql | 10 ++-- ci_scripts/tde_setup_global.sql | 11 ++--- contrib/amcheck/t/001_verify_heapam.pl | 5 ++ src/bin/pg_amcheck/t/003_check.pl | 5 ++ src/bin/pg_amcheck/t/005_opclass_damage.pl | 5 ++ src/bin/pg_basebackup/t/010_pg_basebackup.pl | 6 +++ src/bin/pg_checksums/t/002_actions.pl | 5 ++ src/bin/pg_dump/t/004_pg_dump_parallel.pl | 6 +++ src/bin/pg_dump/t/010_dump_connstr.pl | 6 +++ src/bin/pg_upgrade/t/002_pg_upgrade.pl | 6 +++ src/bin/pg_upgrade/t/003_logical_slots.pl | 6 +++ src/bin/pg_upgrade/t/004_subscription.pl | 6 +++ src/bin/scripts/t/020_createdb.pl | 6 +++ src/test/perl/PostgreSQL/Test/TdeCluster.pm | 17 +++++++ src/test/recovery/t/014_unlogged_reinit.pl | 5 ++ src/test/recovery/t/016_min_consistency.pl | 5 ++ src/test/recovery/t/018_wal_optimize.pl | 5 ++ src/test/recovery/t/020_archive_status.pl | 6 +++ src/test/recovery/t/032_relfilenode_reuse.pl | 4 ++ src/test/regress/expected/create_am_1.out | 10 ++-- src/test/regress/expected/psql_1.out | 48 ++++++++++---------- 21 files changed, 143 insertions(+), 40 deletions(-) diff --git a/ci_scripts/tde_setup.sql b/ci_scripts/tde_setup.sql index dfce0a1b08c5d..dd83fba37144d 100644 --- a/ci_scripts/tde_setup.sql +++ b/ci_scripts/tde_setup.sql @@ -1,6 +1,6 @@ -CREATE SCHEMA IF NOT EXISTS tde; -CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; +CREATE SCHEMA IF NOT EXISTS _pg_tde; +CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA _pg_tde; \! rm -f '/tmp/pg_tde_test_keyring.per' -SELECT tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_create_key_using_database_key_provider('test-db-key', 'reg_file-vault'); -SELECT tde.pg_tde_set_key_using_database_key_provider('test-db-key', 'reg_file-vault'); +SELECT _pg_tde.pg_tde_add_database_key_provider_file('reg_file-vault', '/tmp/pg_tde_test_keyring.per'); +SELECT _pg_tde.pg_tde_create_key_using_database_key_provider('test-db-key', 'reg_file-vault'); +SELECT _pg_tde.pg_tde_set_key_using_database_key_provider('test-db-key', 'reg_file-vault'); diff --git a/ci_scripts/tde_setup_global.sql b/ci_scripts/tde_setup_global.sql index f096285643a83..11b440f3e7c55 100644 --- a/ci_scripts/tde_setup_global.sql +++ b/ci_scripts/tde_setup_global.sql @@ -1,11 +1,10 @@ -CREATE SCHEMA tde; -CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA tde; +CREATE SCHEMA IF NOT EXISTS _pg_tde; +CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA _pg_tde; \! rm -f '/tmp/pg_tde_test_keyring.per' -SELECT tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); -SELECT tde.pg_tde_create_key_using_global_key_provider('server-key', 'reg_file-global'); -SELECT tde.pg_tde_set_server_key_using_global_key_provider('server-key', 'reg_file-global'); +SELECT _pg_tde.pg_tde_add_global_key_provider_file('reg_file-global', '/tmp/pg_tde_test_keyring.per'); +SELECT _pg_tde.pg_tde_create_key_using_global_key_provider('server-key', 'reg_file-global'); +SELECT _pg_tde.pg_tde_set_server_key_using_global_key_provider('server-key', 'reg_file-global'); ALTER SYSTEM SET pg_tde.wal_encrypt = on; ALTER SYSTEM SET default_table_access_method = 'tde_heap'; -ALTER SYSTEM SET search_path = "$user",public,tde; -- restart required diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl index 9de3148277f1d..028a75ce06bea 100644 --- a/contrib/amcheck/t/001_verify_heapam.pl +++ b/contrib/amcheck/t/001_verify_heapam.pl @@ -9,6 +9,11 @@ use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => "hacks relation files directly for scaffolding"; +} + my ($node, $result); # diff --git a/src/bin/pg_amcheck/t/003_check.pl b/src/bin/pg_amcheck/t/003_check.pl index d99b094dba807..e5156c0b55781 100644 --- a/src/bin/pg_amcheck/t/003_check.pl +++ b/src/bin/pg_amcheck/t/003_check.pl @@ -9,6 +9,11 @@ use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => "hacks relation files directly for scaffolding"; +} + my ($node, $port, %corrupt_page, %remove_relation); # Returns the filesystem path for the named relation. diff --git a/src/bin/pg_amcheck/t/005_opclass_damage.pl b/src/bin/pg_amcheck/t/005_opclass_damage.pl index 1eea215227208..a571b79e3ff16 100644 --- a/src/bin/pg_amcheck/t/005_opclass_damage.pl +++ b/src/bin/pg_amcheck/t/005_opclass_damage.pl @@ -10,6 +10,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'investigate why this fails'; +} + my $node = PostgreSQL::Test::Cluster->new('test'); $node->init; $node->start; diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index 54197a256fd4a..3885e860269e7 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -16,6 +16,12 @@ "pg_basebackup without -E from server with encrypted WAL produces broken backups"; } +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'uses corrupt_page_checksum to directly hack relation files'; +} + program_help_ok('pg_basebackup'); program_version_ok('pg_basebackup'); program_options_handling_ok('pg_basebackup'); diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl index 33e7fb53c5e16..ec7d7a2e0781f 100644 --- a/src/bin/pg_checksums/t/002_actions.pl +++ b/src/bin/pg_checksums/t/002_actions.pl @@ -12,6 +12,11 @@ use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'uses corrupt_page_checksum to directly hack relation files'; +} # Utility routine to create and check a table with corrupted checksums # on a wanted tablespace. Note that this stops and starts the node diff --git a/src/bin/pg_dump/t/004_pg_dump_parallel.pl b/src/bin/pg_dump/t/004_pg_dump_parallel.pl index a36ba8cf54887..cd9445794bca5 100644 --- a/src/bin/pg_dump/t/004_pg_dump_parallel.pl +++ b/src/bin/pg_dump/t/004_pg_dump_parallel.pl @@ -8,6 +8,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; +} + my $dbname1 = 'regression_src'; my $dbname2 = 'regression_dest1'; my $dbname3 = 'regression_dest2'; diff --git a/src/bin/pg_dump/t/010_dump_connstr.pl b/src/bin/pg_dump/t/010_dump_connstr.pl index 54e2e08cc20bf..df0646ecd3a00 100644 --- a/src/bin/pg_dump/t/010_dump_connstr.pl +++ b/src/bin/pg_dump/t/010_dump_connstr.pl @@ -8,6 +8,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; +} + if ($PostgreSQL::Test::Utils::is_msys2) { plan skip_all => 'High bit name tests fail on Msys2'; diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 78bd776f5be2f..7ec8538986aa3 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -15,6 +15,12 @@ use PostgreSQL::Test::AdjustUpgrade; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; +} + # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl index 0a2483d3dfcde..ecebd366c8dae 100644 --- a/src/bin/pg_upgrade/t/003_logical_slots.pl +++ b/src/bin/pg_upgrade/t/003_logical_slots.pl @@ -11,6 +11,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; +} + # Can be changed to test the other modes my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/pg_upgrade/t/004_subscription.pl b/src/bin/pg_upgrade/t/004_subscription.pl index c59b83af9cc10..270a200529ff0 100644 --- a/src/bin/pg_upgrade/t/004_subscription.pl +++ b/src/bin/pg_upgrade/t/004_subscription.pl @@ -12,6 +12,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; +} + # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl index 4a0e2c883a11e..8fe9c17c6ec29 100644 --- a/src/bin/scripts/t/020_createdb.pl +++ b/src/bin/scripts/t/020_createdb.pl @@ -8,6 +8,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + 'tries to use FILE_COPY strategy for database creation with encrypted objects in the template'; +} + program_help_ok('createdb'); program_version_ok('createdb'); program_options_handling_ok('createdb'); diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index c88d9a20bf130..b943bdac08b65 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -14,6 +14,7 @@ our ($tde_template_dir); BEGIN { $ENV{TDE_MODE_NOSKIP} = 0 unless defined($ENV{TDE_MODE_NOSKIP}); + $ENV{TDE_MODE_SMGR} = 1 unless defined($ENV{TDE_MODE_SMGR}); $ENV{TDE_MODE_WAL} = 1 unless defined($ENV{TDE_MODE_WAL}); } @@ -28,6 +29,22 @@ sub init $self->_tde_init_principal_key; + if ($ENV{TDE_MODE_SMGR}) + { + # Enable the TDE extension in all databases created by initdb, this is + # necessary for the tde_heap access method to be available everywhere. + foreach ('postgres', 'template0', 'template1') + { + _tde_init_sql_command( + $self->data_dir, $_, q( + CREATE SCHEMA _pg_tde; + CREATE EXTENSION pg_tde WITH SCHEMA _pg_tde; + )); + } + $self->SUPER::append_conf('postgresql.conf', + 'default_table_access_method = tde_heap'); + } + if ($ENV{TDE_MODE_WAL}) { $self->SUPER::append_conf('postgresql.conf', diff --git a/src/test/recovery/t/014_unlogged_reinit.pl b/src/test/recovery/t/014_unlogged_reinit.pl index a37fc9249b5dd..f043bbb5afea6 100644 --- a/src/test/recovery/t/014_unlogged_reinit.pl +++ b/src/test/recovery/t/014_unlogged_reinit.pl @@ -12,6 +12,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'invalid page in block'; +} + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; diff --git a/src/test/recovery/t/016_min_consistency.pl b/src/test/recovery/t/016_min_consistency.pl index 23d161aebc771..c33892a5a7515 100644 --- a/src/test/recovery/t/016_min_consistency.pl +++ b/src/test/recovery/t/016_min_consistency.pl @@ -13,6 +13,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'reads LSN directly from relation files'; +} + # Find the largest LSN in the set of pages part of the given relation # file. This is used for offline checks of page consistency. The LSN # is historically stored as a set of two numbers of 4 byte-length diff --git a/src/test/recovery/t/018_wal_optimize.pl b/src/test/recovery/t/018_wal_optimize.pl index 3fb31e643b69d..e2e29cfa40218 100644 --- a/src/test/recovery/t/018_wal_optimize.pl +++ b/src/test/recovery/t/018_wal_optimize.pl @@ -16,6 +16,11 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'invalid page in block'; +} + sub check_orphan_relfilenodes { local $Test::Builder::Level = $Test::Builder::Level + 1; diff --git a/src/test/recovery/t/020_archive_status.pl b/src/test/recovery/t/020_archive_status.pl index 9cb178950f82f..bdf9176b7ed30 100644 --- a/src/test/recovery/t/020_archive_status.pl +++ b/src/test/recovery/t/020_archive_status.pl @@ -10,6 +10,12 @@ use PostgreSQL::Test::Utils; use Test::More; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => + q(Failed test 'pg_stat_archiver failed to archive 000000010000000000000004'); +} + my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init( has_archiving => 1, diff --git a/src/test/recovery/t/032_relfilenode_reuse.pl b/src/test/recovery/t/032_relfilenode_reuse.pl index 96a8104b80267..0c405acabe18e 100644 --- a/src/test/recovery/t/032_relfilenode_reuse.pl +++ b/src/test/recovery/t/032_relfilenode_reuse.pl @@ -8,6 +8,10 @@ use Test::More; use File::Basename; +if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) +{ + plan skip_all => 'invalid page in block'; +} my $node_primary = PostgreSQL::Test::Cluster->new('primary'); $node_primary->init(allows_streaming => 1); diff --git a/src/test/regress/expected/create_am_1.out b/src/test/regress/expected/create_am_1.out index f5b9b91674e29..822600ef23a59 100644 --- a/src/test/regress/expected/create_am_1.out +++ b/src/test/regress/expected/create_am_1.out @@ -129,11 +129,11 @@ ERROR: function int4in(internal) does not exist CREATE ACCESS METHOD bogus TYPE TABLE HANDLER bthandler; ERROR: function bthandler must return type table_am_handler SELECT amname, amhandler, amtype FROM pg_am where amtype = 't' ORDER BY 1, 2; - amname | amhandler | amtype -----------+----------------------+-------- - heap | heap_tableam_handler | t - heap2 | heap_tableam_handler | t - tde_heap | pg_tdeam_handler | t + amname | amhandler | amtype +----------+--------------------------+-------- + heap | heap_tableam_handler | t + heap2 | heap_tableam_handler | t + tde_heap | _pg_tde.pg_tdeam_handler | t (3 rows) -- First create tables employing the new AM using USING diff --git a/src/test/regress/expected/psql_1.out b/src/test/regress/expected/psql_1.out index 5ad2e9d6e4f4b..8014360071376 100644 --- a/src/test/regress/expected/psql_1.out +++ b/src/test/regress/expected/psql_1.out @@ -5013,33 +5013,33 @@ List of access methods \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description -----------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access method - btree | Index | bthandler | b-tree index access method - gin | Index | ginhandler | GIN index access method - gist | Index | gisthandler | GiST index access method - hash | Index | hashhandler | hash index access method - heap | Table | heap_tableam_handler | heap table access method - heap2 | Table | heap_tableam_handler | - spgist | Index | spghandler | SP-GiST index access method - tde_heap | Table | pg_tdeam_handler | tde_heap table access method + List of access methods + Name | Type | Handler | Description +----------+-------+--------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access method + btree | Index | bthandler | b-tree index access method + gin | Index | ginhandler | GIN index access method + gist | Index | gisthandler | GiST index access method + hash | Index | hashhandler | hash index access method + heap | Table | heap_tableam_handler | heap table access method + heap2 | Table | heap_tableam_handler | + spgist | Index | spghandler | SP-GiST index access method + tde_heap | Table | _pg_tde.pg_tdeam_handler | tde_heap table access method (9 rows) \dA+ * - List of access methods - Name | Type | Handler | Description -----------+-------+----------------------+---------------------------------------- - brin | Index | brinhandler | block range index (BRIN) access method - btree | Index | bthandler | b-tree index access method - gin | Index | ginhandler | GIN index access method - gist | Index | gisthandler | GiST index access method - hash | Index | hashhandler | hash index access method - heap | Table | heap_tableam_handler | heap table access method - heap2 | Table | heap_tableam_handler | - spgist | Index | spghandler | SP-GiST index access method - tde_heap | Table | pg_tdeam_handler | tde_heap table access method + List of access methods + Name | Type | Handler | Description +----------+-------+--------------------------+---------------------------------------- + brin | Index | brinhandler | block range index (BRIN) access method + btree | Index | bthandler | b-tree index access method + gin | Index | ginhandler | GIN index access method + gist | Index | gisthandler | GiST index access method + hash | Index | hashhandler | hash index access method + heap | Table | heap_tableam_handler | heap table access method + heap2 | Table | heap_tableam_handler | + spgist | Index | spghandler | SP-GiST index access method + tde_heap | Table | _pg_tde.pg_tdeam_handler | tde_heap table access method (9 rows) \dA+ h* From ffb828f413792671399eb7a0a83a9ef5a29e5694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 28 Aug 2025 16:42:55 +0200 Subject: [PATCH 615/796] PG-1870 Pre-generate pg_tde files for TAP tests This is currently only added for meson, but could also be added for make. This has to be setup before the actual TAP tests are run as they are run in parallel and as such would all try to setup the template at the same time if we let them use the same folder for it without it being pre-generated. This seems to shorten the test suite run-time by ~25% on my laptop, so it seems worth doing. --- meson.build | 42 +++++++++++++++++++++ src/test/perl/PostgreSQL/Test/TdeCluster.pm | 12 +++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index c054200660486..4ff5644c699ed 100644 --- a/meson.build +++ b/meson.build @@ -3445,6 +3445,48 @@ sys.exit(sp.returncode) env: test_env, suite: ['setup']) +test_tde_template_dir = test_install_destdir / 'pg_tde_template' +test_env.set('TDE_TEMPLATE_DIR', test_tde_template_dir) + +test('init_tde_files', + find_program('sh', required: true, native: true), + args: [ + '-c', ''' +set -e + +if [ -z "$TDE_MODE" ]; then + exit; +fi + +set -e + +PATH="$1":$PATH +TMP_DATA_DIR=$(mktemp -d) + +rm -rf "$2" +mkdir "$2" + +pg_ctl -D "$TMP_DATA_DIR" init -o '--set shared_preload_libraries=pg_tde' + +postgres --single -F -j -D "$TMP_DATA_DIR" postgres << SQL + CREATE EXTENSION pg_tde; + SELECT pg_tde_add_global_key_provider_file('global_test_provider', '$2/pg_tde_test_keys'); + SELECT pg_tde_create_key_using_global_key_provider('test_default_key', 'global_test_provider'); + SELECT pg_tde_set_default_key_using_global_key_provider('test_default_key', 'global_test_provider'); +SQL + +cp -RPp "$TMP_DATA_DIR/pg_tde" "$2/pg_tde" +rm -rf "$TMP_DATA_DIR" +''', + 'init_tde_files', + temp_install_bindir, + test_tde_template_dir + ], + priority: setup_tests_priority - 2, + timeout: 300, + is_parallel: false, + env: test_env, + suite: ['setup']) ############################################################### diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index b943bdac08b65..a7a0dd401882b 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -135,9 +135,17 @@ sub pg_tde_dir sub _tde_init_principal_key { my ($self) = @_; + my $tde_template_dir; - my $tde_template_dir = - $PostgreSQL::Test::Utils::tmp_check . '/pg_tde_template'; + if (defined($ENV{TDE_TEMPLATE_DIR})) + { + $tde_template_dir = $ENV{TDE_TEMPLATE_DIR}; + } + else + { + $tde_template_dir = + $PostgreSQL::Test::Utils::tmp_check . '/pg_tde_template'; + } unless (-e $tde_template_dir) { From d48db2b58f8dd4507dbfc9ced98e4a4175ff03c8 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Fri, 29 Aug 2025 09:28:50 +0300 Subject: [PATCH 616/796] Update warnings for basebackup (#558) --- .../pg_tde/documentation/docs/how-to/backup-wal-enabled.md | 4 ++-- .../documentation/docs/release-notes/release-notes-v2.0.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md index 97c2804b40ff2..7b51bbbd176ea 100644 --- a/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md +++ b/contrib/pg_tde/documentation/docs/how-to/backup-wal-enabled.md @@ -24,9 +24,9 @@ Also copy any external files referenced by your providers configuration (such as ## Key rotation during backups !!! warning - Do not rotate SMGR or WAL encryption keys while `pg_basebackup` is running. Standbys or standalone clusters created from such backups may fail to start during WAL replay. + Do not create, change, or rotate global key providers (or their keys) while `pg_basebackup` is running. Standbys or standalone clusters created from such backups may fail to start during WAL replay and may also lead to the corruption of encrypted data (tables, indexes, and other relations). -Rotations during a base backup can leave the standby in an inconsistent state where it cannot retrieve the correct key history. +Creating, changing, or rotating global key providers (or their keys) during a base backup can leave the standby in an inconsistent state where it cannot retrieve the correct key history. For example, you may see errors such as: diff --git a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md index 22c74ba52d346..5a11ed3300d0b 100644 --- a/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md +++ b/contrib/pg_tde/documentation/docs/release-notes/release-notes-v2.0.md @@ -30,9 +30,9 @@ Clusters that did not use WAL encryption in beta can be upgraded normally. ## Known issues -* Rotating encryption keys while `pg_basebackup` is running may cause standbys or standalone clusters initialized from the backup to fail during WAL replay. +* Creating, changing, or rotating global key providers (or their keys) while `pg_basebackup` is running may cause standbys or standalone clusters initialized from the backup to fail during WAL replay and may also lead to the corruption of encrypted data (tables, indexes, and other relations). - Avoid key rotations during backups. Run a new full backup after completing a rotation. + Avoid making these actions during backup windows. Run a new full backup after completing a rotation or provider update. * Using `pg_basebackup` with `--wal-method=fetch` produces warnings. From 15ae5c6bd512f2aac364a8b980b5e805406b3ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 28 Aug 2025 17:54:32 +0200 Subject: [PATCH 617/796] Remove unused #define I'm not sure how this ended up here, but it doesn't seem to belong here at all and isn't even used it seems. --- contrib/pg_tde/src/include/keyring/keyring_api.h | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/pg_tde/src/include/keyring/keyring_api.h b/contrib/pg_tde/src/include/keyring/keyring_api.h index 8584386bfd37d..88941b31c3a1c 100644 --- a/contrib/pg_tde/src/include/keyring/keyring_api.h +++ b/contrib/pg_tde/src/include/keyring/keyring_api.h @@ -17,7 +17,6 @@ typedef enum ProviderType #define KEY_DATA_SIZE_128 16 /* 128 bit encryption */ #define KEY_DATA_SIZE_256 32 /* 256 bit encryption, not yet supported */ #define MAX_KEY_DATA_SIZE KEY_DATA_SIZE_256 /* maximum 256 bit encryption */ -#define INTERNAL_KEY_LEN 16 typedef struct KeyData { From c13cd7c608a80b89d09c8d663e0af96754003150 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 29 Aug 2025 14:42:13 +0200 Subject: [PATCH 618/796] Respect extra arguments to initdb in TDE_MODE Previously we ignored the extra arguments to initdb when initializing the pg_tde directory and just copied the directory from a database initialized without the extra arguments, or if available from the cache. Now make sure that when extra arguments are supplied that we do not use the cache and that we copy the pg_tde directory from database initialized with the extra arguments. As far as I know this is only relevant to the --allow-group-access flag but we may as well make the solution generic. --- src/bin/pg_rewind/t/002_databases.pl | 6 -- src/test/perl/PostgreSQL/Test/TdeCluster.pm | 93 ++++++++++++++------- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/bin/pg_rewind/t/002_databases.pl b/src/bin/pg_rewind/t/002_databases.pl index cdc73b8e21069..755ea80e332f9 100644 --- a/src/bin/pg_rewind/t/002_databases.pl +++ b/src/bin/pg_rewind/t/002_databases.pl @@ -11,12 +11,6 @@ use RewindTest; -if ($ENV{TDE_MODE} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_combinebackup doesn't set filemodes of pg_tde/ correctly?"; -} - sub run_test { my $test_mode = shift; diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index a7a0dd401882b..c2ae6377d8d6b 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -27,7 +27,7 @@ sub init $self->SUPER::append_conf('postgresql.conf', 'shared_preload_libraries = pg_tde'); - $self->_tde_init_principal_key; + $self->_tde_init_pg_tde_dir($params{extra}); if ($ENV{TDE_MODE_SMGR}) { @@ -132,7 +132,41 @@ sub pg_tde_dir return $self->data_dir . '/pg_tde'; } -sub _tde_init_principal_key +sub _tde_init_pg_tde_dir +{ + my ($self, $extra) = @_; + my $tde_source_dir; + + if (defined($extra)) + { + $tde_source_dir = $self->_tde_generate_pg_tde_dir($extra); + } + else + { + $tde_source_dir = $self->_tde_init_pg_tde_dir_template; + } + + PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', + $tde_source_dir . '/pg_tde', + $self->pg_tde_dir); + + # We don't want clusters sharing the KMS file as any concurrent writes will + # mess it up. + PostgreSQL::Test::Utils::system_log( + 'cp', '-R', '-P', '-p', + $tde_source_dir . '/pg_tde_test_keys', + $self->basedir . '/pg_tde_test_keys'); + + PostgreSQL::Test::Utils::system_log( + 'pg_tde_change_key_provider', + '-D' => $self->data_dir, + '1664', + 'global_test_provider', + 'file', + $self->basedir . '/pg_tde_test_keys'); +} + +sub _tde_init_pg_tde_dir_template { my ($self) = @_; my $tde_template_dir; @@ -149,45 +183,42 @@ sub _tde_init_principal_key unless (-e $tde_template_dir) { - my $temp_dir = PostgreSQL::Test::Utils::tempdir(); + my $temp_dir = $self->_tde_generate_pg_tde_dir; mkdir $tde_template_dir; - PostgreSQL::Test::Utils::system_log( - 'initdb', - '-D' => $temp_dir, - '--set' => 'shared_preload_libraries=pg_tde'); - - _tde_init_sql_command( - $temp_dir, 'postgres', qq( - CREATE EXTENSION pg_tde; - SELECT pg_tde_add_global_key_provider_file('global_test_provider', '$tde_template_dir/pg_tde_test_keys'); - SELECT pg_tde_create_key_using_global_key_provider('default_test_key', 'global_test_provider'); - SELECT pg_tde_set_default_key_using_global_key_provider('default_test_key', 'global_test_provider'); - )); - PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', $temp_dir . '/pg_tde', $tde_template_dir); + + PostgreSQL::Test::Utils::system_log( + 'cp', '-R', '-P', '-p', + $temp_dir . '/pg_tde_test_keys', + $tde_template_dir . '/pg_tde_test_keys'); } - PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', - $tde_template_dir . '/pg_tde', - $self->pg_tde_dir); + return $tde_template_dir; +} - # We don't want clusters sharing the KMS file as any concurrent writes will - # mess it up. - PostgreSQL::Test::Utils::system_log( - 'cp', '-R', '-P', '-p', - $tde_template_dir . '/pg_tde_test_keys', - $self->basedir . '/pg_tde_test_keys'); +sub _tde_generate_pg_tde_dir +{ + my ($self, $extra) = @_; + my $temp_dir = PostgreSQL::Test::Utils::tempdir(); PostgreSQL::Test::Utils::system_log( - 'pg_tde_change_key_provider', - '-D' => $self->data_dir, - '1664', - 'global_test_provider', - 'file', - $self->basedir . '/pg_tde_test_keys'); + 'initdb', + '-D' => $temp_dir, + '--set' => 'shared_preload_libraries=pg_tde', + @{ $extra }); + + _tde_init_sql_command( + $temp_dir, 'postgres', qq( + CREATE EXTENSION pg_tde; + SELECT pg_tde_add_global_key_provider_file('global_test_provider', '$temp_dir/pg_tde_test_keys'); + SELECT pg_tde_create_key_using_global_key_provider('default_test_key', 'global_test_provider'); + SELECT pg_tde_set_default_key_using_global_key_provider('default_test_key', 'global_test_provider'); + )); + + return $temp_dir; } sub _tde_init_sql_command From 8b3289b3d3cb50257256778c34f30186f7bba80f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Fri, 29 Aug 2025 17:10:52 +0200 Subject: [PATCH 619/796] Disable archiving during TDE_MODE_SMGR setup Archiving being enabled during the setup of the SMGR environment caused one of the test suites for WAL archiving to fail so we disable it while running queries in single-user mode. --- src/test/perl/PostgreSQL/Test/TdeCluster.pm | 1 + src/test/recovery/t/020_archive_status.pl | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index c2ae6377d8d6b..24065a8b47152 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -231,6 +231,7 @@ sub _tde_init_sql_command '-D' => $datadir, '-c' => 'exit_on_error=true', '-c' => 'log_checkpoints=false', + '-c' => 'archive_mode=off', $database, ], '<', diff --git a/src/test/recovery/t/020_archive_status.pl b/src/test/recovery/t/020_archive_status.pl index bdf9176b7ed30..9cb178950f82f 100644 --- a/src/test/recovery/t/020_archive_status.pl +++ b/src/test/recovery/t/020_archive_status.pl @@ -10,12 +10,6 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - q(Failed test 'pg_stat_archiver failed to archive 000000010000000000000004'); -} - my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init( has_archiving => 1, From e257f228612fdc407fb8e5a4bd2d402edafac54d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Wed, 13 Aug 2025 10:00:03 +0200 Subject: [PATCH 620/796] Only initialize WAL shmem once in EXEC_BACKEND builds This only affects EXEC_BACKEND/Windows builds which we currently do not support, but we fix this anyway to make the code more consistent and easier to understand since we try to care about this in other places. In the future we may want to add CI and proper support for EXEC_BACKEND builds. The issue was originally found by Zsolt Parragi. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index c47e7843e5284..79baa95d73a7d 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -156,24 +156,27 @@ TDEXLogEncryptStateSize(void) void TDEXLogShmemInit(void) { - bool foundBuf; + bool found; Assert(LWLockHeldByMeInMode(AddinShmemInitLock, LW_EXCLUSIVE)); EncryptionState = (EncryptionStateData *) ShmemInitStruct("TDE XLog Encryption State", TDEXLogEncryptStateSize(), - &foundBuf); + &found); - memset(EncryptionState, 0, sizeof(EncryptionStateData)); + if (!found) + { + memset(EncryptionState, 0, sizeof(EncryptionStateData)); - EncryptionBuf = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, ((char *) EncryptionState) + sizeof(EncryptionStateData)); + pg_atomic_init_u64(&EncryptionState->enc_key_lsn, 0); - Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionBuf + TDEXLogEncryptBuffSize()); + elog(DEBUG1, "pg_tde: initialized encryption buffer %lu bytes", TDEXLogEncryptStateSize()); + } - pg_atomic_init_u64(&EncryptionState->enc_key_lsn, 0); + EncryptionBuf = (char *) TYPEALIGN(PG_IO_ALIGN_SIZE, ((char *) EncryptionState) + sizeof(EncryptionStateData)); - elog(DEBUG1, "pg_tde: initialized encryption buffer %lu bytes", TDEXLogEncryptStateSize()); + Assert((char *) EncryptionState + TDEXLogEncryptStateSize() >= (char *) EncryptionBuf + TDEXLogEncryptBuffSize()); } #else /* !FRONTEND */ From 7ef4fceabfe3c273d14f83dce35feb4ce2cb6651 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Sat, 30 Aug 2025 08:43:01 +0300 Subject: [PATCH 621/796] Update `pg_verifybackup` tool with workaround (#553) --- contrib/pg_tde/documentation/docs/index/tde-limitations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/docs/index/tde-limitations.md b/contrib/pg_tde/documentation/docs/index/tde-limitations.md index 3d0e95077b50f..905caa04470a2 100644 --- a/contrib/pg_tde/documentation/docs/index/tde-limitations.md +++ b/contrib/pg_tde/documentation/docs/index/tde-limitations.md @@ -11,7 +11,8 @@ The following tools are currently unsupported with `pg_tde` WAL encryption: * `pg_createsubscriber` * `pg_receivewal` -* `pg_verifybackup` (checksum mismatch with encrypted WAL) +* `pg_verifybackup` by default fails with checksum or WAL key size mismatch errors. + As a workaround, use `-s` (skip checksum) and `-n` (`--no-parse-wal`) to verify backups. The following tools and extensions in Percona Distribution for PostgreSQL have been tested and verified to work with `pg_tde` WAL encryption: From e9e9266d03780664cb4f12b89befb04ab0cf460f Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Mon, 1 Sep 2025 13:09:44 +0200 Subject: [PATCH 622/796] Use Perl constructs instead of env variables for TDE_MODE Exposing and setting the environment variables everywhere makes it harder to refactor and to understand what is going on. Instead we can just write a couple of helper functions and use Perl scalar variables. --- contrib/amcheck/t/001_verify_heapam.pl | 6 ++-- src/bin/pg_amcheck/t/003_check.pl | 6 ++-- src/bin/pg_amcheck/t/005_opclass_damage.pl | 6 ++-- src/bin/pg_basebackup/t/010_pg_basebackup.pl | 14 +++----- src/bin/pg_checksums/t/002_actions.pl | 7 ++-- src/bin/pg_combinebackup/t/003_timeline.pl | 7 ++-- .../pg_combinebackup/t/006_db_file_copy.pl | 7 ++-- src/bin/pg_combinebackup/t/008_promote.pl | 7 ++-- src/bin/pg_dump/t/004_pg_dump_parallel.pl | 7 ++-- src/bin/pg_dump/t/010_dump_connstr.pl | 7 ++-- src/bin/pg_rewind/t/001_basic.pl | 7 ++-- src/bin/pg_upgrade/t/002_pg_upgrade.pl | 7 ++-- src/bin/pg_upgrade/t/003_logical_slots.pl | 7 ++-- src/bin/pg_upgrade/t/004_subscription.pl | 7 ++-- src/bin/pg_verifybackup/t/009_extract.pl | 7 ++-- src/bin/pg_waldump/t/001_basic.pl | 6 ++-- src/bin/pg_waldump/t/002_save_fullpage.pl | 6 ++-- src/bin/scripts/t/020_createdb.pl | 7 ++-- src/test/perl/PostgreSQL/Test/Cluster.pm | 2 +- src/test/perl/PostgreSQL/Test/TdeCluster.pm | 35 ++++++++++++------- src/test/recovery/t/014_unlogged_reinit.pl | 6 ++-- src/test/recovery/t/016_min_consistency.pl | 6 ++-- src/test/recovery/t/018_wal_optimize.pl | 6 ++-- src/test/recovery/t/032_relfilenode_reuse.pl | 6 ++-- src/test/recovery/t/039_end_of_wal.pl | 6 ++-- src/test/recovery/t/042_low_level_backup.pl | 7 ++-- .../recovery/t/043_no_contrecord_switch.pl | 6 ++-- 27 files changed, 75 insertions(+), 133 deletions(-) diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl index 028a75ce06bea..a66d4f88eed0f 100644 --- a/contrib/amcheck/t/001_verify_heapam.pl +++ b/contrib/amcheck/t/001_verify_heapam.pl @@ -9,10 +9,8 @@ use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => "hacks relation files directly for scaffolding"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'hacks relation files directly for scaffolding'; my ($node, $result); diff --git a/src/bin/pg_amcheck/t/003_check.pl b/src/bin/pg_amcheck/t/003_check.pl index e5156c0b55781..87cd5b627a4c8 100644 --- a/src/bin/pg_amcheck/t/003_check.pl +++ b/src/bin/pg_amcheck/t/003_check.pl @@ -9,10 +9,8 @@ use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => "hacks relation files directly for scaffolding"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'hacks relation files directly for scaffolding'; my ($node, $port, %corrupt_page, %remove_relation); diff --git a/src/bin/pg_amcheck/t/005_opclass_damage.pl b/src/bin/pg_amcheck/t/005_opclass_damage.pl index a571b79e3ff16..eabd993763f7f 100644 --- a/src/bin/pg_amcheck/t/005_opclass_damage.pl +++ b/src/bin/pg_amcheck/t/005_opclass_damage.pl @@ -10,10 +10,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'investigate why this fails'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'investigate why this fails'; my $node = PostgreSQL::Test::Cluster->new('test'); $node->init; diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index 3885e860269e7..7a6bfe9a8255e 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -10,17 +10,11 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_basebackup without -E from server with encrypted WAL produces broken backups"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_basebackup without -E from server with encrypted WAL produces broken backups'; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'uses corrupt_page_checksum to directly hack relation files'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'uses corrupt_page_checksum to directly hack relation files'; program_help_ok('pg_basebackup'); program_version_ok('pg_basebackup'); diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl index ec7d7a2e0781f..a4d2c98167cc2 100644 --- a/src/bin/pg_checksums/t/002_actions.pl +++ b/src/bin/pg_checksums/t/002_actions.pl @@ -12,11 +12,8 @@ use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'uses corrupt_page_checksum to directly hack relation files'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'uses corrupt_page_checksum to directly hack relation files'; # Utility routine to create and check a table with corrupted checksums # on a wanted tablespace. Note that this stops and starts the node diff --git a/src/bin/pg_combinebackup/t/003_timeline.pl b/src/bin/pg_combinebackup/t/003_timeline.pl index c1ba2b323c303..0ee1646042f0e 100644 --- a/src/bin/pg_combinebackup/t/003_timeline.pl +++ b/src/bin/pg_combinebackup/t/003_timeline.pl @@ -10,11 +10,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_basebackup without -E from server with encrypted WAL produces broken backups"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_basebackup without -E from server with encrypted WAL produces broken backups'; # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_combinebackup/t/006_db_file_copy.pl b/src/bin/pg_combinebackup/t/006_db_file_copy.pl index d2745b7cd0ecb..9bc0ac4b66f3c 100644 --- a/src/bin/pg_combinebackup/t/006_db_file_copy.pl +++ b/src/bin/pg_combinebackup/t/006_db_file_copy.pl @@ -7,11 +7,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_basebackup without -E from server with encrypted WAL produces broken backups"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_basebackup without -E from server with encrypted WAL produces broken backups'; # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_combinebackup/t/008_promote.pl b/src/bin/pg_combinebackup/t/008_promote.pl index 990af7fbf3873..3acbad22b4a23 100644 --- a/src/bin/pg_combinebackup/t/008_promote.pl +++ b/src/bin/pg_combinebackup/t/008_promote.pl @@ -10,11 +10,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_basebackup without -E from server with encrypted WAL produces broken backups"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_basebackup without -E from server with encrypted WAL produces broken backups'; # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_COMBINEBACKUP_MODE} || '--copy'; diff --git a/src/bin/pg_dump/t/004_pg_dump_parallel.pl b/src/bin/pg_dump/t/004_pg_dump_parallel.pl index cd9445794bca5..ef85e6526b2b1 100644 --- a/src/bin/pg_dump/t/004_pg_dump_parallel.pl +++ b/src/bin/pg_dump/t/004_pg_dump_parallel.pl @@ -8,11 +8,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; my $dbname1 = 'regression_src'; my $dbname2 = 'regression_dest1'; diff --git a/src/bin/pg_dump/t/010_dump_connstr.pl b/src/bin/pg_dump/t/010_dump_connstr.pl index df0646ecd3a00..b8f7ce200649d 100644 --- a/src/bin/pg_dump/t/010_dump_connstr.pl +++ b/src/bin/pg_dump/t/010_dump_connstr.pl @@ -8,11 +8,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; if ($PostgreSQL::Test::Utils::is_msys2) { diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl index ee9a2fc8aed2a..5b783e009238c 100644 --- a/src/bin/pg_rewind/t/001_basic.pl +++ b/src/bin/pg_rewind/t/001_basic.pl @@ -11,11 +11,8 @@ use RewindTest; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "copies WAL directly to archive without using archive_command"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'copies WAL directly to archive without using archive_command'; sub run_test { diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 7ec8538986aa3..41d30de36c70d 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -15,11 +15,8 @@ use PostgreSQL::Test::AdjustUpgrade; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl index ecebd366c8dae..97692f4ff99fb 100644 --- a/src/bin/pg_upgrade/t/003_logical_slots.pl +++ b/src/bin/pg_upgrade/t/003_logical_slots.pl @@ -11,11 +11,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; # Can be changed to test the other modes my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/pg_upgrade/t/004_subscription.pl b/src/bin/pg_upgrade/t/004_subscription.pl index 270a200529ff0..1dbdedefb93d4 100644 --- a/src/bin/pg_upgrade/t/004_subscription.pl +++ b/src/bin/pg_upgrade/t/004_subscription.pl @@ -12,11 +12,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'pg_restore fail to restore _pg_tde schema on cluster which already has it'; # Can be changed to test the other modes. my $mode = $ENV{PG_TEST_PG_UPGRADE_MODE} || '--copy'; diff --git a/src/bin/pg_verifybackup/t/009_extract.pl b/src/bin/pg_verifybackup/t/009_extract.pl index 9e7395ba82300..9a43d80d4b865 100644 --- a/src/bin/pg_verifybackup/t/009_extract.pl +++ b/src/bin/pg_verifybackup/t/009_extract.pl @@ -11,11 +11,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - "pg_basebackup without -E from server with encrypted WAL produces broken backups"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_basebackup without -E from server with encrypted WAL produces broken backups'; my $primary = PostgreSQL::Test::Cluster->new('primary'); $primary->init(allows_streaming => 1); diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_basic.pl index 48fcb08c6ce24..e7a2ac689dc7c 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -7,10 +7,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => "pg_waldump needs extra options for encrypted WAL"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_waldump needs extra options for encrypted WAL'; program_help_ok('pg_waldump'); program_version_ok('pg_waldump'); diff --git a/src/bin/pg_waldump/t/002_save_fullpage.pl b/src/bin/pg_waldump/t/002_save_fullpage.pl index 363a733c0e53c..377f2f1272240 100644 --- a/src/bin/pg_waldump/t/002_save_fullpage.pl +++ b/src/bin/pg_waldump/t/002_save_fullpage.pl @@ -9,10 +9,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => "pg_waldump needs extra options for encrypted WAL"; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'pg_waldump needs extra options for encrypted WAL'; my ($blocksize, $walfile_name); diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl index 8fe9c17c6ec29..160769d156e28 100644 --- a/src/bin/scripts/t/020_createdb.pl +++ b/src/bin/scripts/t/020_createdb.pl @@ -8,11 +8,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'tries to use FILE_COPY strategy for database creation with encrypted objects in the template'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'tries to use FILE_COPY strategy for database creation with encrypted objects in the template'; program_help_ok('createdb'); program_version_ok('createdb'); diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index 9db717dd90502..1344cdd6699a4 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -1528,7 +1528,7 @@ sub new } } - if ($ENV{TDE_MODE}) + if ($PostgreSQL::Test::TdeCluster::tde_mode) { bless $node, 'PostgreSQL::Test::TdeCluster'; } diff --git a/src/test/perl/PostgreSQL/Test/TdeCluster.pm b/src/test/perl/PostgreSQL/Test/TdeCluster.pm index 24065a8b47152..068989dfebe6f 100644 --- a/src/test/perl/PostgreSQL/Test/TdeCluster.pm +++ b/src/test/perl/PostgreSQL/Test/TdeCluster.pm @@ -8,15 +8,12 @@ use warnings FATAL => 'all'; use List::Util (); use PostgreSQL::Test::RecursiveCopy (); use PostgreSQL::Test::Utils (); +use Test::More; -our ($tde_template_dir); - -BEGIN -{ - $ENV{TDE_MODE_NOSKIP} = 0 unless defined($ENV{TDE_MODE_NOSKIP}); - $ENV{TDE_MODE_SMGR} = 1 unless defined($ENV{TDE_MODE_SMGR}); - $ENV{TDE_MODE_WAL} = 1 unless defined($ENV{TDE_MODE_WAL}); -} +our $tde_mode = defined($ENV{TDE_MODE}) ? $ENV{TDE_MODE} + 0 : 0; +my $tde_mode_noskip = defined($ENV{TDE_MODE_NOSKIP}) ? $ENV{TDE_MODE_NOSKIP} + 0 : 0; +my $tde_mode_smgr = defined($ENV{TDE_MODE_SMGR}) ? $ENV{TDE_MODE_SMGR} + 0 : $tde_mode; +my $tde_mode_wal = defined($ENV{TDE_MODE_WAL}) ? $ENV{TDE_MODE_WAL} + 0 : $tde_mode; sub init { @@ -29,7 +26,7 @@ sub init $self->_tde_init_pg_tde_dir($params{extra}); - if ($ENV{TDE_MODE_SMGR}) + if ($tde_mode_smgr) { # Enable the TDE extension in all databases created by initdb, this is # necessary for the tde_heap access method to be available everywhere. @@ -45,7 +42,7 @@ sub init 'default_table_access_method = tde_heap'); } - if ($ENV{TDE_MODE_WAL}) + if ($tde_mode_wal) { $self->SUPER::append_conf('postgresql.conf', 'pg_tde.wal_encrypt = on'); @@ -76,7 +73,7 @@ sub backup mkdir $backup_dir or die "mkdir($backup_dir) failed: $!"; - if ($ENV{TDE_MODE_WAL}) + if ($tde_mode_wal) { PostgreSQL::Test::Utils::system_log('cp', '-R', '-P', '-p', $self->pg_tde_dir, $backup_dir . '/pg_tde',); @@ -100,7 +97,7 @@ sub enable_archiving my $path = $self->archive_dir; $self->SUPER::enable_archiving; - if ($ENV{TDE_MODE_WAL}) + if ($tde_mode_wal) { $self->adjust_conf('postgresql.conf', 'archive_command', qq('pg_tde_archive_decrypt %f %p "cp \\"%%p\\" \\"$path/%%f\\""') @@ -116,7 +113,7 @@ sub enable_restoring my $path = $root_node->archive_dir; $self->SUPER::enable_restoring($root_node, $standby); - if ($ENV{TDE_MODE_WAL}) + if ($tde_mode_wal) { $self->adjust_conf('postgresql.conf', 'restore_command', qq('pg_tde_restore_encrypt %f %p "cp \\"$path/%%f\\" \\"%%p\\""') @@ -238,4 +235,16 @@ sub _tde_init_sql_command \$sql); } +sub skip_if_tde_mode_wal +{ + my ($msg) = @_; + plan(skip_all => $msg) if ($tde_mode_wal && !$tde_mode_noskip); +} + +sub skip_if_tde_mode_smgr +{ + my ($msg) = @_; + plan(skip_all => $msg) if ($tde_mode_smgr && !$tde_mode_noskip); +} + 1; diff --git a/src/test/recovery/t/014_unlogged_reinit.pl b/src/test/recovery/t/014_unlogged_reinit.pl index f043bbb5afea6..7b1e33bb58eb7 100644 --- a/src/test/recovery/t/014_unlogged_reinit.pl +++ b/src/test/recovery/t/014_unlogged_reinit.pl @@ -12,10 +12,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'invalid page in block'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'invalid page in block'; my $node = PostgreSQL::Test::Cluster->new('main'); diff --git a/src/test/recovery/t/016_min_consistency.pl b/src/test/recovery/t/016_min_consistency.pl index c33892a5a7515..fda8e177a9447 100644 --- a/src/test/recovery/t/016_min_consistency.pl +++ b/src/test/recovery/t/016_min_consistency.pl @@ -13,10 +13,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'reads LSN directly from relation files'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'reads LSN directly from relation files'; # Find the largest LSN in the set of pages part of the given relation # file. This is used for offline checks of page consistency. The LSN diff --git a/src/test/recovery/t/018_wal_optimize.pl b/src/test/recovery/t/018_wal_optimize.pl index e2e29cfa40218..fecb767441fc0 100644 --- a/src/test/recovery/t/018_wal_optimize.pl +++ b/src/test/recovery/t/018_wal_optimize.pl @@ -16,10 +16,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'invalid page in block'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'invalid page in block'; sub check_orphan_relfilenodes { diff --git a/src/test/recovery/t/032_relfilenode_reuse.pl b/src/test/recovery/t/032_relfilenode_reuse.pl index 0c405acabe18e..7acc68f915b06 100644 --- a/src/test/recovery/t/032_relfilenode_reuse.pl +++ b/src/test/recovery/t/032_relfilenode_reuse.pl @@ -8,10 +8,8 @@ use Test::More; use File::Basename; -if ($ENV{TDE_MODE_SMGR} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'invalid page in block'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'invalid page in block'; my $node_primary = PostgreSQL::Test::Cluster->new('primary'); $node_primary->init(allows_streaming => 1); diff --git a/src/test/recovery/t/039_end_of_wal.pl b/src/test/recovery/t/039_end_of_wal.pl index 665255ba14e58..7d0db88c5ef6d 100644 --- a/src/test/recovery/t/039_end_of_wal.pl +++ b/src/test/recovery/t/039_end_of_wal.pl @@ -13,10 +13,8 @@ use integer; # causes / operator to use integer math -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'uses write_wal to hack wal directly'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'uses write_wal to hack wal directly'; # Is this a big-endian system ("network" byte order)? We can't use 'Q' in # pack() calls because it's not available in some perl builds, so we need to diff --git a/src/test/recovery/t/042_low_level_backup.pl b/src/test/recovery/t/042_low_level_backup.pl index c157aaf15ea18..73156e29be4bc 100644 --- a/src/test/recovery/t/042_low_level_backup.pl +++ b/src/test/recovery/t/042_low_level_backup.pl @@ -13,11 +13,8 @@ use PostgreSQL::Test::Utils; use Test::More; -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => - 'directly copies archived data without using restore_command'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_wal + 'directly copies archived data without using restore_command'; # Start primary node with archiving. my $node_primary = PostgreSQL::Test::Cluster->new('primary'); diff --git a/src/test/recovery/t/043_no_contrecord_switch.pl b/src/test/recovery/t/043_no_contrecord_switch.pl index bafc1c5f18de8..71d0aa9bf40a4 100644 --- a/src/test/recovery/t/043_no_contrecord_switch.pl +++ b/src/test/recovery/t/043_no_contrecord_switch.pl @@ -12,10 +12,8 @@ use integer; # causes / operator to use integer math -if ($ENV{TDE_MODE_WAL} and not $ENV{TDE_MODE_NOSKIP}) -{ - plan skip_all => 'uses write_wal to hack wal directly'; -} +PostgreSQL::Test::TdeCluster::skip_if_tde_mode_smgr + 'uses write_wal to hack wal directly'; # Values queried from the server my $WAL_SEGMENT_SIZE; From ca48e7fb7a2636f545f188ceaa22e9a57662fd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 28 Aug 2025 13:54:08 +0200 Subject: [PATCH 623/796] PG-1892 Move InternalKey to common file This structure seems generally useful and doesn't belong with the relation key code. --- contrib/pg_tde/src/include/access/pg_tde_keys_common.h | 3 --- contrib/pg_tde/src/include/access/pg_tde_tdemap.h | 7 +------ contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h | 1 + contrib/pg_tde/src/include/encryption/enc_tde.h | 9 ++++++++- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contrib/pg_tde/src/include/access/pg_tde_keys_common.h b/contrib/pg_tde/src/include/access/pg_tde_keys_common.h index 91554f84474cb..69d7b76484ac1 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_keys_common.h +++ b/contrib/pg_tde/src/include/access/pg_tde_keys_common.h @@ -3,9 +3,6 @@ #include "catalog/tde_principal_key.h" -#define INTERNAL_KEY_LEN 16 -#define INTERNAL_KEY_IV_LEN 16 - #define MAP_ENTRY_IV_SIZE 16 #define MAP_ENTRY_AEAD_TAG_SIZE 16 diff --git a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h index 241d0e7f732ff..40ee9eb9cbc34 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_tdemap.h +++ b/contrib/pg_tde/src/include/access/pg_tde_tdemap.h @@ -4,12 +4,7 @@ #include "storage/relfilelocator.h" #include "access/pg_tde_keys_common.h" - -typedef struct InternalKey -{ - uint8 key[INTERNAL_KEY_LEN]; - uint8 base_iv[INTERNAL_KEY_IV_LEN]; -} InternalKey; +#include "encryption/enc_tde.h" extern void pg_tde_save_smgr_key(RelFileLocator rel, const InternalKey *key); extern bool pg_tde_has_smgr_key(RelFileLocator rel); diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 0ee39585339ac..63c2e202151fc 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -4,6 +4,7 @@ #include "access/xlog_internal.h" #include "access/pg_tde_keys_common.h" +#include "access/pg_tde_tdemap.h" typedef enum { diff --git a/contrib/pg_tde/src/include/encryption/enc_tde.h b/contrib/pg_tde/src/include/encryption/enc_tde.h index 7873606d61dab..64299c85e33f5 100644 --- a/contrib/pg_tde/src/include/encryption/enc_tde.h +++ b/contrib/pg_tde/src/include/encryption/enc_tde.h @@ -5,7 +5,14 @@ #ifndef ENC_TDE_H #define ENC_TDE_H -#include "access/pg_tde_tdemap.h" +#define INTERNAL_KEY_LEN 16 +#define INTERNAL_KEY_IV_LEN 16 + +typedef struct InternalKey +{ + uint8 key[INTERNAL_KEY_LEN]; + uint8 base_iv[INTERNAL_KEY_IV_LEN]; +} InternalKey; extern void pg_tde_generate_internal_key(InternalKey *int_key); extern void pg_tde_stream_crypt(const char *iv_prefix, From 329bbfba3c1c707286ab4cdd086ff3ce26cd47a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Wed, 27 Aug 2025 14:01:15 +0200 Subject: [PATCH 624/796] PG-1892 Do not use InternalKey for encrypted keys It gets confusing when this structure is used for both encrypted and decrypted key data. With this change it's obvious that any allocation of InternalKey can potentially leak key material to swap files. Also be more explicit with empty alignment in the TDEMapEntry struct as multiple levels of anonymous structs to maintain alignment would get a bit messy. --- contrib/pg_tde/src/access/pg_tde_tdemap.c | 60 +++++++++++------------ 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_tdemap.c b/contrib/pg_tde/src/access/pg_tde_tdemap.c index 1a9e118771c49..283424d8e91e7 100644 --- a/contrib/pg_tde/src/access/pg_tde_tdemap.c +++ b/contrib/pg_tde/src/access/pg_tde_tdemap.c @@ -54,30 +54,28 @@ typedef struct TDEFileHeader TDESignedPrincipalKeyInfo signed_key_info; } TDEFileHeader; -/* We do not need the dbOid since the entries are stored in a file per db */ +/* + * Feel free to use the unused fields for something, but beware that existing + * files may contain unexpected values here. Also be aware of alignment if + * changing any of the types as this struct is written/read directly from file. + * + * If changes are made, know that the first four fields are used as AAD when + * encrypting/decrypting existing keys from the key files, so any changes here + * might break existing clusters. + */ typedef struct TDEMapEntry { - Oid spcOid; - RelFileNumber relNumber; - uint32 type; + Oid spcOid; /* Part of AAD */ + RelFileNumber relNumber; /* Part of AAD */ + uint32 type; /* Part of AAD */ + uint32 _unused3; /* Part of AAD */ - /* - * This anonymous struct is here to ensure the same alignment as before - * the unused fields were removed from InternalKey. - */ - struct - { - InternalKey enc_key; - - /* - * These fields were added here to keep the file format the same after - * some fields were removed from InternalKey. Feel free to use them - * for something, but beware that existing files may contain - * unexpected values here. - */ - uint32 _unused1; /* Will be 1 in existing files entries. */ - uint64 _unused2; /* Will be 0 in existing files entries. */ - }; + uint8 encrypted_key_data[INTERNAL_KEY_LEN]; + uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; + + uint32 _unused1; /* Will be 1 in existing files entries. */ + uint32 _unused4; + uint64 _unused2; /* Will be 0 in existing files entries. */ /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; @@ -398,7 +396,7 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ map_entry->spcOid = rlocator->spcOid; map_entry->relNumber = rlocator->relNumber; map_entry->type = MAP_ENTRY_TYPE_KEY; - map_entry->enc_key = *rel_key_data; + memcpy(map_entry->key_base_iv, rel_key_data->base_iv, INTERNAL_KEY_IV_LEN); /* * We set these fields here so that existing file entries will be @@ -414,9 +412,9 @@ pg_tde_initialize_map_entry(TDEMapEntry *map_entry, const TDEPrincipalKey *princ AesGcmEncrypt(principal_key->keyData, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), + (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), rel_key_data->key, INTERNAL_KEY_LEN, - map_entry->enc_key.key, + map_entry->encrypted_key_data, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } #endif @@ -587,22 +585,22 @@ pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, con static InternalKey * tde_decrypt_rel_key(const TDEPrincipalKey *principal_key, TDEMapEntry *map_entry) { - InternalKey *rel_key_data = palloc_object(InternalKey); + InternalKey *key = palloc_object(InternalKey); Assert(principal_key); - *rel_key_data = map_entry->enc_key; - if (!AesGcmDecrypt(principal_key->keyData, map_entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) map_entry, offsetof(TDEMapEntry, enc_key), - map_entry->enc_key.key, INTERNAL_KEY_LEN, - rel_key_data->key, + (unsigned char *) map_entry, offsetof(TDEMapEntry, encrypted_key_data), + map_entry->encrypted_key_data, INTERNAL_KEY_LEN, + key->key, map_entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); - return rel_key_data; + memcpy(key->base_iv, map_entry->key_base_iv, INTERNAL_KEY_IV_LEN); + + return key; } /* From d0d0d2dfb497e829540389fe1bb7572af4251971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Thu, 28 Aug 2025 13:54:49 +0200 Subject: [PATCH 625/796] PG-1892 Rework WalEncryptionKey into ranges This structure isn't really a key but meta data for a range of WAL using a given key. Rework these structures to better represent this. Also decouple the structure from the file format and use InternalKey for the actual decrypted key data. There is still plenty of cleanup to do in relation to this, but I believe this is at least easier to understand than what we currently have. I wish the end of the ranges were handled better though, but that is a bit out of scope for this commit. --- contrib/pg_tde/src/access/pg_tde_xlog_keys.c | 175 ++++++++++-------- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 90 ++++----- .../pg_tde/src/catalog/tde_principal_key.c | 2 +- .../src/include/access/pg_tde_xlog_keys.h | 39 ++-- 4 files changed, 164 insertions(+), 142 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c index 4c14625e0080f..23a61df4f28fd 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_keys.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_keys.c @@ -25,19 +25,33 @@ #define PG_TDE_WAL_KEY_FILE_MAGIC 0x014B4557 /* version ID value = WEK 01 */ #define PG_TDE_WAL_KEY_FILE_NAME "wal_keys" -#define MaxXLogRecPtr (~(XLogRecPtr)0) -#define MaxTimeLineID (~(TimeLineID)0) - typedef struct WalKeyFileHeader { int32 file_version; TDESignedPrincipalKeyInfo signed_key_info; } WalKeyFileHeader; +/* + * Feel free to use the unused fields for something, but beware that existing + * files may contain unexpected values here. Also be aware of alignment if + * changing any of the types as this struct is written/read directly from file. + * + * If changes are made, know that the first two fields are used as AAD when + * encrypting/decrypting existing keys from the key files, so any changes here + * might break existing clusters. + */ typedef struct WalKeyFileEntry { - uint32 type; - WalEncryptionKey enc_key; + uint32 _unused1; /* Part of AAD, is 1 or 2 in existing entries */ + uint32 _unused2; /* Part of AAD */ + + uint8 encrypted_key_data[INTERNAL_KEY_LEN]; + uint8 key_base_iv[INTERNAL_KEY_IV_LEN]; + + uint32 range_type; /* WalEncryptionRangeType */ + uint32 _unused3; + WalLocation range_start; + /* IV and tag used when encrypting the key itself */ unsigned char entry_iv[MAP_ENTRY_IV_SIZE]; unsigned char aead_tag[MAP_ENTRY_AEAD_TAG_SIZE]; @@ -46,11 +60,11 @@ typedef struct WalKeyFileEntry static WALKeyCacheRec *tde_wal_key_cache = NULL; static WALKeyCacheRec *tde_wal_prealloc_record = NULL; static WALKeyCacheRec *tde_wal_key_last_rec = NULL; -static WalEncryptionKey *tde_wal_prealloc_key = NULL; +static WalEncryptionRange *tde_wal_prealloc_range = NULL; -static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(WalEncryptionKey *cached_key); -static WalEncryptionKey *pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); -static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const WalEncryptionKey *rel_key_data); +static WALKeyCacheRec *pg_tde_add_wal_range_to_cache(WalEncryptionRange *cached_range); +static WalEncryptionRange *pg_tde_wal_range_from_entry(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry); +static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, const WalEncryptionRange *range); static int pg_tde_open_wal_key_file_basic(const char *filename, int flags, bool ignore_missing); static int pg_tde_open_wal_key_file_read(const char *filename, bool ignore_missing, off_t *curr_pos); static int pg_tde_open_wal_key_file_write(const char *filename, const TDESignedPrincipalKeyInfo *signed_key_info, bool truncate, off_t *curr_pos); @@ -59,7 +73,7 @@ static void pg_tde_read_one_wal_key_file_entry2(int fd, int32 key_index, WalKeyF static void pg_tde_wal_key_file_header_read(const char *filename, int fd, WalKeyFileHeader *fheader, off_t *bytes_read); static int pg_tde_wal_key_file_header_write(const char *filename, int fd, const TDESignedPrincipalKeyInfo *signed_key_info, off_t *bytes_written); static void pg_tde_write_one_wal_key_file_entry(int fd, const WalKeyFileEntry *entry, off_t *offset, const char *db_map_path); -static void pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, const TDEPrincipalKey *principal_key); +static void pg_tde_write_wal_key_file_entry(const WalEncryptionRange *range, const TDEPrincipalKey *principal_key); static char * get_wal_key_file_path(void) @@ -90,7 +104,7 @@ pg_tde_free_wal_key_cache(void) } void -pg_tde_wal_last_key_set_location(WalLocation loc) +pg_tde_wal_last_range_set_location(WalLocation loc) { LWLock *lock_pk = tde_lwlock_enc_keys(); int fd; @@ -105,8 +119,7 @@ pg_tde_wal_last_key_set_location(WalLocation loc) last_key_idx = ((lseek(fd, 0, SEEK_END) - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; write_pos = sizeof(WalKeyFileHeader) + (last_key_idx * sizeof(WalKeyFileEntry)) + - offsetof(WalKeyFileEntry, enc_key) + - offsetof(WalEncryptionKey, wal_start); + offsetof(WalKeyFileEntry, range_start); if (pg_pwrite(fd, &loc, sizeof(WalLocation), write_pos) != sizeof(WalLocation)) { @@ -132,9 +145,9 @@ pg_tde_wal_last_key_set_location(WalLocation loc) errmsg("could not read previous WAL key: %m")); } - if (wal_location_cmp(prev_entry.enc_key.wal_start, loc) >= 0) + if (wal_location_cmp(prev_entry.range_start, loc) >= 0) { - prev_entry.enc_key.type = WAL_KEY_TYPE_INVALID; + prev_entry.range_type = WAL_ENCRYPTION_RANGE_INVALID; if (pg_pwrite(fd, &prev_entry, sizeof(WalKeyFileEntry), prev_key_pos) != sizeof(WalKeyFileEntry)) { @@ -165,7 +178,7 @@ pg_tde_wal_last_key_set_location(WalLocation loc) * with the actual lsn by the first WAL write. */ void -pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry_type) +pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type) { TDEPrincipalKey *principal_key; @@ -179,23 +192,16 @@ pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry errhint("Use pg_tde_set_server_key_using_global_key_provider() to configure one.")); } - /* TODO: no need in generating key if WAL_KEY_TYPE_UNENCRYPTED */ - rel_key_data->type = entry_type; - rel_key_data->wal_start.lsn = InvalidXLogRecPtr; - rel_key_data->wal_start.tli = 0; + /* TODO: no need in generating key if WAL_ENCRYPTION_RANGE_UNENCRYPTED */ + range->type = type; + range->start.lsn = InvalidXLogRecPtr; + range->start.tli = 0; + range->end.lsn = MaxXLogRecPtr; + range->end.tli = MaxTimeLineID; - if (!RAND_bytes(rel_key_data->key, INTERNAL_KEY_LEN)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate WAL encryption key: %s", - ERR_error_string(ERR_get_error(), NULL))); - if (!RAND_bytes(rel_key_data->base_iv, INTERNAL_KEY_IV_LEN)) - ereport(ERROR, - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate IV for WAL encryption key: %s", - ERR_error_string(ERR_get_error(), NULL))); + pg_tde_generate_internal_key(&range->key); - pg_tde_write_wal_key_file_entry(rel_key_data, principal_key); + pg_tde_write_wal_key_file_entry(range, principal_key); #ifdef FRONTEND free(principal_key); @@ -218,8 +224,8 @@ pg_tde_get_wal_cache_keys(void) return tde_wal_key_cache; } -WalEncryptionKey * -pg_tde_read_last_wal_key(void) +WalEncryptionRange * +pg_tde_read_last_wal_range(void) { off_t read_pos = 0; LWLock *lock_pk = tde_lwlock_enc_keys(); @@ -227,7 +233,7 @@ pg_tde_read_last_wal_key(void) int fd; int file_idx; WalKeyFileEntry entry; - WalEncryptionKey *rel_key_data; + WalEncryptionRange *range; off_t fsize; LWLockAcquire(lock_pk, LW_EXCLUSIVE); @@ -255,14 +261,14 @@ pg_tde_read_last_wal_key(void) file_idx = ((fsize - sizeof(WalKeyFileHeader)) / sizeof(WalKeyFileEntry)) - 1; pg_tde_read_one_wal_key_file_entry2(fd, file_idx, &entry); - rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); + range = pg_tde_wal_range_from_entry(principal_key, &entry); #ifdef FRONTEND pfree(principal_key); #endif LWLockRelease(lock_pk); CloseTransientFile(fd); - return rel_key_data; + return range; } /* Fetches WAL keys from disk and adds them to the WAL cache */ @@ -297,11 +303,12 @@ pg_tde_fetch_wal_keys(WalLocation start) if (keys_count == 0) { WALKeyCacheRec *wal_rec; - WalEncryptionKey stub_key = { - .wal_start = {.tli = 0,.lsn = InvalidXLogRecPtr}, + WalEncryptionRange stub_range = { + .start = {.tli = 0,.lsn = InvalidXLogRecPtr}, + .end = {.tli = MaxTimeLineID,.lsn = MaxXLogRecPtr}, }; - wal_rec = pg_tde_add_wal_key_to_cache(&stub_key); + wal_rec = pg_tde_add_wal_range_to_cache(&stub_range); #ifdef FRONTEND /* The backend frees it after copying to the cache. */ @@ -321,17 +328,16 @@ pg_tde_fetch_wal_keys(WalLocation start) /* * Skip new (just created but not updated by write) and invalid keys */ - if (wal_location_valid(entry.enc_key.wal_start) && - (entry.enc_key.type == WAL_KEY_TYPE_UNENCRYPTED || - entry.enc_key.type == WAL_KEY_TYPE_ENCRYPTED) && - wal_location_cmp(entry.enc_key.wal_start, start) >= 0) + if (entry.range_type != WAL_ENCRYPTION_RANGE_INVALID && + wal_location_valid(entry.range_start) && + wal_location_cmp(entry.range_start, start) >= 0) { - WalEncryptionKey *rel_key_data = pg_tde_decrypt_wal_key(principal_key, &entry); + WalEncryptionRange *range = pg_tde_wal_range_from_entry(principal_key, &entry); WALKeyCacheRec *wal_rec; - wal_rec = pg_tde_add_wal_key_to_cache(rel_key_data); + wal_rec = pg_tde_add_wal_range_to_cache(range); - pfree(rel_key_data); + pfree(range); if (!return_wal_rec) return_wal_rec = wal_rec; @@ -365,9 +371,9 @@ pg_tde_wal_cache_extra_palloc(void) { tde_wal_prealloc_record = palloc0_object(WALKeyCacheRec); } - if (tde_wal_prealloc_key == NULL) + if (tde_wal_prealloc_range == NULL) { - tde_wal_prealloc_key = palloc0_object(WalEncryptionKey); + tde_wal_prealloc_range = palloc0_object(WalEncryptionRange); } #ifndef FRONTEND MemoryContextSwitchTo(oldCtx); @@ -375,7 +381,7 @@ pg_tde_wal_cache_extra_palloc(void) } static WALKeyCacheRec * -pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) +pg_tde_add_wal_range_to_cache(WalEncryptionRange *range) { WALKeyCacheRec *wal_rec; #ifndef FRONTEND @@ -389,10 +395,7 @@ pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) MemoryContextSwitchTo(oldCtx); #endif - wal_rec->start = key->wal_start; - wal_rec->end.tli = MaxTimeLineID; - wal_rec->end.lsn = MaxXLogRecPtr; - wal_rec->key = *key; + wal_rec->range = *range; wal_rec->crypt_ctx = NULL; if (!tde_wal_key_last_rec) { @@ -402,7 +405,7 @@ pg_tde_add_wal_key_to_cache(WalEncryptionKey *key) else { tde_wal_key_last_rec->next = wal_rec; - tde_wal_key_last_rec->end = wal_rec->start; + tde_wal_key_last_rec->range.end = wal_rec->range.start; tde_wal_key_last_rec = wal_rec; } @@ -574,7 +577,7 @@ pg_tde_read_one_wal_key_file_entry2(int fd, } static void -pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, +pg_tde_write_wal_key_file_entry(const WalEncryptionRange *range, const TDEPrincipalKey *principal_key) { int fd; @@ -591,7 +594,7 @@ pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, curr_pos = lseek(fd, 0, SEEK_END); /* Initialize WAL key file entry and encrypt key */ - pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, rel_key_data); + pg_tde_initialize_wal_key_file_entry(&write_entry, principal_key, range); /* Write the given entry at curr_pos; i.e. the free entry. */ pg_tde_write_one_wal_key_file_entry(fd, &write_entry, &curr_pos, get_wal_key_file_path()); @@ -599,27 +602,31 @@ pg_tde_write_wal_key_file_entry(const WalEncryptionKey *rel_key_data, CloseTransientFile(fd); } -static WalEncryptionKey * -pg_tde_decrypt_wal_key(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) +static WalEncryptionRange * +pg_tde_wal_range_from_entry(const TDEPrincipalKey *principal_key, WalKeyFileEntry *entry) { - WalEncryptionKey *key = tde_wal_prealloc_key == NULL ? palloc_object(WalEncryptionKey) : tde_wal_prealloc_key; + WalEncryptionRange *range = tde_wal_prealloc_range == NULL ? palloc0_object(WalEncryptionRange) : tde_wal_prealloc_range; - tde_wal_prealloc_key = NULL; + tde_wal_prealloc_range = NULL; Assert(principal_key); - *key = entry->enc_key; + range->type = entry->range_type; + range->start = entry->range_start; + range->end.tli = MaxTimeLineID; + range->end.lsn = MaxXLogRecPtr; + memcpy(range->key.base_iv, entry->key_base_iv, INTERNAL_KEY_IV_LEN); if (!AesGcmDecrypt(principal_key->keyData, entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) entry, offsetof(WalKeyFileEntry, enc_key), - entry->enc_key.key, INTERNAL_KEY_LEN, - key->key, + (unsigned char *) entry, offsetof(WalKeyFileEntry, encrypted_key_data), + entry->encrypted_key_data, INTERNAL_KEY_LEN, + range->key.key, entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE)) ereport(ERROR, errmsg("Failed to decrypt key, incorrect principal key or corrupted key file")); - return key; + return range; } static void @@ -651,10 +658,22 @@ pg_tde_write_one_wal_key_file_entry(int fd, static void pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, const TDEPrincipalKey *principal_key, - const WalEncryptionKey *rel_key_data) + const WalEncryptionRange *range) { - entry->type = rel_key_data->type; - entry->enc_key = *rel_key_data; + Assert(range->type == WAL_ENCRYPTION_RANGE_ENCRYPTED || range->type == WAL_ENCRYPTION_RANGE_UNENCRYPTED); + + memset(entry, 0, sizeof(WalKeyFileEntry)); + + /* + * We set this field here so that existing file entries will be consistent + * and future use of this field easier. Some existing entries will have 2 + * here. + */ + entry->_unused1 = 1; + + entry->range_type = range->type; + entry->range_start = range->start; + memcpy(entry->key_base_iv, range->key.base_iv, INTERNAL_KEY_IV_LEN); if (!RAND_bytes(entry->entry_iv, MAP_ENTRY_IV_SIZE)) ereport(ERROR, @@ -663,9 +682,9 @@ pg_tde_initialize_wal_key_file_entry(WalKeyFileEntry *entry, AesGcmEncrypt(principal_key->keyData, entry->entry_iv, MAP_ENTRY_IV_SIZE, - (unsigned char *) entry, offsetof(WalKeyFileEntry, enc_key), - rel_key_data->key, INTERNAL_KEY_LEN, - entry->enc_key.key, + (unsigned char *) entry, offsetof(WalKeyFileEntry, encrypted_key_data), + range->key.key, INTERNAL_KEY_LEN, + entry->encrypted_key_data, entry->aead_tag, MAP_ENTRY_AEAD_TAG_SIZE); } @@ -698,7 +717,7 @@ pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, /* Read all entries until EOF */ while (1) { - WalEncryptionKey *key; + WalEncryptionRange *range; WalKeyFileEntry read_map_entry; WalKeyFileEntry write_map_entry; @@ -706,12 +725,10 @@ pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, break; /* Decrypt and re-encrypt key */ - key = pg_tde_decrypt_wal_key(principal_key, &read_map_entry); - pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, key); - + range = pg_tde_wal_range_from_entry(principal_key, &read_map_entry); + pg_tde_initialize_wal_key_file_entry(&write_map_entry, new_principal_key, range); pg_tde_write_one_wal_key_file_entry(new_fd, &write_map_entry, &new_curr_pos, tmp_path); - - pfree(key); + pfree(range); } CloseTransientFile(old_fd); @@ -840,7 +857,7 @@ pg_tde_get_server_key_info(void) } int -pg_tde_count_wal_keys_in_file(void) +pg_tde_count_wal_ranges_in_file(void) { File fd; off_t curr_pos = 0; @@ -869,7 +886,7 @@ pg_tde_delete_server_key(void) Oid dbOid = GLOBAL_DATA_TDE_OID; Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE)); - Assert(pg_tde_count_wal_keys_in_file() == 0); + Assert(pg_tde_count_wal_ranges_in_file() == 0); XLogBeginInsert(); XLogRegisterData((char *) &dbOid, sizeof(Oid)); diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 79baa95d73a7d..3a0932675b56e 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -41,10 +41,10 @@ static const XLogSmgr tde_xlog_smgr = { static void *EncryptionCryptCtx = NULL; /* TODO: can be swapped out to the disk */ -static WalEncryptionKey EncryptionKey = -{ - .type = WAL_KEY_TYPE_INVALID, - .wal_start = {.tli = 0,.lsn = InvalidXLogRecPtr}, +static WalEncryptionRange CurrentWalEncryptionRange = { + .type = WAL_ENCRYPTION_RANGE_INVALID, + .start = {.tli = 0,.lsn = InvalidXLogRecPtr}, + .end = {.tli = MaxTimeLineID,.lsn = MaxXLogRecPtr}, }; /* @@ -223,7 +223,7 @@ TDEXLogSmgrInit() void TDEXLogSmgrInitWrite(bool encrypt_xlog) { - WalEncryptionKey *key; + WalEncryptionRange *range; WALKeyCacheRec *keys; /* @@ -233,7 +233,7 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) */ pg_tde_free_wal_key_cache(); - key = pg_tde_read_last_wal_key(); + range = pg_tde_read_last_wal_range(); /* * Always generate a new key on starting PostgreSQL to protect against @@ -242,16 +242,16 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) */ if (encrypt_xlog) { - pg_tde_create_wal_key(&EncryptionKey, WAL_KEY_TYPE_ENCRYPTED); + pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_ENCRYPTED); } - else if (key && key->type == WAL_KEY_TYPE_ENCRYPTED) + else if (range && range->type == WAL_ENCRYPTION_RANGE_ENCRYPTED) { - pg_tde_create_wal_key(&EncryptionKey, WAL_KEY_TYPE_UNENCRYPTED); + pg_tde_create_wal_range(&CurrentWalEncryptionRange, WAL_ENCRYPTION_RANGE_UNENCRYPTED); } - else if (key) + else if (range) { - EncryptionKey = *key; - TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); + CurrentWalEncryptionRange = *range; + TDEXLogSetEncKeyLocation(CurrentWalEncryptionRange.start); } keys = pg_tde_get_wal_cache_keys(); @@ -265,8 +265,8 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog) pg_tde_wal_cache_extra_palloc(); } - if (key) - pfree(key); + if (range) + pfree(range); } /* @@ -280,13 +280,14 @@ void TDEXLogSmgrInitWriteOldKeys() { WALKeyCacheRec *keys; - WalEncryptionKey dummy = { - .type = WAL_KEY_TYPE_UNENCRYPTED, - .wal_start = {.tli = -1,.lsn = -1} + WalEncryptionRange dummy = { + .type = WAL_ENCRYPTION_RANGE_UNENCRYPTED, + .start = {.tli = MaxTimeLineID,.lsn = MaxXLogRecPtr}, + .end = {.tli = MaxTimeLineID,.lsn = MaxXLogRecPtr}, }; - EncryptionKey = dummy; - TDEXLogSetEncKeyLocation(dummy.wal_start); + CurrentWalEncryptionRange = dummy; + TDEXLogSetEncKeyLocation(dummy.start); keys = pg_tde_get_wal_cache_keys(); @@ -333,7 +334,7 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, TimeLineID tli, XLogSegNo segno) { char iv_prefix[16]; - WalEncryptionKey *key = &EncryptionKey; + WalEncryptionRange *range = &CurrentWalEncryptionRange; char *enc_buff = EncryptionBuf; #ifndef FRONTEND @@ -342,17 +343,17 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset, #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "write encrypted WAL, size: %lu, offset: %ld [%lX], seg: %X/%X, key_start_lsn: %u_%X/%X", - count, offset, offset, LSN_FORMAT_ARGS(segno), key->wal_start.tli, LSN_FORMAT_ARGS(key->wal_start.lsn)); + count, offset, offset, LSN_FORMAT_ARGS(segno), range->start.tli, LSN_FORMAT_ARGS(range->start.lsn)); #endif - CalcXLogPageIVPrefix(tli, segno, key->base_iv, iv_prefix); + CalcXLogPageIVPrefix(tli, segno, range->key.base_iv, iv_prefix); pg_tde_stream_crypt(iv_prefix, offset, (char *) buf, count, enc_buff, - key->key, + range->key.key, &EncryptionCryptCtx); return pg_pwrite(fd, enc_buff, count, offset); @@ -384,15 +385,15 @@ tde_ensure_xlog_key_location(WalLocation loc) lastKeyUsable = (writeKeyLoc.lsn != 0); afterWriteKey = wal_location_cmp(writeKeyLoc, loc) <= 0; - if (EncryptionKey.type != WAL_KEY_TYPE_INVALID && !lastKeyUsable && afterWriteKey && !crashRecovery) + if (CurrentWalEncryptionRange.type != WAL_ENCRYPTION_RANGE_INVALID && !lastKeyUsable && afterWriteKey && !crashRecovery) { WALKeyCacheRec *last_key = pg_tde_get_last_wal_key(); - if (last_key == NULL || last_key->start.lsn < loc.lsn) + if (last_key == NULL || last_key->range.start.lsn < loc.lsn) { - pg_tde_wal_last_key_set_location(loc); - EncryptionKey.wal_start = loc; - TDEXLogSetEncKeyLocation(EncryptionKey.wal_start); + pg_tde_wal_last_range_set_location(loc); + CurrentWalEncryptionRange.start = loc; + TDEXLogSetEncKeyLocation(CurrentWalEncryptionRange.start); lastKeyUsable = true; } } @@ -410,11 +411,11 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset, XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn); lastKeyUsable = tde_ensure_xlog_key_location(loc); - if (!lastKeyUsable && EncryptionKey.type != WAL_KEY_TYPE_INVALID) + if (!lastKeyUsable && CurrentWalEncryptionRange.type != WAL_ENCRYPTION_RANGE_INVALID) { return TDEXLogWriteEncryptedPagesOldKeys(fd, buf, count, offset, tli, segno, segSize); } - else if (EncryptionKey.type == WAL_KEY_TYPE_ENCRYPTED) + else if (CurrentWalEncryptionRange.type == WAL_ENCRYPTION_RANGE_ENCRYPTED) { return TDEXLogWriteEncryptedPages(fd, buf, count, offset, tli, segno); } @@ -481,7 +482,7 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, WalLocation write_loc = {.tli = TDEXLogGetEncKeyTli(),.lsn = write_key_lsn}; /* write has generated a new key, need to fetch it */ - if (last_key != NULL && wal_location_cmp(last_key->start, write_loc) < 0) + if (last_key != NULL && wal_location_cmp(last_key->range.start, write_loc) < 0) { pg_tde_fetch_wal_keys(write_loc); @@ -501,19 +502,20 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, { #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "WAL key %u_%X/%X - %u_%X/%X, encrypted: %s", - curr_key->start.tli, LSN_FORMAT_ARGS(curr_key->start.lsn), - curr_key->end.tli, LSN_FORMAT_ARGS(curr_key->end.lsn), - curr_key->key.type == WAL_KEY_TYPE_ENCRYPTED ? "yes" : "no"); + curr_key->range.start.tli, LSN_FORMAT_ARGS(curr_key->range.start.lsn), + curr_key->range.end.tli, LSN_FORMAT_ARGS(curr_key->range.end.lsn), + curr_key->range.type == WAL_ENCRYPTION_RANGE_ENCRYPTED ? "yes" : "no"); #endif - if (wal_location_valid(curr_key->key.wal_start) && - curr_key->key.type == WAL_KEY_TYPE_ENCRYPTED) + if (wal_location_valid(curr_key->range.start) && + curr_key->range.type == WAL_ENCRYPTION_RANGE_ENCRYPTED) { /* * Check if the key's range overlaps with the buffer's and decypt * the part that does. */ - if (wal_location_cmp(data_start, curr_key->end) < 0 && wal_location_cmp(data_end, curr_key->start) > 0) + if (wal_location_cmp(data_start, curr_key->range.end) < 0 && + wal_location_cmp(data_end, curr_key->range.start) > 0) { char iv_prefix[16]; @@ -544,11 +546,11 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, size_t end_lsn = - data_end.tli < curr_key->end.tli ? data_end.lsn : - Min(data_end.lsn, curr_key->end.lsn); + data_end.tli < curr_key->range.end.tli ? data_end.lsn : + Min(data_end.lsn, curr_key->range.end.lsn); size_t start_lsn = - data_start.tli > curr_key->start.tli ? data_start.lsn : - Max(data_start.lsn, curr_key->start.lsn); + data_start.tli > curr_key->range.start.tli ? data_start.lsn : + Max(data_start.lsn, curr_key->range.start.lsn); off_t dec_off = XLogSegmentOffset(start_lsn, segSize); off_t dec_end = @@ -559,7 +561,7 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, Assert(dec_off >= offset); - CalcXLogPageIVPrefix(tli, segno, curr_key->key.base_iv, + CalcXLogPageIVPrefix(tli, segno, curr_key->range.key.base_iv, iv_prefix); /* @@ -575,7 +577,7 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, #ifdef TDE_XLOG_DEBUG elog(DEBUG1, "decrypt WAL, dec_off: %lu [buff_off %lu], sz: %lu | key %u_%X/%X", - dec_off, dec_off - offset, dec_sz, curr_key->key.wal_start.tli, LSN_FORMAT_ARGS(curr_key->key.wal_start.lsn)); + dec_off, dec_off - offset, dec_sz, curr_key->range.start.tli, LSN_FORMAT_ARGS(curr_key->range.start.lsn)); #endif pg_tde_stream_crypt(iv_prefix, @@ -583,7 +585,7 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, dec_buf, dec_sz, o_buf, - curr_key->key.key, + curr_key->range.key.key, &curr_key->crypt_ctx); } } diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index abb160f47e209..7c2ef542b80d8 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -812,7 +812,7 @@ pg_tde_delete_default_key(PG_FUNCTION_ARGS) principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE); if (pg_tde_is_same_principal_key(default_principal_key, principal_key)) { - if (pg_tde_count_wal_keys_in_file() != 0) + if (pg_tde_count_wal_ranges_in_file() != 0) ereport(ERROR, errcode(ERRCODE_OBJECT_IN_USE), errmsg("cannot delete default principal key"), diff --git a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h index 63c2e202151fc..047d76414e962 100644 --- a/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h +++ b/contrib/pg_tde/src/include/access/pg_tde_xlog_keys.h @@ -5,13 +5,14 @@ #include "access/pg_tde_keys_common.h" #include "access/pg_tde_tdemap.h" +#include "encryption/enc_tde.h" typedef enum { - WAL_KEY_TYPE_INVALID = 0, - WAL_KEY_TYPE_UNENCRYPTED = 1, - WAL_KEY_TYPE_ENCRYPTED = 2, -} WalEncryptionKeyType; + WAL_ENCRYPTION_RANGE_INVALID = 0, + WAL_ENCRYPTION_RANGE_UNENCRYPTED = 1, + WAL_ENCRYPTION_RANGE_ENCRYPTED = 2, +} WalEncryptionRangeType; typedef struct WalLocation { @@ -47,14 +48,19 @@ wal_location_valid(WalLocation loc) return loc.tli != 0 && loc.lsn != InvalidXLogRecPtr; } -typedef struct WalEncryptionKey +#define MaxXLogRecPtr UINT64_MAX +#define MaxTimeLineID UINT32_MAX + +typedef struct WalEncryptionRange { - uint8 key[INTERNAL_KEY_LEN]; - uint8 base_iv[INTERNAL_KEY_IV_LEN]; - uint32 type; + WalEncryptionRangeType type; + + /* key is only used when type is WAL_ENCRYPTION_RANGE_ENCRYPTED */ + InternalKey key; - WalLocation wal_start; -} WalEncryptionKey; + WalLocation start; + WalLocation end; +} WalEncryptionRange; /* * TODO: For now it's a simple linked list which is no good. So consider having @@ -62,17 +68,14 @@ typedef struct WalEncryptionKey */ typedef struct WALKeyCacheRec { - WalLocation start; - WalLocation end; - - WalEncryptionKey key; + WalEncryptionRange range; void *crypt_ctx; struct WALKeyCacheRec *next; } WALKeyCacheRec; -extern int pg_tde_count_wal_keys_in_file(void); -extern void pg_tde_create_wal_key(WalEncryptionKey *rel_key_data, WalEncryptionKeyType entry_type); +extern int pg_tde_count_wal_ranges_in_file(void); +extern void pg_tde_create_wal_range(WalEncryptionRange *range, WalEncryptionRangeType type); extern void pg_tde_delete_server_key(void); extern WALKeyCacheRec *pg_tde_fetch_wal_keys(WalLocation start); extern void pg_tde_free_wal_key_cache(void); @@ -80,10 +83,10 @@ extern WALKeyCacheRec *pg_tde_get_last_wal_key(void); extern TDESignedPrincipalKeyInfo *pg_tde_get_server_key_info(void); extern WALKeyCacheRec *pg_tde_get_wal_cache_keys(void); extern void pg_tde_perform_rotate_server_key(const TDEPrincipalKey *principal_key, const TDEPrincipalKey *new_principal_key, bool write_xlog); -extern WalEncryptionKey *pg_tde_read_last_wal_key(void); +extern WalEncryptionRange *pg_tde_read_last_wal_range(void); extern void pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog); extern void pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info); -extern void pg_tde_wal_last_key_set_location(WalLocation loc); +extern void pg_tde_wal_last_range_set_location(WalLocation loc); extern void pg_tde_wal_cache_extra_palloc(void); #endif /* PG_TDE_XLOG_KEYS_H */ From c4c685160cb8990ecd0c5d05bb05cf76b9e9addc Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 1 Sep 2025 17:41:18 +0300 Subject: [PATCH 626/796] Small date fix (#568) --- contrib/pg_tde/documentation/variables.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index a61f6cb088753..2ff670155b9c7 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -7,7 +7,7 @@ pgversion17: '17.5' tdebranch: release-17.5.2 date: - GA20: '2025-08-30' + GA20: '2025-09-01' GA10: '2025-06-30' RC2: '2025-05-29' RC: '2025-03-27' From f16d4e5dc8ea340facdbf43535058755dc6dbc47 Mon Sep 17 00:00:00 2001 From: Dragos Andriciuc Date: Mon, 1 Sep 2025 17:55:52 +0300 Subject: [PATCH 627/796] Update git ver and pdf cover date (#569) --- contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl | 2 +- contrib/pg_tde/documentation/variables.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl index 28d0a506f1b1f..576e8d262efde 100644 --- a/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl +++ b/contrib/pg_tde/documentation/docs/templates/pdf_cover_page.tpl @@ -7,5 +7,5 @@ {% if config.site_description %}

{{ config.site_description }}

{% endif %} -

2.0 (2025-08-30)

+

2.0 (2025-09-01)

\ No newline at end of file diff --git a/contrib/pg_tde/documentation/variables.yml b/contrib/pg_tde/documentation/variables.yml index 2ff670155b9c7..1427a44065327 100644 --- a/contrib/pg_tde/documentation/variables.yml +++ b/contrib/pg_tde/documentation/variables.yml @@ -4,7 +4,7 @@ latestreleasenotes: 'release-notes-v2.0' tdeversion: '2.0' release: '2.0' pgversion17: '17.5' -tdebranch: release-17.5.2 +tdebranch: release-17.5.3 date: GA20: '2025-09-01' From 1b13757895d014c3f6797cf3d2de27eb7f9d4828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85strand?= Date: Mon, 1 Sep 2025 16:30:53 +0200 Subject: [PATCH 628/796] Clean up some triple newlines These seem to have appeared after the last purge. --- contrib/pg_tde/src/access/pg_tde_xlog_smgr.c | 2 -- contrib/pg_tde/src/catalog/tde_principal_key.c | 1 - contrib/pg_tde/src/keyring/keyring_vault.c | 1 - 3 files changed, 4 deletions(-) diff --git a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c index 3a0932675b56e..c8e62a1edc144 100644 --- a/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c +++ b/contrib/pg_tde/src/access/pg_tde_xlog_smgr.c @@ -325,7 +325,6 @@ TDEXLogWriteEncryptedPagesOldKeys(int fd, const void *buf, size_t count, off_t o return pg_pwrite(fd, enc_buff, count, offset); } - /* * Encrypt XLog page(s) from the buf and write to the segment file. */ @@ -544,7 +543,6 @@ TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset, * is part of the key. */ - size_t end_lsn = data_end.tli < curr_key->range.end.tli ? data_end.lsn : Min(data_end.lsn, curr_key->range.end.lsn); diff --git a/contrib/pg_tde/src/catalog/tde_principal_key.c b/contrib/pg_tde/src/catalog/tde_principal_key.c index 7c2ef542b80d8..97ae2a612babe 100644 --- a/contrib/pg_tde/src/catalog/tde_principal_key.c +++ b/contrib/pg_tde/src/catalog/tde_principal_key.c @@ -679,7 +679,6 @@ pg_tde_set_principal_key_internal(Oid providerOid, } } - /* * SQL interface to delete principal key. * diff --git a/contrib/pg_tde/src/keyring/keyring_vault.c b/contrib/pg_tde/src/keyring/keyring_vault.c index b322cb328b100..06790de2e09d1 100644 --- a/contrib/pg_tde/src/keyring/keyring_vault.c +++ b/contrib/pg_tde/src/keyring/keyring_vault.c @@ -42,7 +42,6 @@ typedef enum JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD, } JsonVaultRespMountInfoSemState; - typedef enum { JRESP_F_UNUSED, From 72e3b597c874a0ea2c7a19bf7787ed260cfed24c Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 19 Aug 2025 21:52:07 +0200 Subject: [PATCH 629/796] Add some simple tests for the archive and restore command These error paths are annoying to test with the whole setup so we call the CLI tools directly. Plus add a couple of tests for the --help argument. --- .../pg_tde/t/pg_tde_change_key_provider.pl | 8 ++ contrib/pg_tde/t/wal_archiving.pl | 73 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/contrib/pg_tde/t/pg_tde_change_key_provider.pl b/contrib/pg_tde/t/pg_tde_change_key_provider.pl index 9cd2746a80e92..561a899c167f4 100644 --- a/contrib/pg_tde/t/pg_tde_change_key_provider.pl +++ b/contrib/pg_tde/t/pg_tde_change_key_provider.pl @@ -6,6 +6,14 @@ use JSON; +command_like([ 'pg_tde_change_key_provider', '--help' ], + qr/Usage:/, 'displays help'); + +command_like( + [ 'pg_tde_change_key_provider', '--version' ], + qr/pg_tde_change_key_provider \(PostgreSQL\) /, + 'displays version'); + my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->append_conf('postgresql.conf', q{shared_preload_libraries = 'pg_tde'}); diff --git a/contrib/pg_tde/t/wal_archiving.pl b/contrib/pg_tde/t/wal_archiving.pl index 3bcbdf51bb108..05c74df4d3486 100644 --- a/contrib/pg_tde/t/wal_archiving.pl +++ b/contrib/pg_tde/t/wal_archiving.pl @@ -6,9 +6,82 @@ use Test::More; use lib 't'; use pgtde; +use PostgreSQL::Test::Utils; unlink('/tmp/wal_archiving.per'); +# Test CLI tools directly + +command_like( + [ 'pg_tde_archive_decrypt', '--help' ], + qr/wraps an archive command to give the command unencrypted WAL/, + 'pg_tde_archive_decrypt displays help'); + +command_like( + [ 'pg_tde_restore_encrypt', '--help' ], + qr/wraps a restore command to encrypt its returned WAL/, + 'pg_tde_restore_encrypt displays help'); + +command_like( + [ 'pg_tde_archive_decrypt', '--version' ], + qr/pg_tde_archive_decrypt \(PostgreSQL\) /, + 'pg_tde_archive_decrypt displays version'); + +command_like( + [ 'pg_tde_restore_encrypt', '--version' ], + qr/pg_tde_restore_encrypt \(PostgreSQL\) /, + 'pg_tde_restore_encrypt displays version'); + +command_fails_like( + [ 'pg_tde_archive_decrypt', 'a', 'b' ], + qr/error: wrong number of arguments, 3 expected/, + 'pg_tde_archive_decrypt checks for number of arguments'); + +command_fails_like( + [ 'pg_tde_restore_encrypt', 'a', 'b' ], + qr/error: wrong number of arguments, 3 expected/, + 'pg_tde_restore_encrypt checks for number of arguments'); + +command_fails_like( + [ 'pg_tde_archive_decrypt', 'file', 'pg_wal/file', 'false %q' ], + qr/error: invalid value for parameter "ARCHIVE-COMMAND": "false %q"\n.*?detail: String contains unexpected placeholder "%q"/, + 'pg_tde_archive_decrypt gives error if command not found'); + +command_fails_like( + [ 'pg_tde_restore_encrypt', 'file', 'pg_wal/file', 'false %q' ], + qr/error: invalid value for parameter "RESTORE-COMMAND": "false %q"\n.*?detail: String contains unexpected placeholder "%q"/, + 'pg_tde_restore_encrypt gives error if command not found'); + +command_fails_like( + [ 'pg_tde_archive_decrypt', 'file', 'pg_wal/file', 'unknown_command_42' ], + qr/error: ARCHIVE-COMMAND "unknown_command_42" failed with exit code 127/, + 'pg_tde_archive_decrypt gives error if command not found'); + +command_fails_like( + [ 'pg_tde_restore_encrypt', 'file', 'pg_wal/file', 'unknown_command_42' ], + qr/error: RESTORE-COMMAND "unknown_command_42" failed with exit code 127/, + 'pg_tde_restore_encrypt gives error if command not found'); + +command_fails_like( + [ 'pg_tde_archive_decrypt', 'file', 'pg_wal/file', 'false' ], + qr/error: ARCHIVE-COMMAND "false" failed with exit code 1/, + 'pg_tde_archive_decrypt prints return code of failed command'); + +command_fails_like( + [ 'pg_tde_restore_encrypt', 'file', 'pg_wal/file', 'false' ], + qr/error: RESTORE-COMMAND "false" failed with exit code 1/, + 'pg_tde_restore_encrypt prints return code of failed command'); + +command_fails_like( + [ 'pg_tde_archive_decrypt', 'file', 'pg_wal/file', 'kill $$; sleep' ], + qr/error: ARCHIVE-COMMAND "kill \$\$; sleep" was terminated by signal 15: Terminated/, + 'pg_tde_archive_decrypt prints which signal killed the command'); + +command_fails_like( + [ 'pg_tde_restore_encrypt', 'file', 'pg_wal/file', 'kill $$; sleep' ], + qr/error: RESTORE-COMMAND "kill \$\$; sleep" was terminated by signal 15: Terminated/, + 'pg_tde_restore_encrypt prints which signal killed the command'); + # Test archive_command my $primary = PostgreSQL::Test::Cluster->new('primary'); From 1e850764910b3aa670607f3c510df401315ea0ca Mon Sep 17 00:00:00 2001 From: Andrew Pogrebnoy Date: Fri, 29 Aug 2025 15:59:10 +0300 Subject: [PATCH 630/796] PG-1895 Rewrite global providers during basebackup Before this commit, we excluded 1664_providers from being rewritten during a backup, since this is one of the files that the user has to copy to the destination before the backup starts. It was done so the user can have different global providers and keys to encrypt backup's WAL than the source server. However, this raised several issues in case the server creates new providers or modifies existing ones during the backup. Then, those changes will be lost in the backup and data related to such providers might become unreadable, or redo might struggle to perform a rotation. This commit treats 1664_providers as the rest of the files and makes provider changes safe during the backup. We still don't rewrite wal_keys, as we generate a unique WAL key for the backup, and the server can't generate new WAL keys can't during the backup. Fixes PG-1895 --- src/bin/pg_basebackup/bbstreamer_file.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/bin/pg_basebackup/bbstreamer_file.c b/src/bin/pg_basebackup/bbstreamer_file.c index 210550e1bf1c3..7df949e64db3d 100644 --- a/src/bin/pg_basebackup/bbstreamer_file.c +++ b/src/bin/pg_basebackup/bbstreamer_file.c @@ -247,19 +247,18 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member, #ifdef PERCONA_EXT /* * A streamed WAL is encrypted with the newly generated WAL key, - * hence we have to prevent these files from rewriting. + * hence we have to prevent wal_keys from rewriting. */ - if (mystreamer->encryped_wal) + if (strcmp(member->pathname, "pg_tde/wal_keys") == 0) { - if (strcmp(member->pathname, "pg_tde/wal_keys") == 0 || - strcmp(member->pathname, "pg_tde/1664_providers") == 0) + if (mystreamer->encryped_wal) break; - } - else if (strcmp(member->pathname, "pg_tde/wal_keys") == 0) - { - pg_log_warning("the source has WAL keys, but no WAL encryption configured for the target backups"); - pg_log_warning_detail("This may lead to exposed data and broken backup."); - pg_log_warning_hint("Run pg_basebackup with -E to encrypt streamed WAL."); + else + { + pg_log_warning("the source has WAL keys, but no WAL encryption configured for the target backups"); + pg_log_warning_detail("This may lead to exposed data and broken backup."); + pg_log_warning_hint("Run pg_basebackup with -E to encrypt streamed WAL."); + } } #endif mystreamer->file = From fe513b1fd16c89a1f950f4f9f035564d91279078 Mon Sep 17 00:00:00 2001 From: Kai Wagner Date: Wed, 3 Sep 2025 12:09:37 +0200 Subject: [PATCH 631/796] Add to FAQ how to rotate internal encryption keys (#573) Adding to the FAQ an answer about "How to rotate the internal keys". This was asked in the forum: https://forums.percona.com/t/how-to-rotate-internal-encryption-keys-in-pg-tde/39261 On top, dropping the duplicated FAQ entry about post-quantum encryption. Signed-off-by: Kai Wagner --- contrib/pg_tde/documentation/docs/faq.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/pg_tde/documentation/docs/faq.md b/contrib/pg_tde/documentation/docs/faq.md index 9e9c88f181a65..643b247e65cae 100644 --- a/contrib/pg_tde/documentation/docs/faq.md +++ b/contrib/pg_tde/documentation/docs/faq.md @@ -167,6 +167,8 @@ To restore from an encrypted backup, you must have the same principal encryption Yes. `pg_tde` works with the FIPS-compliant version of OpenSSL, whether it is provided by your operating system or if you use your own OpenSSL libraries. If you use your own libraries, make sure they are FIPS certified. -## Is post-quantum encryption supported? +## How to rotate internal encryption keys in pg_tde? + +We don't have a dedicated function to rotate internal keys, because a key is effectively rotated any time a table's data file is completely rewritten. Operations like `VACUUM FULL`, `TRUNCATE`, or some but not all `ALTER TABLE` commands automatically generate a new internal key. -No. Post-quantum encryption is not currently supported. +If you're concerned about internal keys being leaked, the best way to address it is by vacuuming the database. This operation rewrites the table's data and, in the process, creates a new internal key. From 9159939321ab2628c7a93cf0decd2cf418df5621 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Thu, 8 May 2025 13:53:16 +0200 Subject: [PATCH 632/796] doc: Fix title markup for AT TIME ZONE and AT LOCAL The title for AT TIME ZONE and AT LOCAL was accidentally wrapping the "and" in the tag. Backpatch to v17 where it was introduced in 97957fdbaa42. Author: Noboru Saito Reviewed-by: Daniel Gustafsson Reviewed-by: Tatsuo Ishii Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CAAM3qn+7QUWW9R6_YwPKXmky0xGE4n63U3EsxZeWE_QtogeU8g@mail.gmail.com Backpatch-through: 17 --- doc/src/sgml/func.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index f441ec43314d7..697c1a02891d5 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10687,7 +10687,7 @@ SELECT date_bin('15 minutes', TIMESTAMP '2020-02-11 15:44:17', TIMESTAMP '2001-0 - <literal>AT TIME ZONE and AT LOCAL</literal> + <literal>AT TIME ZONE</literal> and <literal>AT LOCAL</literal> time zone From bb28d37d2da785777ec2b82d4464404326f6c4b8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 9 May 2025 11:50:33 -0400 Subject: [PATCH 633/796] Centralize ssl tests' check for whether we're using LibreSSL. Right now there's only one caller, so that this is merely an exercise in shoving code from one module to another, but there will shortly be another one. It seems better to avoid having two copies of this highly-subject-to-change test. Back-patch to v15, where we first introduced some tests that don't work with LibreSSL. Reported-by: Thomas Munro Author: Tom Lane Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CA+hUKG+fLqyweHqFSBcErueUVT0vDuSNWui-ySz3+d_APmq7dw@mail.gmail.com Backpatch-through: 15 --- src/test/ssl/t/001_ssltests.pl | 6 ++---- src/test/ssl/t/SSL/Backend/OpenSSL.pm | 20 ++++++++++++++++++++ src/test/ssl/t/SSL/Server.pm | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index b8773270235ae..02353120f6b5b 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -35,10 +35,8 @@ sub switch_server_cert $ssl_server->switch_server_cert(@_); } -# Determine whether this build uses OpenSSL or LibreSSL. As a heuristic, the -# HAVE_SSL_CTX_SET_CERT_CB macro isn't defined for LibreSSL. (Nor for OpenSSL -# 1.0.1, but that's old enough that accommodating it isn't worth the cost.) -my $libressl = not check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1"); +# Determine whether this build uses OpenSSL or LibreSSL. +my $libressl = $ssl_server->is_libressl; #### Some configuration diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm b/src/test/ssl/t/SSL/Backend/OpenSSL.pm index 410b4b1a3f5a8..f28125d2b6e69 100644 --- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm +++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm @@ -26,6 +26,7 @@ package SSL::Backend::OpenSSL; use strict; use warnings FATAL => 'all'; +use PostgreSQL::Test::Utils; use File::Basename; use File::Copy; @@ -205,6 +206,25 @@ sub get_library return $self->{_library}; } +=pod + +=item $backend->library_is_libressl() + +Detect whether the SSL library is LibreSSL. + +=cut + +sub library_is_libressl +{ + my ($self) = @_; + + # The HAVE_SSL_CTX_SET_CERT_CB macro isn't defined for LibreSSL. + # (Nor for OpenSSL 1.0.1, but that's old enough that accommodating it + # isn't worth the cost.) + # We may eventually need a less-bogus heuristic. + return not check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1"); +} + # Internal method for copying a set of files, taking into account wildcards sub _copy_files { diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm index 021eec74abff0..88bf9ab63172a 100644 --- a/src/test/ssl/t/SSL/Server.pm +++ b/src/test/ssl/t/SSL/Server.pm @@ -243,6 +243,23 @@ sub ssl_library =pod +=item $server->is_libressl() + +Detect whether the currently used SSL backend is LibreSSL. +(Ideally we'd not need this hack, but presently we do.) + +=cut + +sub is_libressl +{ + my $self = shift; + my $backend = $self->{backend}; + + return $backend->library_is_libressl(); +} + +=pod + =item switch_server_cert(params) Change the configuration to use the given set of certificate, key, ca and From a1924cac0f3dcef7d9266bf86846244b929897d7 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 9 May 2025 12:29:01 -0400 Subject: [PATCH 634/796] Skip RSA-PSS ssl test when using LibreSSL. Presently, LibreSSL does not have working support for RSA-PSS, so disable that test. Per discussion at https://marc.info/?l=libressl&m=174664225002441&w=2 they do intend to fix this, but it's a ways off yet. Reported-by: Thomas Munro Author: Tom Lane Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CA+hUKG+fLqyweHqFSBcErueUVT0vDuSNWui-ySz3+d_APmq7dw@mail.gmail.com Backpatch-through: 15 --- src/test/ssl/t/002_scram.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index dd9322412434f..23827ec5aeef5 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -44,11 +44,17 @@ sub switch_server_cert # This is the pattern to use in pg_hba.conf to match incoming connections. my $SERVERHOSTCIDR = '127.0.0.1/32'; +# Determine whether this build uses OpenSSL or LibreSSL. +my $libressl = $ssl_server->is_libressl; + # Determine whether build supports detection of hash algorithms for # RSA-PSS certificates. my $supports_rsapss_certs = check_pg_config("#define HAVE_X509_GET_SIGNATURE_INFO 1"); +# As of 5/2025, LibreSSL doesn't actually work for RSA-PSS certificates. +$supports_rsapss_certs = 0 if $libressl; + # Allocation of base connection string shared among multiple tests. my $common_connstr; From adcefd61e1424fbfcfe8f604ddbe54c9fa008f81 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 10 May 2025 20:22:39 -0400 Subject: [PATCH 635/796] Fix incorrect "return NULL" in BumpAllocLarge(). This must be "return MemoryContextAllocationFailure(context, size, flags)" instead. The effect of this oversight is that if we got a malloc failure right here, the code would act as though MCXT_ALLOC_NO_OOM had been specified, whether it was or not. That would likely lead to a null-pointer-dereference crash at the unsuspecting call site. Noted while messing with a patch to improve our Valgrind leak detection support. Back-patch to v17 where this code came in. --- src/backend/utils/mmgr/bump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/mmgr/bump.c b/src/backend/utils/mmgr/bump.c index c60c9c131e395..afd7fe04ab0c2 100644 --- a/src/backend/utils/mmgr/bump.c +++ b/src/backend/utils/mmgr/bump.c @@ -316,7 +316,7 @@ BumpAllocLarge(MemoryContext context, Size size, int flags) block = (BumpBlock *) malloc(blksize); if (block == NULL) - return NULL; + return MemoryContextAllocationFailure(context, size, flags); context->mem_allocated += blksize; From ea2b7f476ef7baa60fecf5772a3f53a94c4a97d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Sun, 11 May 2025 09:47:10 -0400 Subject: [PATCH 636/796] Fix comment of tsquerysend() The comment describes the order in which fields are sent, and it had one of the fields in the wrong place. This has been wrong since e6dbcb72fafa (2008), so backpatch all the way back. Author: Emre Hasegeli Discussion: https://postgr.es/m/CAE2gYzzf38bR_R=izhpMxAmqHXKeM5ajkmukh4mNs_oXfxcMCA@mail.gmail.com --- src/backend/utils/adt/tsquery.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/tsquery.c b/src/backend/utils/adt/tsquery.c index 6f532188392d3..439b7c6482c64 100644 --- a/src/backend/utils/adt/tsquery.c +++ b/src/backend/utils/adt/tsquery.c @@ -1176,10 +1176,11 @@ tsqueryout(PG_FUNCTION_ARGS) * * uint8 type, QI_VAL * uint8 weight - * operand text in client encoding, null-terminated * uint8 prefix + * operand text in client encoding, null-terminated * * For each operator: + * * uint8 type, QI_OPR * uint8 operator, one of OP_AND, OP_PHRASE OP_OR, OP_NOT. * uint16 distance (only for OP_PHRASE) From 8de8420e7d58845f9fac3a532247921d07a670e8 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 13 May 2025 07:29:14 -0400 Subject: [PATCH 637/796] Fix order of parameters in POD documentation The documentation for log_check() had the parameters in the wrong order. Also while there, rename %parameters to %params to better documentation for similar functions which use %params. Backpatch down to v14 where this was introduced. Author: Daniel Gustafsson Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/9F503B5-32F2-45D7-A0AE-952879AD65F1@yesql.se Backpatch-through: 14 --- src/test/perl/PostgreSQL/Test/Cluster.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index 1344cdd6699a4..45ee854a724a7 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -2710,7 +2710,7 @@ sub log_content =pod -=item $node->log_check($offset, $test_name, %parameters) +=item $node->log_check($test_name, $offset, %params) Check contents of server logs. From 5465a7f894157f470fba2625373ab3bce6017761 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Thu, 15 May 2025 17:09:04 +0900 Subject: [PATCH 638/796] Fix Assert failure in XMLTABLE parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In an XMLTABLE expression, columns can be marked NOT NULL, and the parser internally fabricates an option named "is_not_null" to represent this. However, the parser also allows users to specify arbitrary option names. This creates a conflict: a user can explicitly use "is_not_null" as an option name and assign it a non-Boolean value, which violates internal assumptions and triggers an assertion failure. To fix, this patch checks whether a user-supplied name collides with the internally reserved option name and raises an error if so. Additionally, the internal name is renamed to "__pg__is_not_null" to further reduce the risk of collision with user-defined names. Reported-by: Евгений Горбанев Author: Richard Guo Reviewed-by: Alvaro Herrera Discussion: https://postgr.es/m/6bac9886-65bf-4cec-96bd-e304159f28db@basealt.ru Backpatch-through: 15 --- src/backend/parser/gram.y | 15 +++++++++++---- src/test/regress/expected/xml.out | 4 ++++ src/test/regress/expected/xml_1.out | 4 ++++ src/test/regress/expected/xml_2.out | 4 ++++ src/test/regress/sql/xml.sql | 2 ++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 08e2195fa48a4..f230c5ff9e76c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -14048,7 +14048,7 @@ xmltable_column_el: parser_errposition(defel->location))); fc->colexpr = defel->arg; } - else if (strcmp(defel->defname, "is_not_null") == 0) + else if (strcmp(defel->defname, "__pg__is_not_null") == 0) { if (nullability_seen) ereport(ERROR, @@ -14091,13 +14091,20 @@ xmltable_column_option_list: xmltable_column_option_el: IDENT b_expr - { $$ = makeDefElem($1, $2, @1); } + { + if (strcmp($1, "__pg__is_not_null") == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("option name \"%s\" cannot be used in XMLTABLE", $1), + parser_errposition(@1))); + $$ = makeDefElem($1, $2, @1); + } | DEFAULT b_expr { $$ = makeDefElem("default", $2, @1); } | NOT NULL_P - { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(true), @1); } + { $$ = makeDefElem("__pg__is_not_null", (Node *) makeBoolean(true), @1); } | NULL_P - { $$ = makeDefElem("is_not_null", (Node *) makeBoolean(false), @1); } + { $$ = makeDefElem("__pg__is_not_null", (Node *) makeBoolean(false), @1); } | PATH b_expr { $$ = makeDefElem("path", $2, @1); } ; diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 868479997d8ac..21677b609a6ca 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1373,6 +1373,10 @@ EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; -- errors SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2); ERROR: XMLTABLE function has 1 columns available but 2 columns specified +SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1); +ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE +LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n... + ^ -- XMLNAMESPACES tests SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), '/zz:rows/zz:row' diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 4e8f65de0416b..852444cb0527b 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -1047,6 +1047,10 @@ EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; -- errors SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2); ERROR: XMLTABLE function has 1 columns available but 2 columns specified +SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1); +ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE +LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n... + ^ -- XMLNAMESPACES tests SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), '/zz:rows/zz:row' diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out index 4e71cd4f26638..e35fc58f098ec 100644 --- a/src/test/regress/expected/xml_2.out +++ b/src/test/regress/expected/xml_2.out @@ -1359,6 +1359,10 @@ EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; -- errors SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2); ERROR: XMLTABLE function has 1 columns available but 2 columns specified +SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1); +ERROR: option name "__pg__is_not_null" cannot be used in XMLTABLE +LINE 1: ...MLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_n... + ^ -- XMLNAMESPACES tests SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), '/zz:rows/zz:row' diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 4c3520ce8980f..0ea4f508837cf 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -435,6 +435,8 @@ EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM xmltableview1; -- errors SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp) AS f (v1, v2); +SELECT * FROM XMLTABLE (ROW () PASSING null COLUMNS v1 timestamp __pg__is_not_null 1) AS f (v1); + -- XMLNAMESPACES tests SELECT * FROM XMLTABLE(XMLNAMESPACES('http://x.y' AS zz), '/zz:rows/zz:row' From 2d690e6658eb18f65e97bee277b0b7f3f8c27ac1 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Fri, 16 May 2025 11:20:07 -0400 Subject: [PATCH 639/796] Align organization wording in copyright statement This aligns the copyright and legal notice wordig with commit a233a603bab8 and pgweb commit 2d764dbc083ab8. Backpatch down to all supported versions. Author: Daniel Gustafsson Reviewed-by: Dave Page Reviewed-by: Tom Lane Discussion: https://postgr.es/m/744E414E-3F52-404C-97FB-ED9B3AA37DC8@yesql.se Backpatch-through: 13 --- COPYRIGHT | 2 +- doc/src/sgml/legal.sgml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index e941d8ef28065..4ce1725ec2cdd 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ Percona Server for PostgreSQL - License -(formerly known as Postgres, then as Postgres95) +(also known as Postgres, formerly known as Postgres95) This software includes portions copyrighted or made available by: • Percona, LLC and/or its affiliates (collectively, "Percona") diff --git a/doc/src/sgml/legal.sgml b/doc/src/sgml/legal.sgml index af13bf2e055b1..75c1309cf7326 100644 --- a/doc/src/sgml/legal.sgml +++ b/doc/src/sgml/legal.sgml @@ -11,13 +11,15 @@ Legal Notice - PostgreSQL is Copyright © 1996–2025 - by the PostgreSQL Global Development Group. + PostgreSQL Database Management System + (also known as Postgres, formerly known as Postgres95) - Postgres95 is Copyright © 1994–5 - by the Regents of the University of California. + Portions Copyright © 1996-2025, PostgreSQL Global Development Group + + + Portions Copyright © 1994, The Regents of the University of California From cd202c1ad1801e5a99d4994f4ea33ae983176712 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 18 May 2025 12:45:55 -0400 Subject: [PATCH 640/796] Make our usage of memset_s() conform strictly to the C11 standard. Per the letter of the C11 standard, one must #define __STDC_WANT_LIB_EXT1__ as 1 before including in order to have access to memset_s(). It appears that many platforms are lenient about this, because we weren't doing it and yet the code appeared to work anyway. But we now find that with -std=c11, macOS is strict and doesn't declare memset_s, leading to compile failures since we try to use it anyway. (Given the lack of prior reports, perhaps this is new behavior in the latest SDK? No matter, we're clearly in the wrong.) In addition to the immediate problem, which could be fixed merely by adding the needed #define to explicit_bzero.c, it seems possible that our configure-time probe for memset_s() could fail in case a platform implements the function in some odd way due to this spec requirement. This concern can be fixed in largely the same way that we dealt with strchrnul() in 6da2ba1d8: switch to using a declaration-based configure probe instead of a does-it-link probe. Back-patch to v13 where we started using memset_s(). Reported-by: Lakshmi Narayana Velayudam Author: Tom Lane Discussion: https://postgr.es/m/CAA4pTnLcKGG78xeOjiBr5yS7ZeE-Rh=FaFQQGOO=nPzA1L8yEA@mail.gmail.com Backpatch-through: 13 --- configure | 15 ++++++++++++++- configure.ac | 3 ++- meson.build | 14 ++++++++------ src/include/pg_config.h.in | 7 ++++--- src/port/explicit_bzero.c | 4 +++- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/configure b/configure index 542e748217370..dbe565d73d6a2 100755 --- a/configure +++ b/configure @@ -15419,7 +15419,7 @@ fi LIBS_including_readline="$LIBS" LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` -for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l memset_s posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l +for ac_func in backtrace_symbols copyfile copy_file_range getifaddrs getpeerucred inet_pton kqueue mbstowcs_l posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15975,6 +15975,19 @@ cat >>confdefs.h <<_ACEOF #define HAVE_DECL_STRCHRNUL $ac_have_decl _ACEOF +ac_fn_c_check_decl "$LINENO" "memset_s" "ac_cv_have_decl_memset_s" "#define __STDC_WANT_LIB_EXT1__ 1 +#include +" +if test "x$ac_cv_have_decl_memset_s" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_MEMSET_S $ac_have_decl +_ACEOF + # This is probably only present on macOS, but may as well check always ac_fn_c_check_decl "$LINENO" "F_FULLFSYNC" "ac_cv_have_decl_F_FULLFSYNC" "#include diff --git a/configure.ac b/configure.ac index d3f805585017a..b18dcd2acc68e 100644 --- a/configure.ac +++ b/configure.ac @@ -1793,7 +1793,6 @@ AC_CHECK_FUNCS(m4_normalize([ inet_pton kqueue mbstowcs_l - memset_s posix_fallocate ppoll pthread_is_threaded_np @@ -1839,6 +1838,8 @@ AC_CHECK_DECLS([strlcat, strlcpy, strnlen]) AC_CHECK_DECLS([preadv], [], [], [#include ]) AC_CHECK_DECLS([pwritev], [], [], [#include ]) AC_CHECK_DECLS([strchrnul], [], [], [#include ]) +AC_CHECK_DECLS([memset_s], [], [], [#define __STDC_WANT_LIB_EXT1__ 1 +#include ]) # This is probably only present on macOS, but may as well check always AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include ]) diff --git a/meson.build b/meson.build index 2be439853f805..65c3961980fe0 100644 --- a/meson.build +++ b/meson.build @@ -2479,6 +2479,7 @@ decl_checks += [ ['preadv', 'sys/uio.h'], ['pwritev', 'sys/uio.h'], ['strchrnul', 'string.h'], + ['memset_s', 'string.h', '#define __STDC_WANT_LIB_EXT1__ 1'], ] # Check presence of some optional LLVM functions. @@ -2492,21 +2493,23 @@ endif foreach c : decl_checks func = c.get(0) header = c.get(1) - args = c.get(2, {}) + prologue = c.get(2, '') + args = c.get(3, {}) varname = 'HAVE_DECL_' + func.underscorify().to_upper() found = cc.compiles(''' -#include <@0@> +@0@ +#include <@1@> int main() { -#ifndef @1@ - (void) @1@; +#ifndef @2@ + (void) @2@; #endif return 0; } -'''.format(header, func), +'''.format(prologue, header, func), name: 'test whether @0@ is declared'.format(func), # need to add cflags_warn to get at least # -Werror=unguarded-availability-new if applicable @@ -2735,7 +2738,6 @@ func_checks = [ ['inet_pton'], ['kqueue'], ['mbstowcs_l'], - ['memset_s'], ['mkdtemp'], ['posix_fadvise'], ['posix_fallocate'], diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 026c54859df37..65d95d0775ff7 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -103,6 +103,10 @@ `LLVMCreatePerfJITEventListener', and to 0 if you don't. */ #undef HAVE_DECL_LLVMCREATEPERFJITEVENTLISTENER +/* Define to 1 if you have the declaration of `memset_s', and to 0 if you + don't. */ +#undef HAVE_DECL_MEMSET_S + /* Define to 1 if you have the declaration of `posix_fadvise', and to 0 if you don't. */ #undef HAVE_DECL_POSIX_FADVISE @@ -304,9 +308,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H -/* Define to 1 if you have the `memset_s' function. */ -#undef HAVE_MEMSET_S - /* Define to 1 if you have the `mkdtemp' function. */ #undef HAVE_MKDTEMP diff --git a/src/port/explicit_bzero.c b/src/port/explicit_bzero.c index 735e21c8b36da..7d2ad7439f764 100644 --- a/src/port/explicit_bzero.c +++ b/src/port/explicit_bzero.c @@ -12,9 +12,11 @@ *------------------------------------------------------------------------- */ +#define __STDC_WANT_LIB_EXT1__ 1 /* needed to access memset_s() */ + #include "c.h" -#if defined(HAVE_MEMSET_S) +#if HAVE_DECL_MEMSET_S void explicit_bzero(void *buf, size_t len) From 9948e743fe427f8840cc17022dae5109fb6a5884 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Mon, 19 May 2025 11:55:55 +0530 Subject: [PATCH 641/796] Don't retreat slot's confirmed_flush LSN. Prevent moving the confirmed_flush backwards, as this could lead to data duplication issues caused by replicating already replicated changes. This can happen when a client acknowledges an LSN it doesn't have to do anything for, and thus didn't store persistently. After a restart, the client can send the prior LSN that it stored persistently as an acknowledgement, but we need to ignore such an LSN to avoid retreating confirm_flush LSN. Diagnosed-by: Zhijie Hou Author: shveta malik Reviewed-by: Amit Kapila Reviewed-by: Dilip Kumar Tested-by: Nisha Moond Backpatch-through: 13 Discussion: https://postgr.es/m/CAJpy0uDZ29P=BYB1JDWMCh-6wXaNqMwG1u1mB4=10Ly0x7HhwQ@mail.gmail.com Discussion: https://postgr.es/m/OS0PR01MB57164AB5716AF2E477D53F6F9489A@OS0PR01MB5716.jpnprd01.prod.outlook.com --- src/backend/replication/logical/logical.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index e941bb491d81d..97b6aa899ee10 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -1847,7 +1847,19 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) SpinLockAcquire(&MyReplicationSlot->mutex); - MyReplicationSlot->data.confirmed_flush = lsn; + /* + * Prevent moving the confirmed_flush backwards, as this could lead to + * data duplication issues caused by replicating already replicated + * changes. + * + * This can happen when a client acknowledges an LSN it doesn't have + * to do anything for, and thus didn't store persistently. After a + * restart, the client can send the prior LSN that it stored + * persistently as an acknowledgement, but we need to ignore such an + * LSN. See similar case handling in CreateDecodingContext. + */ + if (lsn > MyReplicationSlot->data.confirmed_flush) + MyReplicationSlot->data.confirmed_flush = lsn; /* if we're past the location required for bumping xmin, do so */ if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr && @@ -1912,7 +1924,14 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) else { SpinLockAcquire(&MyReplicationSlot->mutex); - MyReplicationSlot->data.confirmed_flush = lsn; + + /* + * Prevent moving the confirmed_flush backwards. See comments above + * for the details. + */ + if (lsn > MyReplicationSlot->data.confirmed_flush) + MyReplicationSlot->data.confirmed_flush = lsn; + SpinLockRelease(&MyReplicationSlot->mutex); } } From 5dc77cac56aa51ed56681e2fa76eff6cd284978f Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 19 May 2025 18:50:26 +0300 Subject: [PATCH 642/796] Fix deparsing FETCH FIRST ROWS WITH TIES In the grammar, is a c_expr, which accepts only a limited set of integer literals and simple expressions without parens. The deparsing logic didn't quite match the grammar rule, and failed to use parens e.g. for "5::bigint". To fix, always surround the expression with parens. Would be nice to omit the parens in simple cases, but unfortunately it's non-trivial to detect such simple cases. Even if the expression is a simple literal 123 in the original query, after parse analysis it becomes a FuncExpr with COERCE_IMPLICIT_CAST rather than a simple Const. Reported-by: yonghao lee Backpatch-through: 13 Discussion: https://www.postgresql.org/message-id/18929-077d6b7093b176e2@postgresql.org --- src/backend/utils/adt/ruleutils.c | 10 ++++++++++ src/test/regress/expected/limit.out | 20 +++++++++++++++++--- src/test/regress/sql/limit.sql | 5 ++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c6293b20cfeaf..d1139a268f3e9 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -5752,9 +5752,19 @@ get_select_query_def(Query *query, deparse_context *context) { if (query->limitOption == LIMIT_OPTION_WITH_TIES) { + /* + * The limitCount arg is a c_expr, so it needs parens. Simple + * literals and function expressions would not need parens, but + * unfortunately it's hard to tell if the expression will be + * printed as a simple literal like 123 or as a typecast + * expression, like '-123'::int4. The grammar accepts the former + * without quoting, but not the latter. + */ appendContextKeyword(context, " FETCH FIRST ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + appendStringInfoChar(buf, '('); get_rule_expr(query->limitCount, context, false); + appendStringInfoChar(buf, ')'); appendStringInfoString(buf, " ROWS WITH TIES"); } else diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index a2cd0f9f5b89e..d45427ac03952 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -643,7 +643,7 @@ View definition: WHERE thousand < 995 ORDER BY thousand OFFSET 10 - FETCH FIRST 5 ROWS WITH TIES; + FETCH FIRST (5) ROWS WITH TIES; CREATE VIEW limit_thousand_v_2 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand OFFSET 10 FETCH FIRST 5 ROWS ONLY; @@ -675,15 +675,29 @@ View definition: FROM onek WHERE thousand < 995 ORDER BY thousand - FETCH FIRST (NULL::integer + 1) ROWS WITH TIES; + FETCH FIRST ((NULL::integer + 1)) ROWS WITH TIES; CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 - ORDER BY thousand FETCH FIRST NULL ROWS ONLY; + ORDER BY thousand FETCH FIRST (5::bigint) ROWS WITH TIES; \d+ limit_thousand_v_4 View "public.limit_thousand_v_4" Column | Type | Collation | Nullable | Default | Storage | Description ----------+---------+-----------+----------+---------+---------+------------- thousand | integer | | | | plain | +View definition: + SELECT thousand + FROM onek + WHERE thousand < 995 + ORDER BY thousand + FETCH FIRST (5::bigint) ROWS WITH TIES; + +CREATE VIEW limit_thousand_v_5 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS ONLY; +\d+ limit_thousand_v_5 + View "public.limit_thousand_v_5" + Column | Type | Collation | Nullable | Default | Storage | Description +----------+---------+-----------+----------+---------+---------+------------- + thousand | integer | | | | plain | View definition: SELECT thousand FROM onek diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql index 6f0cda9870155..603910fe6d11c 100644 --- a/src/test/regress/sql/limit.sql +++ b/src/test/regress/sql/limit.sql @@ -196,6 +196,9 @@ CREATE VIEW limit_thousand_v_3 AS SELECT thousand FROM onek WHERE thousand < 995 ORDER BY thousand FETCH FIRST (NULL+1) ROWS WITH TIES; \d+ limit_thousand_v_3 CREATE VIEW limit_thousand_v_4 AS SELECT thousand FROM onek WHERE thousand < 995 - ORDER BY thousand FETCH FIRST NULL ROWS ONLY; + ORDER BY thousand FETCH FIRST (5::bigint) ROWS WITH TIES; \d+ limit_thousand_v_4 +CREATE VIEW limit_thousand_v_5 AS SELECT thousand FROM onek WHERE thousand < 995 + ORDER BY thousand FETCH FIRST NULL ROWS ONLY; +\d+ limit_thousand_v_5 -- leave these views From 31af1d856e7f1d7be6b85cf1b277bac144bba4c4 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 20 May 2025 14:39:10 +0900 Subject: [PATCH 643/796] doc: Clarify use of _ccnew and _ccold in REINDEX CONCURRENTLY Invalid indexes are suffixed with "_ccnew" or "_ccold". The documentation missed to mention the initial underscore. ChooseRelationName() may also append an extra number if indexes with a similar name already exist; let's add a note about that too. Author: Alec Cozens Discussion: https://postgr.es/m/174733277404.1455388.11471370288789479593@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/ref/reindex.sgml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml index 2942dccf1e2e1..748eec2e20d7e 100644 --- a/doc/src/sgml/ref/reindex.sgml +++ b/doc/src/sgml/ref/reindex.sgml @@ -464,14 +464,17 @@ Indexes: If the index marked INVALID is suffixed - ccnew, then it corresponds to the transient + _ccnew, then it corresponds to the transient index created during the concurrent operation, and the recommended recovery method is to drop it using DROP INDEX, then attempt REINDEX CONCURRENTLY again. - If the invalid index is instead suffixed ccold, + If the invalid index is instead suffixed _ccold, it corresponds to the original index which could not be dropped; the recommended recovery method is to just drop said index, since the rebuild proper has been successful. + A nonzero number may be appended to the suffix of the invalid index + names to keep them unique, like _ccnew1, + _ccold2, etc. From 711cf66eacace544941939e8acd84ff50840c164 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Tue, 20 May 2025 10:39:14 +0300 Subject: [PATCH 644/796] Fix cross-version upgrade test failure Commit 29f7ce6fe7 added another view that needs adjustment in the cross-version upgrade test. This should fix the XversionUpgrade failures in the buildfarm. Backpatch-through: 16 Discussion: https://www.postgresql.org/message-id/18929-077d6b7093b176e2@postgresql.org --- src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm index db833ba251717..90c74bde40bf0 100644 --- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm +++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm @@ -511,6 +511,7 @@ my @_unused_view_qualifiers = ( { obj => 'VIEW public.limit_thousand_v_2', qual => 'onek' }, { obj => 'VIEW public.limit_thousand_v_3', qual => 'onek' }, { obj => 'VIEW public.limit_thousand_v_4', qual => 'onek' }, + { obj => 'VIEW public.limit_thousand_v_5', qual => 'onek' }, # Since 14 { obj => 'MATERIALIZED VIEW public.compressmv', qual => 'cmdata1' }); From 435442b678b4972933fe4a0ad0444887991af4e9 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Wed, 21 May 2025 11:55:14 +0900 Subject: [PATCH 645/796] Fix incorrect WAL description for PREPARE TRANSACTION record. Since commit 8b1dccd37c7, the PREPARE TRANSACTION WAL record includes information about dropped statistics entries. However, the WAL resource manager description function for PREPARE TRANSACTION record failed to parse this information correctly and always assumed there were no such entries. As a result, for example, pg_waldump could not display the dropped statistics entries stored in PREPARE TRANSACTION records. The root cause was that ParsePrepareRecord() did not set the number of statistics entries to drop on commit or abort. These values remained zero-initialized and were never updated from the parsed record. This commit fixes the issue by properly setting those values during parsing. With this fix, pg_waldump can now correctly report dropped statistics entries in PREPARE TRANSACTION records. Back-patch to v15, where commit 8b1dccd37c7 was introduced. Author: Daniil Davydov <3danissimo@gmail.com> Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/CAJDiXgh-6Epb2XiJe4uL0zF-cf0_s_7Lw1TfEHDMLzYjEmfGOw@mail.gmail.com Backpatch-through: 15 --- src/backend/access/rmgrdesc/xactdesc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c index dccca201e05c0..2836112282e4d 100644 --- a/src/backend/access/rmgrdesc/xactdesc.c +++ b/src/backend/access/rmgrdesc/xactdesc.c @@ -252,6 +252,8 @@ ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parsed_prepare *p parsed->nsubxacts = xlrec->nsubxacts; parsed->nrels = xlrec->ncommitrels; parsed->nabortrels = xlrec->nabortrels; + parsed->nstats = xlrec->ncommitstats; + parsed->nabortstats = xlrec->nabortstats; parsed->nmsgs = xlrec->ninvalmsgs; strncpy(parsed->twophase_gid, bufptr, xlrec->gidlen); From 3eac9ef407bdf936e78d56f52e54006fd7543ef0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 22 May 2025 13:52:46 -0400 Subject: [PATCH 646/796] Fix memory leak in XMLSERIALIZE(... INDENT). xmltotext_with_options sometimes tries to replace the existing root node of a libxml2 document. In that case xmlDocSetRootElement will unlink and return the old root node; if we fail to free it, it's leaked for the remainder of the session. The amount of memory at stake is not large, a couple hundred bytes per occurrence, but that could still become annoying in heavy usage. Our only other xmlDocSetRootElement call is not at risk because it's working on a just-created document, but let's modify that code too to make it clear that it's dependent on that. Author: Tom Lane Reviewed-by: Jim Jones Discussion: https://postgr.es/m/1358967.1747858817@sss.pgh.pa.us Backpatch-through: 16 --- src/backend/utils/adt/xml.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 41b1a5c6b0bf5..fba2c684b8ab9 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -754,6 +754,7 @@ xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) * content nodes, and then iterate over the nodes. */ xmlNodePtr root; + xmlNodePtr oldroot; xmlNodePtr newline; root = xmlNewNode(NULL, (const xmlChar *) "content-root"); @@ -761,8 +762,14 @@ xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, "could not allocate xml node"); - /* This attaches root to doc, so we need not free it separately. */ - xmlDocSetRootElement(doc, root); + /* + * This attaches root to doc, so we need not free it separately... + * but instead, we have to free the old root if there was one. + */ + oldroot = xmlDocSetRootElement(doc, root); + if (oldroot != NULL) + xmlFreeNode(oldroot); + xmlAddChildList(root, content_nodes); /* @@ -1850,6 +1857,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, else { xmlNodePtr root; + xmlNodePtr oldroot PG_USED_FOR_ASSERTS_ONLY; /* set up document with empty root node to be the context node */ doc = xmlNewDoc(version); @@ -1868,8 +1876,13 @@ xml_parse(text *data, XmlOptionType xmloption_arg, if (root == NULL || xmlerrcxt->err_occurred) xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, "could not allocate xml node"); - /* This attaches root to doc, so we need not free it separately. */ - xmlDocSetRootElement(doc, root); + + /* + * This attaches root to doc, so we need not free it separately; + * and there can't yet be any old root to free. + */ + oldroot = xmlDocSetRootElement(doc, root); + Assert(oldroot == NULL); /* allow empty content */ if (*(utf8string + count)) From f2827e58ab6cf48ed9c25e6119e4b9589b8876f8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 23 May 2025 11:47:33 -0400 Subject: [PATCH 647/796] Fix AlignedAllocRealloc to cope sanely with OOM. If the inner allocation call returns NULL, we should restore the previous state and return NULL. Previously this code pfree'd the old chunk anyway, which is surely wrong. Also, make it call MemoryContextAllocationFailure rather than summarily returning NULL. The fact that we got control back from the inner call proves that MCXT_ALLOC_NO_OOM was passed, so this change is just cosmetic, but someday it might be less so. This is just a latent bug at present: AFAICT no in-core callers use this function at all, let alone call it with MCXT_ALLOC_NO_OOM. Still, it's the kind of bug that might bite back-patched code pretty hard someday, so let's back-patch to v17 where the bug was introduced (by commit 743112a2e). Author: Tom Lane Reviewed-by: Andres Freund Discussion: https://postgr.es/m/285483.1746756246@sss.pgh.pa.us Backpatch-through: 17 --- src/backend/utils/mmgr/alignedalloc.c | 29 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/mmgr/alignedalloc.c b/src/backend/utils/mmgr/alignedalloc.c index c266fb3dbb148..2d19aa78bb2e3 100644 --- a/src/backend/utils/mmgr/alignedalloc.c +++ b/src/backend/utils/mmgr/alignedalloc.c @@ -45,6 +45,7 @@ AlignedAllocFree(void *pointer) GetMemoryChunkContext(unaligned)->name, chunk); #endif + /* Recursively pfree the unaligned chunk */ pfree(unaligned); } @@ -96,18 +97,32 @@ AlignedAllocRealloc(void *pointer, Size size, int flags) Assert(old_size >= redirchunk->requested_size); #endif + /* + * To keep things simple, we always allocate a new aligned chunk and copy + * data into it. Because of the above inaccuracy, this may end in copying + * more data than was in the original allocation request size, but that + * should be OK. + */ ctx = GetMemoryChunkContext(unaligned); newptr = MemoryContextAllocAligned(ctx, size, alignto, flags); - /* - * We may memcpy beyond the end of the original allocation request size, - * so we must mark the entire allocation as defined. - */ - if (likely(newptr != NULL)) + /* Cope cleanly with OOM */ + if (unlikely(newptr == NULL)) { - VALGRIND_MAKE_MEM_DEFINED(pointer, old_size); - memcpy(newptr, pointer, Min(size, old_size)); + VALGRIND_MAKE_MEM_NOACCESS(redirchunk, sizeof(MemoryChunk)); + return MemoryContextAllocationFailure(ctx, size, flags); } + + /* + * We may memcpy more than the original allocation request size, which + * would result in trying to copy trailing bytes that the original + * MemoryContextAllocAligned call marked NOACCESS. So we must mark the + * entire old_size as defined. That's slightly annoying, but probably not + * worth improving. + */ + VALGRIND_MAKE_MEM_DEFINED(pointer, old_size); + memcpy(newptr, pointer, Min(size, old_size)); + pfree(unaligned); return newptr; From 84ee85b4db6e17a1cec54a397e26c6fd86359e99 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 23 May 2025 14:43:43 -0400 Subject: [PATCH 648/796] Fix per-relation memory leakage in autovacuum. PgStat_StatTabEntry and AutoVacOpts structs were leaked until the end of the autovacuum worker's run, which is bad news if there are a lot of relations in the database. Note: pfree'ing the PgStat_StatTabEntry structs here seems a bit risky, because pgstat_fetch_stat_tabentry_ext does not guarantee anything about whether its result is long-lived. It appears okay so long as autovacuum forces PGSTAT_FETCH_CONSISTENCY_NONE, but I think that API could use a re-think. Also ensure that the VacuumRelation structure passed to vacuum() is in recoverable storage. Back-patch to v15 where we started to manage table statistics this way. (The AutoVacOpts leakage is probably older, but I'm not excited enough to worry about just that part.) Author: Tom Lane Reviewed-by: Andres Freund Discussion: https://postgr.es/m/285483.1746756246@sss.pgh.pa.us Backpatch-through: 15 --- src/backend/postmaster/autovacuum.c | 50 +++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 51198b779e416..0e30a7fda66b5 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2059,6 +2059,12 @@ do_autovacuum(void) } } } + + /* Release stuff to avoid per-relation leakage */ + if (relopts) + pfree(relopts); + if (tabentry) + pfree(tabentry); } table_endscan(relScan); @@ -2075,7 +2081,8 @@ do_autovacuum(void) Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); PgStat_StatTabEntry *tabentry; Oid relid; - AutoVacOpts *relopts = NULL; + AutoVacOpts *relopts; + bool free_relopts = false; bool dovacuum; bool doanalyze; bool wraparound; @@ -2093,7 +2100,9 @@ do_autovacuum(void) * main rel */ relopts = extract_autovac_opts(tuple, pg_class_desc); - if (relopts == NULL) + if (relopts) + free_relopts = true; + else { av_relation *hentry; bool found; @@ -2114,6 +2123,12 @@ do_autovacuum(void) /* ignore analyze for toast tables */ if (dovacuum) table_oids = lappend_oid(table_oids, relid); + + /* Release stuff to avoid leakage */ + if (free_relopts) + pfree(relopts); + if (tabentry) + pfree(tabentry); } table_endscan(relScan); @@ -2485,6 +2500,8 @@ do_autovacuum(void) pg_atomic_test_set_flag(&MyWorkerInfo->wi_dobalance); } + list_free(table_oids); + /* * Perform additional work items, as requested by backends. */ @@ -2666,8 +2683,8 @@ perform_work_item(AutoVacuumWorkItem *workitem) /* * extract_autovac_opts * - * Given a relation's pg_class tuple, return the AutoVacOpts portion of - * reloptions, if set; otherwise, return NULL. + * Given a relation's pg_class tuple, return a palloc'd copy of the + * AutoVacOpts portion of reloptions, if set; otherwise, return NULL. * * Note: callers do not have a relation lock on the table at this point, * so the table could have been dropped, and its catalog rows gone, after @@ -2716,6 +2733,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, autovac_table *tab = NULL; bool wraparound; AutoVacOpts *avopts; + bool free_avopts = false; /* fetch the relation's relcache entry */ classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); @@ -2728,8 +2746,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * main table reloptions if the toast table itself doesn't have. */ avopts = extract_autovac_opts(classTup, pg_class_desc); - if (classForm->relkind == RELKIND_TOASTVALUE && - avopts == NULL && table_toast_map != NULL) + if (avopts) + free_avopts = true; + else if (classForm->relkind == RELKIND_TOASTVALUE && + table_toast_map != NULL) { av_relation *hentry; bool found; @@ -2832,6 +2852,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, avopts->vacuum_cost_delay >= 0)); } + if (free_avopts) + pfree(avopts); heap_freetuple(classTup); return tab; } @@ -2863,6 +2885,10 @@ recheck_relation_needs_vacanalyze(Oid relid, effective_multixact_freeze_max_age, dovacuum, doanalyze, wraparound); + /* Release tabentry to avoid leakage */ + if (tabentry) + pfree(tabentry); + /* ignore ANALYZE for toast tables */ if (classForm->relkind == RELKIND_TOASTVALUE) *doanalyze = false; @@ -3088,18 +3114,22 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy) VacuumRelation *rel; List *rel_list; MemoryContext vac_context; + MemoryContext old_context; /* Let pgstat know what we're doing */ autovac_report_activity(tab); + /* Create a context that vacuum() can use as cross-transaction storage */ + vac_context = AllocSetContextCreate(CurrentMemoryContext, + "Vacuum", + ALLOCSET_DEFAULT_SIZES); + /* Set up one VacuumRelation target, identified by OID, for vacuum() */ + old_context = MemoryContextSwitchTo(vac_context); rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1); rel = makeVacuumRelation(rangevar, tab->at_relid, NIL); rel_list = list_make1(rel); - - vac_context = AllocSetContextCreate(CurrentMemoryContext, - "Vacuum", - ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(old_context); vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true); From 88c3f98f6926b291fccc0237d2edef640ae8cb60 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Mon, 26 May 2025 12:47:33 +0900 Subject: [PATCH 649/796] doc: Fix documenation for snapshot export in logical decoding. The documentation for exported snapshots in logical decoding previously stated that snapshot creation may fail on a hot standby. This is no longer accurate, as snapshot exporting on standbys has been supported since PostgreSQL 10. This commit removes the outdated description. Additionally, the docs referred to the NOEXPORT_SNAPSHOT option to suppress snapshot exporting in CREATE_REPLICATION_SLOT. However, since PostgreSQL 15, NOEXPORT_SNAPSHOT is considered legacy syntax and retained only for backward compatibility. This commit updates the documentation for v15 and later to use the modern equivalent: SNAPSHOT 'nothing'. The older syntax is preserved in documentation for v14 and earlier. Back-patched to all supported branches. Reported-by: Kevin K Biju Author: Fujii Masao Reviewed-by: Kevin K Biju Discussion: https://postgr.es/m/174791480466.798.17122832105389395178@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/logicaldecoding.sgml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index 1c4ae38f1b992..0b4f1fffb6aa1 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -455,9 +455,8 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU using the slot's contents without losing any changes. - Creation of a snapshot is not always possible. In particular, it will - fail when connected to a hot standby. Applications that do not require - snapshot export may suppress it with the NOEXPORT_SNAPSHOT + Applications that do not require + snapshot export may suppress it with the SNAPSHOT 'nothing' option. From 398a9fb778f632ab98629aa95af2111f69c323f8 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 26 May 2025 17:28:40 +0900 Subject: [PATCH 650/796] Fix race condition in subscription TAP test 021_twophase The test did not wait for all the subscriptions to have caught up when dropping the subscription "tab_copy". In a slow environment, it could be possible for the replay of the COMMIT PREPARED transaction "mygid" to not be confirmed yet, causing one prepared transaction to be left around before moving to the next steps of the test. One failure noticed is a transaction found in pg_prepared_xacts for the cases where copy_data = false and two_phase = true, but there should be none after dropping the subscription. As an extra safety measure, a check is added before dropping the subscription, scanning pg_prepared_xacts to make sure that no prepared transactions are left once both subscriptions have caught up. Issue introduced by a8fd13cab0ba, fixing a problem similar to eaf5321c3524. Per buildfarm member kestrel. Author: Vignesh C Reviewed-by: Amit Kapila Discussion: https://postgr.es/m/CALDaNm329QaZ+bwU--bW6GjbNSZ8-38cDE8QWofafub7NV67oA@mail.gmail.com Backpatch-through: 15 --- src/test/subscription/t/021_twophase.pl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/subscription/t/021_twophase.pl b/src/test/subscription/t/021_twophase.pl index e635be74c63f3..94a6d53794e96 100644 --- a/src/test/subscription/t/021_twophase.pl +++ b/src/test/subscription/t/021_twophase.pl @@ -373,7 +373,14 @@ $node_publisher->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); is($result, qq(6), 'publisher inserted data'); +# Wait for both subscribers to catchup $node_publisher->wait_for_catchup($appname_copy); +$node_publisher->wait_for_catchup($appname); + +# Make sure there are no prepared transactions on the subscriber +$result = $node_subscriber->safe_psql('postgres', + "SELECT count(*) FROM pg_prepared_xacts;"); +is($result, qq(0), 'should be no prepared transactions on subscriber'); $result = $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_copy;"); From 5428415f4d04ed0c55b56167f0ecd17253264737 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 28 May 2025 08:59:22 +0900 Subject: [PATCH 651/796] Fix conversion of SIMILAR TO regexes for character classes The code that translates SIMILAR TO pattern matching expressions to POSIX-style regular expressions did not consider that square brackets can be nested. For example, in an expression like [[:alpha:]%_], the logic replaced the placeholders '_' and '%' but it should not. This commit fixes the conversion logic by tracking the nesting level of square brackets marking character class areas, while considering that in expressions like []] or [^]] the first closing square bracket is a regular character. Multiple tests are added to show how the conversions should or should not apply applied while in a character class area, with specific cases added for all the characters converted outside character classes like an opening parenthesis '(', dollar sign '$', etc. Author: Laurenz Albe Reviewed-by: Tom Lane Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/16ab039d1af455652bdf4173402ddda145f2c73b.camel@cybertec.at Backpatch-through: 13 --- src/backend/utils/adt/regexp.c | 38 +++++++++++++--- src/test/regress/expected/strings.out | 62 +++++++++++++++++++++++++++ src/test/regress/sql/strings.sql | 20 +++++++++ 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c index 0e2519bfd5772..37ca136acf186 100644 --- a/src/backend/utils/adt/regexp.c +++ b/src/backend/utils/adt/regexp.c @@ -773,8 +773,11 @@ similar_escape_internal(text *pat_text, text *esc_text) int plen, elen; bool afterescape = false; - bool incharclass = false; int nquotes = 0; + int charclass_depth = 0; /* Nesting level of character classes, + * encompassed by square brackets */ + int charclass_start = 0; /* State of the character class start, + * for carets */ p = VARDATA_ANY(pat_text); plen = VARSIZE_ANY_EXHDR(pat_text); @@ -904,7 +907,7 @@ similar_escape_internal(text *pat_text, text *esc_text) /* fast path */ if (afterescape) { - if (pchar == '"' && !incharclass) /* escape-double-quote? */ + if (pchar == '"' && charclass_depth < 1) /* escape-double-quote? */ { /* emit appropriate part separator, per notes above */ if (nquotes == 0) @@ -953,18 +956,41 @@ similar_escape_internal(text *pat_text, text *esc_text) /* SQL escape character; do not send to output */ afterescape = true; } - else if (incharclass) + else if (charclass_depth > 0) { if (pchar == '\\') *r++ = '\\'; *r++ = pchar; - if (pchar == ']') - incharclass = false; + + /* + * Ignore a closing bracket at the start of a character class. + * Such a bracket is taken literally rather than closing the + * class. "charclass_start" is 1 right at the beginning of a + * class and 2 after an initial caret. + */ + if (pchar == ']' && charclass_start > 2) + charclass_depth--; + else if (pchar == '[') + charclass_depth++; + + /* + * If there is a caret right after the opening bracket, it negates + * the character class, but a following closing bracket should + * still be treated as a normal character. That holds only for + * the first caret, so only the values 1 and 2 mean that closing + * brackets should be taken literally. + */ + if (pchar == '^') + charclass_start++; + else + charclass_start = 3; /* definitely past the start */ } else if (pchar == '[') { + /* start of a character class */ *r++ = pchar; - incharclass = true; + charclass_depth++; + charclass_start = 1; } else if (pchar == '%') { diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 52b69a107fb7a..0ae8e04ef823b 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -596,6 +596,68 @@ SELECT 'abcdefg' SIMILAR TO '_bcd%' ESCAPE NULL AS null; SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '##' AS error; ERROR: invalid escape string HINT: Escape string must be empty or one character. +-- Characters that should be left alone in character classes when a +-- SIMILAR TO regexp pattern is converted to POSIX style. +-- Underscore "_" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '_[_[:alpha:]_]_'; + QUERY PLAN +------------------------------------------------ + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:.[_[:alpha:]_].)$'::text) +(2 rows) + +-- Percentage "%" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '%[%[:alnum:]%]%'; + QUERY PLAN +-------------------------------------------------- + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:.*[%[:alnum:]%].*)$'::text) +(2 rows) + +-- Dot "." +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '.[.[:alnum:].].'; + QUERY PLAN +-------------------------------------------------- + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:\.[.[:alnum:].]\.)$'::text) +(2 rows) + +-- Dollar "$" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '$[$[:alnum:]$]$'; + QUERY PLAN +-------------------------------------------------- + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:\$[$[:alnum:]$]\$)$'::text) +(2 rows) + +-- Opening parenthesis "(" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '([([:alnum:](]('; +ERROR: invalid regular expression: parentheses () not balanced +-- Caret "^" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '^[^[:alnum:]^[^^][[^^]][\^][[\^]]\^]^'; + QUERY PLAN +------------------------------------------------------------------------ + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:\^[^[:alnum:]^[^^][[^^]][\^][[\^]]\^]\^)$'::text) +(2 rows) + +-- Closing square bracket "]" at the beginning of character class +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[]%][^]%][^%]%'; + QUERY PLAN +------------------------------------------------ + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:[]%][^]%][^%].*)$'::text) +(2 rows) + +-- Closing square bracket effective after two carets at the beginning +-- of character class. +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[^^]^'; + QUERY PLAN +--------------------------------------- + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:[^^]\^)$'::text) +(2 rows) + -- Test backslash escapes in regexp_replace's replacement string SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); regexp_replace diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index 395967899290b..8fb80ed3061f4 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -193,6 +193,26 @@ SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true; SELECT 'abcdefg' SIMILAR TO '_bcd%' ESCAPE NULL AS null; SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '##' AS error; +-- Characters that should be left alone in character classes when a +-- SIMILAR TO regexp pattern is converted to POSIX style. +-- Underscore "_" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '_[_[:alpha:]_]_'; +-- Percentage "%" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '%[%[:alnum:]%]%'; +-- Dot "." +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '.[.[:alnum:].].'; +-- Dollar "$" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '$[$[:alnum:]$]$'; +-- Opening parenthesis "(" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '([([:alnum:](]('; +-- Caret "^" +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '^[^[:alnum:]^[^^][[^^]][\^][[\^]]\^]^'; +-- Closing square bracket "]" at the beginning of character class +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[]%][^]%][^%]%'; +-- Closing square bracket effective after two carets at the beginning +-- of character class. +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '[^^]^'; + -- Test backslash escapes in regexp_replace's replacement string SELECT regexp_replace('1112223333', E'(\\d{3})(\\d{3})(\\d{4})', E'(\\1) \\2-\\3'); SELECT regexp_replace('foobarrbazz', E'(.)\\1', E'X\\&Y', 'g'); From 7a2c14d8d5b2b9f8240efec55081177b339d9414 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 28 May 2025 09:43:45 +0900 Subject: [PATCH 652/796] Adjust regex for test with opening parenthesis in character classes As written, the test was throwing an error because of an unbalanced parenthesis. The regex used in the test is adjusted to not fail and to test the case of an opening parenthesis in a character class after some nested square brackets. Oversight in d46911e584d4. Discussion: https://postgr.es/m/16ab039d1af455652bdf4173402ddda145f2c73b.camel@cybertec.at --- src/test/regress/expected/strings.out | 9 +++++++-- src/test/regress/sql/strings.sql | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 0ae8e04ef823b..45f9a14a9571e 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -631,8 +631,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '$[$[:alnum:]$]$' (2 rows) -- Opening parenthesis "(" -EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '([([:alnum:](]('; -ERROR: invalid regular expression: parentheses () not balanced +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '()[([:alnum:](]()'; + QUERY PLAN +------------------------------------------------------ + Seq Scan on text_tbl + Filter: (f1 ~ '^(?:(?:)[([:alnum:](](?:))$'::text) +(2 rows) + -- Caret "^" EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '^[^[:alnum:]^[^^][[^^]][\^][[\^]]\^]^'; QUERY PLAN diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index 8fb80ed3061f4..faa43caee2e22 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -204,7 +204,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '.[.[:alnum:].].' -- Dollar "$" EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '$[$[:alnum:]$]$'; -- Opening parenthesis "(" -EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '([([:alnum:](]('; +EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '()[([:alnum:](]()'; -- Caret "^" EXPLAIN (COSTS OFF) SELECT * FROM TEXT_TBL WHERE f1 SIMILAR TO '^[^[:alnum:]^[^^][[^^]][\^][[\^]]\^]^'; -- Closing square bracket "]" at the beginning of character class From 137e4461ec88ff539f3e660011799fd2517b3164 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 29 May 2025 11:26:23 +0900 Subject: [PATCH 653/796] pg_stat_statements: Fix parameter number gaps in normalized queries pg_stat_statements anticipates that certain constant locations may be recorded multiple times and attempts to avoid calculating a length for these locations in fill_in_constant_lengths(). However, during generate_normalized_query() where normalized query strings are generated, these locations are not excluded from consideration. This could increment the parameter number counter for every recorded occurrence at such a location, leading to an incorrect normalization in certain cases with gaps in the numbers reported. For example, take this query: SELECT WHERE '1' IN ('2'::int, '3'::int::text) Before this commit, it would be normalized like that, with gaps in the parameter numbers: SELECT WHERE $1 IN ($3::int, $4::int::text) However the correct, less confusing one should be like that: SELECT WHERE $1 IN ($2::int, $3::int::text) This commit fixes the computation of the parameter numbers to track the number of constants replaced with an $n by a separate counter instead of the iterator used to loop through the list of locations. The underlying query IDs are not changed, neither are the normalized strings for existing PGSS hash entries. New entries with fresh normalized queries would automatically get reshaped based on the new parameter numbering. Issue discovered while discussing a separate problem for HEAD, but this affects all the stable branches. Author: Sami Imseih Discussion: https://postgr.es/m/CAA5RZ0tzxvWXsacGyxrixdhy3tTTDfJQqxyFBRFh31nNHBQ5qA@mail.gmail.com Backpatch-through: 13 --- .../pg_stat_statements/expected/extended.out | 58 +++++++++++++++++++ .../pg_stat_statements/expected/select.out | 29 ++++++++++ .../pg_stat_statements/pg_stat_statements.c | 4 +- contrib/pg_stat_statements/sql/extended.sql | 16 +++++ contrib/pg_stat_statements/sql/select.sql | 8 +++ 5 files changed, 114 insertions(+), 1 deletion(-) diff --git a/contrib/pg_stat_statements/expected/extended.out b/contrib/pg_stat_statements/expected/extended.out index dbc7868022662..f60cbc7bb2eb6 100644 --- a/contrib/pg_stat_statements/expected/extended.out +++ b/contrib/pg_stat_statements/expected/extended.out @@ -8,3 +8,61 @@ SELECT query_id IS NOT NULL AS query_id_set t (1 row) +-- Various parameter numbering patterns +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + +-- Unique query IDs with parameter numbers switched. +SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g +-- +(0 rows) + +SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g +-- +(0 rows) + +SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g +-- +(0 rows) + +SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g +-- +(0 rows) + +SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g +-- +(0 rows) + +-- Two groups of two queries with the same query ID. +SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g +-- +(1 row) + +SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g +-- +(0 rows) + +SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g +-- +(0 rows) + +SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g +-- +(0 rows) + +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls +--------------------------------------------------------------+------- + SELECT WHERE $1::int IN ($2::int, $3::int) | 1 + SELECT WHERE $2::int IN ($1::int, $3::int) | 2 + SELECT WHERE $2::int IN ($1::int, $3::int) | 2 + SELECT WHERE $2::int IN ($3::int, $1::int) | 1 + SELECT WHERE $3::int IN ($1::int, $2::int) | 1 + SELECT WHERE ($1::int, $4) IN (($5, $2::int), ($3::int, $6)) | 1 + SELECT WHERE ($2::int, $4) IN (($5, $3::int), ($1::int, $6)) | 1 + SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1 +(8 rows) + diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out index dd6c756f67d5b..c031003e62419 100644 --- a/contrib/pg_stat_statements/expected/select.out +++ b/contrib/pg_stat_statements/expected/select.out @@ -152,6 +152,35 @@ SELECT pg_stat_statements_reset() IS NOT NULL AS t; t (1 row) +-- normalization of constants and parameters, with constant locations +-- recorded one or more times. +SELECT pg_stat_statements_reset() IS NOT NULL AS t; + t +--- + t +(1 row) + +SELECT WHERE '1' IN ('1'::int, '3'::int::text); +-- +(1 row) + +SELECT WHERE (1, 2) IN ((1, 2), (2, 3)); +-- +(1 row) + +SELECT WHERE (3, 4) IN ((5, 6), (8, 7)); +-- +(0 rows) + +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls +------------------------------------------------------------------------+------- + SELECT WHERE $1 IN ($2::int, $3::int::text) | 1 + SELECT WHERE ($1, $2) IN (($3, $4), ($5, $6)) | 2 + SELECT pg_stat_statements_reset() IS NOT NULL AS t | 1 + SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 +(4 rows) + -- -- queries with locking clauses -- diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 12718dfe45c2d..c5c4edce42362 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2801,6 +2801,7 @@ generate_normalized_query(JumbleState *jstate, const char *query, n_quer_loc = 0, /* Normalized query byte location */ last_off = 0, /* Offset from start for previous tok */ last_tok_len = 0; /* Length (in bytes) of that tok */ + int num_constants_replaced = 0; /* * Get constants' lengths (core system only gives us locations). Note @@ -2844,7 +2845,8 @@ generate_normalized_query(JumbleState *jstate, const char *query, /* And insert a param symbol in place of the constant token */ n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d", - i + 1 + jstate->highest_extern_param_id); + num_constants_replaced + 1 + jstate->highest_extern_param_id); + num_constants_replaced++; quer_loc = off + tok_len; last_off = off; diff --git a/contrib/pg_stat_statements/sql/extended.sql b/contrib/pg_stat_statements/sql/extended.sql index 07b6c5a93d673..1dd49cf202503 100644 --- a/contrib/pg_stat_statements/sql/extended.sql +++ b/contrib/pg_stat_statements/sql/extended.sql @@ -5,3 +5,19 @@ SET pg_stat_statements.track_utility = FALSE; -- This test checks that an execute message sets a query ID. SELECT query_id IS NOT NULL AS query_id_set FROM pg_stat_activity WHERE pid = pg_backend_pid() \bind \g + +-- Various parameter numbering patterns +SELECT pg_stat_statements_reset() IS NOT NULL AS t; +-- Unique query IDs with parameter numbers switched. +SELECT WHERE ($1::int, 7) IN ((8, $2::int), ($3::int, 9)) \bind '1' '2' '3' \g +SELECT WHERE ($2::int, 10) IN ((11, $3::int), ($1::int, 12)) \bind '1' '2' '3' \g +SELECT WHERE $1::int IN ($2::int, $3::int) \bind '1' '2' '3' \g +SELECT WHERE $2::int IN ($3::int, $1::int) \bind '1' '2' '3' \g +SELECT WHERE $3::int IN ($1::int, $2::int) \bind '1' '2' '3' \g +-- Two groups of two queries with the same query ID. +SELECT WHERE '1'::int IN ($1::int, '2'::int) \bind '1' \g +SELECT WHERE '4'::int IN ($1::int, '5'::int) \bind '2' \g +SELECT WHERE $2::int IN ($1::int, '1'::int) \bind '1' '2' \g +SELECT WHERE $2::int IN ($1::int, '2'::int) \bind '3' '4' \g + +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql index eb45cb81ad23f..c419c84579607 100644 --- a/contrib/pg_stat_statements/sql/select.sql +++ b/contrib/pg_stat_statements/sql/select.sql @@ -58,6 +58,14 @@ DEALLOCATE pgss_test; SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; SELECT pg_stat_statements_reset() IS NOT NULL AS t; +-- normalization of constants and parameters, with constant locations +-- recorded one or more times. +SELECT pg_stat_statements_reset() IS NOT NULL AS t; +SELECT WHERE '1' IN ('1'::int, '3'::int::text); +SELECT WHERE (1, 2) IN ((1, 2), (2, 3)); +SELECT WHERE (3, 4) IN ((5, 6), (8, 7)); +SELECT query, calls FROM pg_stat_statements ORDER BY query COLLATE "C"; + -- -- queries with locking clauses -- From b19f2967b9fb583b194a7fa12c6d52e9bb2b7886 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 29 May 2025 17:50:32 +0900 Subject: [PATCH 654/796] Fix assertion failure in pg_prewarm() on objects without storage. An assertion test added in commit 049ef33 could fail when pg_prewarm() was called on objects without storage, such as partitioned tables. This resulted in the following failure in assert-enabled builds: Failed Assert("RelFileNumberIsValid(rlocator.relNumber)") Note that, in non-assert builds, pg_prewarm() just failed with an error in that case, so there was no ill effect in practice. This commit fixes the issue by having pg_prewarm() raise an error early if the specified object has no storage. This approach is similar to the fix in commit 4623d7144 for pg_freespacemap. Back-patched to v17, where the issue was introduced. Author: Masahiro Ikeda Reviewed-by: Dilip Kumar Reviewed-by: Richard Guo Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/e082e6027610fd0a4091ae6d033aa117@oss.nttdata.com Backpatch-through: 17 --- contrib/pg_prewarm/Makefile | 2 ++ contrib/pg_prewarm/expected/pg_prewarm.out | 10 ++++++++++ contrib/pg_prewarm/meson.build | 5 +++++ contrib/pg_prewarm/pg_prewarm.c | 8 ++++++++ contrib/pg_prewarm/sql/pg_prewarm.sql | 10 ++++++++++ 5 files changed, 35 insertions(+) create mode 100644 contrib/pg_prewarm/expected/pg_prewarm.out create mode 100644 contrib/pg_prewarm/sql/pg_prewarm.sql diff --git a/contrib/pg_prewarm/Makefile b/contrib/pg_prewarm/Makefile index 9cfde8c4e4fad..617ac8e09b2d8 100644 --- a/contrib/pg_prewarm/Makefile +++ b/contrib/pg_prewarm/Makefile @@ -10,6 +10,8 @@ EXTENSION = pg_prewarm DATA = pg_prewarm--1.1--1.2.sql pg_prewarm--1.1.sql pg_prewarm--1.0--1.1.sql PGFILEDESC = "pg_prewarm - preload relation data into system buffer cache" +REGRESS = pg_prewarm + TAP_TESTS = 1 ifdef USE_PGXS diff --git a/contrib/pg_prewarm/expected/pg_prewarm.out b/contrib/pg_prewarm/expected/pg_prewarm.out new file mode 100644 index 0000000000000..94e4fa1a9d237 --- /dev/null +++ b/contrib/pg_prewarm/expected/pg_prewarm.out @@ -0,0 +1,10 @@ +-- Test pg_prewarm extension +CREATE EXTENSION pg_prewarm; +-- pg_prewarm() should fail if the target relation has no storage. +CREATE TABLE test (c1 int) PARTITION BY RANGE (c1); +SELECT pg_prewarm('test', 'buffer'); +ERROR: relation "test" does not have storage +DETAIL: This operation is not supported for partitioned tables. +-- Cleanup +DROP TABLE test; +DROP EXTENSION pg_prewarm; diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build index da58f70a9f82c..6880d6d0a219f 100644 --- a/contrib/pg_prewarm/meson.build +++ b/contrib/pg_prewarm/meson.build @@ -29,6 +29,11 @@ tests += { 'name': 'pg_prewarm', 'sd': meson.current_source_dir(), 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_prewarm', + ], + }, 'tap': { 'tests': [ 't/001_basic.pl', diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c index 5c859e983c5ca..259ad83b77860 100644 --- a/contrib/pg_prewarm/pg_prewarm.c +++ b/contrib/pg_prewarm/pg_prewarm.c @@ -128,6 +128,14 @@ pg_prewarm(PG_FUNCTION_ARGS) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid)); + /* Check that the relation has storage. */ + if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" does not have storage", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + /* Check that the fork exists. */ if (!smgrexists(RelationGetSmgr(rel), forkNumber)) ereport(ERROR, diff --git a/contrib/pg_prewarm/sql/pg_prewarm.sql b/contrib/pg_prewarm/sql/pg_prewarm.sql new file mode 100644 index 0000000000000..c76f2c7916436 --- /dev/null +++ b/contrib/pg_prewarm/sql/pg_prewarm.sql @@ -0,0 +1,10 @@ +-- Test pg_prewarm extension +CREATE EXTENSION pg_prewarm; + +-- pg_prewarm() should fail if the target relation has no storage. +CREATE TABLE test (c1 int) PARTITION BY RANGE (c1); +SELECT pg_prewarm('test', 'buffer'); + +-- Cleanup +DROP TABLE test; +DROP EXTENSION pg_prewarm; From a8aac66ac3bff8b266b43570892da1b6c1e01c10 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 29 May 2025 10:39:55 -0400 Subject: [PATCH 655/796] Avoid resource leaks when a dblink connection fails. If we hit out-of-memory between creating the PGconn and inserting it into dblink's hashtable, we'd lose track of the PGconn, which is quite bad since it represents a live connection to a remote DB. Fix by rearranging things so that we create the hashtable entry first. Also reduce the number of states we have to deal with by getting rid of the separately-allocated remoteConn object, instead allocating it in-line in the hashtable entries. (That incidentally removes a session-lifespan memory leak observed in the regression tests.) There is an apparently-irreducible remaining OOM hazard, which is that if the connection fails at the libpq level (ie it's CONNECTION_BAD) then we have to pstrdup the PGconn's error message before we can release it, and theoretically that could fail. However, in such cases we're only leaking memory not a live remote connection, so I'm not convinced that it's worth sweating over. This is a pretty low-probability failure mode of course, but losing a live connection seems bad enough to justify back-patching. Author: Tom Lane Reviewed-by: Matheus Alcantara Discussion: https://postgr.es/m/1346940.1748381911@sss.pgh.pa.us Backpatch-through: 13 --- contrib/dblink/dblink.c | 78 ++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 755293456fff6..bf5fe7a71da40 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -100,7 +100,7 @@ static PGresult *storeQueryResult(volatile storeInfo *sinfo, PGconn *conn, const static void storeRow(volatile storeInfo *sinfo, PGresult *res, bool first); static remoteConn *getConnectionByName(const char *name); static HTAB *createConnHash(void); -static void createNewConnection(const char *name, remoteConn *rconn); +static remoteConn *createNewConnection(const char *name); static void deleteConnection(const char *name); static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts); static char **get_text_array_contents(ArrayType *array, int *numitems); @@ -114,7 +114,8 @@ static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclM static char *generate_relation_name(Relation rel); static void dblink_connstr_check(const char *connstr); static bool dblink_connstr_has_pw(const char *connstr); -static void dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr); +static void dblink_security_check(PGconn *conn, const char *connname, + const char *connstr); static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res, bool fail, const char *fmt,...) pg_attribute_printf(5, 6); static char *get_connect_string(const char *servername); @@ -137,16 +138,22 @@ static uint32 dblink_we_get_conn = 0; static uint32 dblink_we_get_result = 0; /* - * Following is list that holds multiple remote connections. + * Following is hash that holds multiple remote connections. * Calling convention of each dblink function changes to accept - * connection name as the first parameter. The connection list is + * connection name as the first parameter. The connection hash is * much like ecpg e.g. a mapping between a name and a PGconn object. + * + * To avoid potentially leaking a PGconn object in case of out-of-memory + * errors, we first create the hash entry, then open the PGconn. + * Hence, a hash entry whose rconn.conn pointer is NULL must be + * understood as a leftover from a failed create; it should be ignored + * by lookup operations, and silently replaced by create operations. */ typedef struct remoteConnHashEnt { char name[NAMEDATALEN]; - remoteConn *rconn; + remoteConn rconn; } remoteConnHashEnt; /* initial number of connection hashes */ @@ -225,7 +232,7 @@ dblink_get_conn(char *conname_or_str, errmsg("could not establish connection"), errdetail_internal("%s", msg))); } - dblink_security_check(conn, rconn, connstr); + dblink_security_check(conn, NULL, connstr); if (PQclientEncoding(conn) != GetDatabaseEncoding()) PQsetClientEncoding(conn, GetDatabaseEncodingName()); freeconn = true; @@ -288,15 +295,6 @@ dblink_connect(PG_FUNCTION_ARGS) else if (PG_NARGS() == 1) conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0)); - if (connname) - { - rconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext, - sizeof(remoteConn)); - rconn->conn = NULL; - rconn->openCursorCount = 0; - rconn->newXactForCursor = false; - } - /* first check for valid foreign data server */ connstr = get_connect_string(conname_or_str); if (connstr == NULL) @@ -309,6 +307,13 @@ dblink_connect(PG_FUNCTION_ARGS) if (dblink_we_connect == 0) dblink_we_connect = WaitEventExtensionNew("DblinkConnect"); + /* if we need a hashtable entry, make that first, since it might fail */ + if (connname) + { + rconn = createNewConnection(connname); + Assert(rconn->conn == NULL); + } + /* OK to make connection */ conn = libpqsrv_connect(connstr, dblink_we_connect); @@ -316,8 +321,8 @@ dblink_connect(PG_FUNCTION_ARGS) { msg = pchomp(PQerrorMessage(conn)); libpqsrv_disconnect(conn); - if (rconn) - pfree(rconn); + if (connname) + deleteConnection(connname); ereport(ERROR, (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), @@ -326,16 +331,16 @@ dblink_connect(PG_FUNCTION_ARGS) } /* check password actually used if not superuser */ - dblink_security_check(conn, rconn, connstr); + dblink_security_check(conn, connname, connstr); /* attempt to set client encoding to match server encoding, if needed */ if (PQclientEncoding(conn) != GetDatabaseEncoding()) PQsetClientEncoding(conn, GetDatabaseEncodingName()); + /* all OK, save away the conn */ if (connname) { rconn->conn = conn; - createNewConnection(connname, rconn); } else { @@ -375,10 +380,7 @@ dblink_disconnect(PG_FUNCTION_ARGS) libpqsrv_disconnect(conn); if (rconn) - { deleteConnection(conname); - pfree(rconn); - } else pconn->conn = NULL; @@ -1296,6 +1298,9 @@ dblink_get_connections(PG_FUNCTION_ARGS) hash_seq_init(&status, remoteConnHash); while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL) { + /* ignore it if it's not an open connection */ + if (hentry->rconn.conn == NULL) + continue; /* stash away current value */ astate = accumArrayResult(astate, CStringGetTextDatum(hentry->name), @@ -2533,8 +2538,8 @@ getConnectionByName(const char *name) hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key, HASH_FIND, NULL); - if (hentry) - return hentry->rconn; + if (hentry && hentry->rconn.conn != NULL) + return &hentry->rconn; return NULL; } @@ -2551,8 +2556,8 @@ createConnHash(void) HASH_ELEM | HASH_STRINGS); } -static void -createNewConnection(const char *name, remoteConn *rconn) +static remoteConn * +createNewConnection(const char *name) { remoteConnHashEnt *hentry; bool found; @@ -2566,17 +2571,15 @@ createNewConnection(const char *name, remoteConn *rconn) hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key, HASH_ENTER, &found); - if (found) - { - libpqsrv_disconnect(rconn->conn); - pfree(rconn); - + if (found && hentry->rconn.conn != NULL) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("duplicate connection name"))); - } - hentry->rconn = rconn; + /* New, or reusable, so initialize the rconn struct to zeroes */ + memset(&hentry->rconn, 0, sizeof(remoteConn)); + + return &hentry->rconn; } static void @@ -2604,9 +2607,12 @@ deleteConnection(const char *name) * We need to make sure that the connection made used credentials * which were provided by the user, so check what credentials were * used to connect and then make sure that they came from the user. + * + * On failure, we close "conn" and also delete the hashtable entry + * identified by "connname" (if that's not NULL). */ static void -dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr) +dblink_security_check(PGconn *conn, const char *connname, const char *connstr) { /* Superuser bypasses security check */ if (superuser()) @@ -2624,8 +2630,8 @@ dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr) /* Otherwise, fail out */ libpqsrv_disconnect(conn); - if (rconn) - pfree(rconn); + if (connname) + deleteConnection(connname); ereport(ERROR, (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), From ba6308c80539dee63bbc8a167042925b5c3af1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 30 May 2025 16:18:18 +0200 Subject: [PATCH 656/796] Fix broken-FK-detection query in release notes Commits 53af9491a043 and 2d5fe514052a fixed a number of problems with foreign keys that reference partitioned tables, and a query to detect already broken FKs was supplied with the release notes for 17.1, 16.5, 15.9, 14.14, 13.17. However, that query has a bug that causes it to wrongly report self-referential foreign keys even when they are correct, so if the user was to drop and rebuild the FKs as indicated, the query would continue to report them as needing to be repaired. Here we fix the query to not have that problem. Reported-by: Paul Foerster Discussion: https://postgr.es/m/5456A1D0-CD47-4315-9C65-71B27E7A2906@gmail.com Backpatch-through: 13-17 --- doc/src/sgml/release-17.sgml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index dddc3229f94f3..57e3b8fcc01d5 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -3581,10 +3581,14 @@ FROM pg_catalog.pg_constraint c WHERE contype = 'f' AND conparentid = 0 AND (SELECT count(*) FROM pg_catalog.pg_constraint c2 WHERE c2.conparentid = c.oid) <> - (SELECT count(*) FROM pg_catalog.pg_inherits i + ((SELECT count(*) FROM pg_catalog.pg_inherits i WHERE (i.inhparent = c.conrelid OR i.inhparent = c.confrelid) AND EXISTS (SELECT 1 FROM pg_catalog.pg_partitioned_table - WHERE partrelid = i.inhparent)); + WHERE partrelid = i.inhparent)) + + CASE WHEN pg_catalog.pg_partition_root(conrelid) = confrelid THEN + (SELECT count(*) FROM pg_catalog.pg_partition_tree(confrelid) + WHERE level = 1) + ELSE 0 END); Since it is possible that one or more of the ADD CONSTRAINT steps will fail, you should save the query's From c0edd2d0874c8f8e8189792d9eacb4b359c85493 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Sat, 31 May 2025 00:08:40 +0900 Subject: [PATCH 657/796] Make XactLockTableWait() and ConditionalXactLockTableWait() interruptable more. Previously, XactLockTableWait() and ConditionalXactLockTableWait() could enter a non-interruptible loop when they successfully acquired a lock on a transaction but the transaction still appeared to be running. Since this loop continued until the transaction completed, it could result in long, uninterruptible waits. Although this scenario is generally unlikely since XactLockTableWait() and ConditionalXactLockTableWait() can basically acquire a transaction lock only when the transaction is not running, it can occur in a hot standby. In such cases, the transaction may still appear active due to the KnownAssignedXids list, even while no lock on the transaction exists. For example, this situation can happen when creating a logical replication slot on a standby. The cause of the non-interruptible loop was the absence of CHECK_FOR_INTERRUPTS() within it. This commit adds CHECK_FOR_INTERRUPTS() to the loop in both functions, ensuring they can be interrupted safely. Back-patch to all supported branches. Author: Kevin K Biju Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/CAM45KeELdjhS-rGuvN=ZLJ_asvZACucZ9LZWVzH7bGcD12DDwg@mail.gmail.com Backpatch-through: 13 --- src/backend/storage/lmgr/lmgr.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index 094522acb414d..ae883ffac2b73 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -711,7 +711,10 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, * through, to avoid slowing down the normal case.) */ if (!first) + { + CHECK_FOR_INTERRUPTS(); pg_usleep(1000L); + } first = false; xid = SubTransGetTopmostTransaction(xid); } @@ -749,7 +752,10 @@ ConditionalXactLockTableWait(TransactionId xid) /* See XactLockTableWait about this case */ if (!first) + { + CHECK_FOR_INTERRUPTS(); pg_usleep(1000L); + } first = false; xid = SubTransGetTopmostTransaction(xid); } From 332271a38dfb49f7695493ed564fd21f4e1b6bbb Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 30 May 2025 12:55:15 -0400 Subject: [PATCH 658/796] Allow larger packets during GSSAPI authentication exchange. Our GSSAPI code only allows packet sizes up to 16kB. However it emerges that during authentication, larger packets might be needed; various authorities suggest 48kB or 64kB as the maximum packet size. This limitation caused login failure for AD users who belong to many AD groups. To add insult to injury, we gave an unintelligible error message, typically "GSSAPI context establishment error: The routine must be called again to complete its function: Unknown error". As noted in code comments, the 16kB packet limit is effectively a protocol constant once we are doing normal data transmission: the GSSAPI code splits the data stream at those points, and if we change the limit then we will have cross-version compatibility problems due to the receiver's buffer being too small in some combinations. However, during the authentication exchange the packet sizes are not determined by us, but by the underlying GSSAPI library. So we might as well just try to send what the library tells us to. An unpatched recipient will fail on a packet larger than 16kB, but that's not worse than the sender failing without even trying. So this doesn't introduce any meaningful compatibility problem. We still need a buffer size limit, but we can easily make it be 64kB rather than 16kB until transport negotiation is complete. (Larger values were discussed, but don't seem likely to add anything.) Reported-by: Chris Gooch Fix-suggested-by: Jacob Champion Author: Tom Lane Reviewed-by: Jacob Champion Discussion: https://postgr.es/m/DS0PR22MB5971A9C8A3F44BCC6293C4DABE99A@DS0PR22MB5971.namprd22.prod.outlook.com Backpatch-through: 13 --- src/backend/libpq/be-secure-gssapi.c | 61 +++++++++++++++------- src/interfaces/libpq/fe-secure-gssapi.c | 68 ++++++++++++++++++------- 2 files changed, 94 insertions(+), 35 deletions(-) diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c index bc04e78abba14..e95786c681817 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -45,11 +45,18 @@ * don't want the other side to send arbitrarily huge packets as we * would have to allocate memory for them to then pass them to GSSAPI. * - * Therefore, these two #define's are effectively part of the protocol + * Therefore, this #define is effectively part of the protocol * spec and can't ever be changed. */ -#define PQ_GSS_SEND_BUFFER_SIZE 16384 -#define PQ_GSS_RECV_BUFFER_SIZE 16384 +#define PQ_GSS_MAX_PACKET_SIZE 16384 /* includes uint32 header word */ + +/* + * However, during the authentication exchange we must cope with whatever + * message size the GSSAPI library wants to send (because our protocol + * doesn't support splitting those messages). Depending on configuration + * those messages might be as much as 64kB. + */ +#define PQ_GSS_AUTH_BUFFER_SIZE 65536 /* includes uint32 header word */ /* * Since we manage at most one GSS-encrypted connection per backend, @@ -209,12 +216,12 @@ be_gssapi_write(Port *port, void *ptr, size_t len) errno = ECONNRESET; return -1; } - if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) + if (output.length > PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)) { ereport(COMMERROR, (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (size_t) output.length, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)))); errno = ECONNRESET; return -1; } @@ -345,12 +352,12 @@ be_gssapi_read(Port *port, void *ptr, size_t len) /* Decode the packet length and check for overlength packet */ input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); - if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) + if (input.length > PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)) { ereport(COMMERROR, (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", (size_t) input.length, - PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)))); + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)))); errno = ECONNRESET; return -1; } @@ -514,10 +521,13 @@ secure_open_gssapi(Port *port) * that will never use them, and we ensure that the buffers are * sufficiently aligned for the length-word accesses that we do in some * places in this file. + * + * We'll use PQ_GSS_AUTH_BUFFER_SIZE-sized buffers until transport + * negotiation is complete, then switch to PQ_GSS_MAX_PACKET_SIZE. */ - PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE); - PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); - PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); + PqGSSSendBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); + PqGSSRecvBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); + PqGSSResultBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer) ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), @@ -565,16 +575,16 @@ secure_open_gssapi(Port *port) /* * During initialization, packets are always fully consumed and - * shouldn't ever be over PQ_GSS_RECV_BUFFER_SIZE in length. + * shouldn't ever be over PQ_GSS_AUTH_BUFFER_SIZE in total length. * * Verify on our side that the client doesn't do something funny. */ - if (input.length > PQ_GSS_RECV_BUFFER_SIZE) + if (input.length > PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)) { ereport(COMMERROR, - (errmsg("oversize GSSAPI packet sent by the client (%zu > %d)", + (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", (size_t) input.length, - PQ_GSS_RECV_BUFFER_SIZE))); + PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)))); return -1; } @@ -628,12 +638,12 @@ secure_open_gssapi(Port *port) { uint32 netlen = pg_hton32(output.length); - if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) + if (output.length > PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)) { ereport(COMMERROR, (errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)", (size_t) output.length, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); + PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)))); gss_release_buffer(&minor, &output); return -1; } @@ -688,12 +698,29 @@ secure_open_gssapi(Port *port) break; } + /* + * Release the large authentication buffers and allocate the ones we want + * for normal operation. + */ + free(PqGSSSendBuffer); + free(PqGSSRecvBuffer); + free(PqGSSResultBuffer); + PqGSSSendBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + PqGSSRecvBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + PqGSSResultBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer) + ereport(FATAL, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0; + PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0; + /* * Determine the max packet size which will fit in our buffer, after * accounting for the length. be_gssapi_write will need this. */ major = gss_wrap_size_limit(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32), &PqGSSMaxPktSize); if (GSS_ERROR(major)) diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index 98b314613c36c..6dfca69a062ad 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -47,11 +47,18 @@ * don't want the other side to send arbitrarily huge packets as we * would have to allocate memory for them to then pass them to GSSAPI. * - * Therefore, these two #define's are effectively part of the protocol + * Therefore, this #define is effectively part of the protocol * spec and can't ever be changed. */ -#define PQ_GSS_SEND_BUFFER_SIZE 16384 -#define PQ_GSS_RECV_BUFFER_SIZE 16384 +#define PQ_GSS_MAX_PACKET_SIZE 16384 /* includes uint32 header word */ + +/* + * However, during the authentication exchange we must cope with whatever + * message size the GSSAPI library wants to send (because our protocol + * doesn't support splitting those messages). Depending on configuration + * those messages might be as much as 64kB. + */ +#define PQ_GSS_AUTH_BUFFER_SIZE 65536 /* includes uint32 header word */ /* * We need these state variables per-connection. To allow the functions @@ -203,11 +210,11 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len) goto cleanup; } - if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) + if (output.length > PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)) { libpq_append_conn_error(conn, "client tried to send oversize GSSAPI packet (%zu > %zu)", (size_t) output.length, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)); + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)); errno = EIO; /* for lack of a better idea */ goto cleanup; } @@ -342,11 +349,11 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len) /* Decode the packet length and check for overlength packet */ input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); - if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) + if (input.length > PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)) { libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)", (size_t) input.length, - PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32)); errno = EIO; /* for lack of a better idea */ return -1; } @@ -485,12 +492,15 @@ pqsecure_open_gss(PGconn *conn) * initialize state variables. By malloc'ing the buffers separately, we * ensure that they are sufficiently aligned for the length-word accesses * that we do in some places in this file. + * + * We'll use PQ_GSS_AUTH_BUFFER_SIZE-sized buffers until transport + * negotiation is complete, then switch to PQ_GSS_MAX_PACKET_SIZE. */ if (PqGSSSendBuffer == NULL) { - PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE); - PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); - PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); + PqGSSSendBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); + PqGSSRecvBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); + PqGSSResultBuffer = malloc(PQ_GSS_AUTH_BUFFER_SIZE); if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer) { libpq_append_conn_error(conn, "out of memory"); @@ -564,13 +574,13 @@ pqsecure_open_gss(PGconn *conn) * so leave a spot at the end for a NULL byte too) and report that * back to the caller. */ - result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, PQ_GSS_RECV_BUFFER_SIZE - PqGSSRecvLength - 1, &ret); + result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, PQ_GSS_AUTH_BUFFER_SIZE - PqGSSRecvLength - 1, &ret); if (result != PGRES_POLLING_OK) return result; PqGSSRecvLength += ret; - Assert(PqGSSRecvLength < PQ_GSS_RECV_BUFFER_SIZE); + Assert(PqGSSRecvLength < PQ_GSS_AUTH_BUFFER_SIZE); PqGSSRecvBuffer[PqGSSRecvLength] = '\0'; appendPQExpBuffer(&conn->errorMessage, "%s\n", PqGSSRecvBuffer + 1); @@ -584,11 +594,11 @@ pqsecure_open_gss(PGconn *conn) /* Get the length and check for over-length packet */ input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); - if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) + if (input.length > PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)) { libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)", (size_t) input.length, - PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); + PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)); return PGRES_POLLING_FAILED; } @@ -668,12 +678,33 @@ pqsecure_open_gss(PGconn *conn) conn->gcred = GSS_C_NO_CREDENTIAL; gss_release_buffer(&minor, &output); + /* + * Release the large authentication buffers and allocate the ones we + * want for normal operation. (This maneuver is safe only because + * pqDropConnection will drop the buffers; otherwise, during a + * reconnection we'd be at risk of using undersized buffers during + * negotiation.) + */ + free(PqGSSSendBuffer); + free(PqGSSRecvBuffer); + free(PqGSSResultBuffer); + PqGSSSendBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + PqGSSRecvBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + PqGSSResultBuffer = malloc(PQ_GSS_MAX_PACKET_SIZE); + if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer) + { + libpq_append_conn_error(conn, "out of memory"); + return PGRES_POLLING_FAILED; + } + PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0; + PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0; + /* * Determine the max packet size which will fit in our buffer, after * accounting for the length. pg_GSS_write will need this. */ major = gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), + PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32), &PqGSSMaxPktSize); if (GSS_ERROR(major)) @@ -687,10 +718,11 @@ pqsecure_open_gss(PGconn *conn) } /* Must have output.length > 0 */ - if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) + if (output.length > PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)) { - pg_GSS_error(libpq_gettext("GSSAPI context establishment error"), - conn, major, minor); + libpq_append_conn_error(conn, "client tried to send oversize GSSAPI packet (%zu > %zu)", + (size_t) output.length, + PQ_GSS_AUTH_BUFFER_SIZE - sizeof(uint32)); gss_release_buffer(&minor, &output); return PGRES_POLLING_FAILED; } From 10d9e752b0625ae188f0ecce2d9d457d022a6518 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 30 May 2025 13:45:41 -0400 Subject: [PATCH 659/796] Fix memory leakage in postgres_fdw's DirectModify code path. postgres_fdw tries to use PG_TRY blocks to ensure that it will eventually free the PGresult created by the remote modify command. However, it's fundamentally impossible for this scheme to work reliably when there's RETURNING data, because the query could fail in between invocations of postgres_fdw's DirectModify methods. There is at least one instance of exactly this situation in the regression tests, and the ensuing session-lifespan leak is visible under Valgrind. We can improve matters by using a memory context reset callback attached to the ExecutorState context. That ensures that the PGresult will be freed when the ExecutorState context is torn down, even if control never reaches postgresEndDirectModify. I have little faith that there aren't other potential PGresult leakages in the backend modules that use libpq. So I think it'd be a good idea to apply this concept universally by creating infrastructure that attaches a reset callback to every PGresult generated in the backend. However, that seems too invasive for v18 at this point, let alone the back branches. So for the moment, apply this narrow fix that just makes DirectModify safe. I have a patch in the queue for the more general idea, but it will have to wait for v19. Author: Tom Lane Reviewed-by: Matheus Alcantara Discussion: https://postgr.es/m/2976982.1748049023@sss.pgh.pa.us Backpatch-through: 13 --- contrib/postgres_fdw/postgres_fdw.c | 62 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 7c4b91e01fd72..353a4e6a2a3eb 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -239,6 +239,7 @@ typedef struct PgFdwDirectModifyState PGresult *result; /* result for query */ int num_tuples; /* # of result tuples */ int next_tuple; /* index of next one to return */ + MemoryContextCallback result_cb; /* ensures result will get freed */ Relation resultRel; /* relcache entry for the target relation */ AttrNumber *attnoMap; /* array of attnums of input user columns */ AttrNumber ctidAttno; /* attnum of input ctid column */ @@ -2662,6 +2663,17 @@ postgresBeginDirectModify(ForeignScanState *node, int eflags) dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState)); node->fdw_state = (void *) dmstate; + /* + * We use a memory context callback to ensure that the dmstate's PGresult + * (if any) will be released, even if the query fails somewhere that's + * outside our control. The callback is always armed for the duration of + * the query; this relies on PQclear(NULL) being a no-op. + */ + dmstate->result_cb.func = (MemoryContextCallbackFunction) PQclear; + dmstate->result_cb.arg = NULL; + MemoryContextRegisterResetCallback(CurrentMemoryContext, + &dmstate->result_cb); + /* * Identify which user to do the remote access as. This should match what * ExecCheckPermissions() does. @@ -2809,7 +2821,13 @@ postgresEndDirectModify(ForeignScanState *node) return; /* Release PGresult */ - PQclear(dmstate->result); + if (dmstate->result) + { + PQclear(dmstate->result); + dmstate->result = NULL; + /* ... and don't forget to disable the callback */ + dmstate->result_cb.arg = NULL; + } /* Release remote connection */ ReleaseConnection(dmstate->conn); @@ -4578,13 +4596,17 @@ execute_dml_stmt(ForeignScanState *node) /* * Get the result, and check for success. * - * We don't use a PG_TRY block here, so be careful not to throw error - * without releasing the PGresult. + * We use a memory context callback to ensure that the PGresult will be + * released, even if the query fails somewhere that's outside our control. + * The callback is already registered, just need to fill in its arg. */ + Assert(dmstate->result == NULL); dmstate->result = pgfdw_get_result(dmstate->conn); + dmstate->result_cb.arg = dmstate->result; + if (PQresultStatus(dmstate->result) != (dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK)) - pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, true, + pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, false, dmstate->query); /* Get the number of rows affected. */ @@ -4628,30 +4650,16 @@ get_returning_data(ForeignScanState *node) } else { - /* - * On error, be sure to release the PGresult on the way out. Callers - * do not have PG_TRY blocks to ensure this happens. - */ - PG_TRY(); - { - HeapTuple newtup; - - newtup = make_tuple_from_result_row(dmstate->result, - dmstate->next_tuple, - dmstate->rel, - dmstate->attinmeta, - dmstate->retrieved_attrs, - node, - dmstate->temp_cxt); - ExecStoreHeapTuple(newtup, slot, false); - } - PG_CATCH(); - { - PQclear(dmstate->result); - PG_RE_THROW(); - } - PG_END_TRY(); + HeapTuple newtup; + newtup = make_tuple_from_result_row(dmstate->result, + dmstate->next_tuple, + dmstate->rel, + dmstate->attinmeta, + dmstate->retrieved_attrs, + node, + dmstate->temp_cxt); + ExecStoreHeapTuple(newtup, slot, false); /* Get the updated/deleted tuple. */ if (dmstate->rel) resultSlot = slot; From a0da792faed2941b2f58ca9bcef780fb10cc16dd Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 30 May 2025 15:17:28 -0500 Subject: [PATCH 660/796] Ensure we have a snapshot when updating various system catalogs. A few places that access system catalogs don't set up an active snapshot before potentially accessing their TOAST tables. To fix, push an active snapshot just before each section of code that might require accessing one of these TOAST tables, and pop it shortly afterwards. While at it, this commit adds some rather strict assertions in an attempt to prevent such issues in the future. Commit 16bf24e0e4 recently removed pg_replication_origin's TOAST table in order to fix the same problem for that catalog. On the back-branches, those bugs are left in place. We cannot easily remove a catalog's TOAST table on released major versions, and only replication origins with extremely long names are affected. Given the low severity of the issue, fixing older versions doesn't seem worth the trouble of significantly modifying the patch. Also, on v13 and v14, the aforementioned strict assertions have been omitted because commit 2776922201, which added HaveRegisteredOrActiveSnapshot(), was not back-patched. While we could probably back-patch it now, I've opted against it because it seems unlikely that new TOAST snapshot issues will be introduced in the oldest supported versions. Reported-by: Alexander Lakhin Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/18127-fe54b6a667f29658%40postgresql.org Discussion: https://postgr.es/m/18309-c0bf914950c46692%40postgresql.org Discussion: https://postgr.es/m/ZvMSUPOqUU-VNADN%40nathan Backpatch-through: 13 --- src/backend/access/heap/heapam.c | 41 ++++++++++++++++++++++++ src/backend/commands/indexcmds.c | 8 +++++ src/backend/commands/tablecmds.c | 8 +++++ src/backend/postmaster/autovacuum.c | 7 ++++ src/backend/replication/logical/worker.c | 24 ++++++++++++++ 5 files changed, 88 insertions(+) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 95e3be524a7f8..ccf151f548b46 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -53,6 +53,7 @@ #include "catalog/catalog.h" #include "catalog/pg_database.h" #include "catalog/pg_database_d.h" +#include "catalog/pg_replication_origin.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "pgstat.h" @@ -230,6 +231,38 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] = #define TUPLOCK_from_mxstatus(status) \ (MultiXactStatusLock[(status)]) +/* + * Check that we have a valid snapshot if we might need TOAST access. + */ +static inline void +AssertHasSnapshotForToast(Relation rel) +{ +#ifdef USE_ASSERT_CHECKING + + /* bootstrap mode in particular breaks this rule */ + if (!IsNormalProcessingMode()) + return; + + /* if the relation doesn't have a TOAST table, we are good */ + if (!OidIsValid(rel->rd_rel->reltoastrelid)) + return; + + /* + * Commit 16bf24e fixed accesses to pg_replication_origin without a + * an active snapshot by removing its TOAST table. On older branches, + * these bugs are left in place. Its only varlena column is roname (the + * replication origin name), so this is only a problem if the name + * requires out-of-line storage, which seems unlikely. In any case, + * fixing it doesn't seem worth extra code churn on the back-branches. + */ + if (RelationGetRelid(rel) == ReplicationOriginRelationId) + return; + + Assert(HaveRegisteredOrActiveSnapshot()); + +#endif /* USE_ASSERT_CHECKING */ +} + /* ---------------------------------------------------------------- * heap support routines * ---------------------------------------------------------------- @@ -2015,6 +2048,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, Assert(HeapTupleHeaderGetNatts(tup->t_data) <= RelationGetNumberOfAttributes(relation)); + AssertHasSnapshotForToast(relation); + /* * Fill in tuple header fields and toast the tuple if necessary. * @@ -2292,6 +2327,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* currently not needed (thus unsupported) for heap_multi_insert() */ Assert(!(options & HEAP_INSERT_NO_LOGICAL)); + AssertHasSnapshotForToast(relation); + needwal = RelationNeedsWAL(relation); saveFreeSpace = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); @@ -2714,6 +2751,8 @@ heap_delete(Relation relation, ItemPointer tid, Assert(ItemPointerIsValid(tid)); + AssertHasSnapshotForToast(relation); + /* * Forbid this during a parallel operation, lest it allocate a combo CID. * Other workers might need that combo CID for visibility checks, and we @@ -3209,6 +3248,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Assert(HeapTupleHeaderGetNatts(newtup->t_data) <= RelationGetNumberOfAttributes(relation)); + AssertHasSnapshotForToast(relation); + /* * Forbid this during a parallel operation, lest it allocate a combo CID. * Other workers might need that combo CID for visibility checks, and we diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index ff96a56c9e9b9..56a425a171e4d 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -4094,12 +4094,20 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein get_rel_namespace(oldidx->tableId), false); + /* + * Swapping the indexes might involve TOAST table access, so ensure we + * have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + /* * Swap old index with the new one. This also marks the new one as * valid and the old one as not valid. */ index_concurrently_swap(newidx->indexId, oldidx->indexId, oldName); + PopActiveSnapshot(); + /* * Invalidate the relcache for the table, so that after this commit * all sessions will refresh any cached plans that might reference the diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index b1090497eb401..06a0067f829fc 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -19268,9 +19268,17 @@ ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, tab->rel = rel; } + /* + * Detaching the partition might involve TOAST table access, so ensure we + * have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + /* Do the final part of detaching */ DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid); + PopActiveSnapshot(); + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel)); /* keep our lock until commit */ diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 0e30a7fda66b5..ec5699e48e884 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2220,6 +2220,12 @@ do_autovacuum(void) get_namespace_name(classForm->relnamespace), NameStr(classForm->relname)))); + /* + * Deletion might involve TOAST table access, so ensure we have a + * valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + object.classId = RelationRelationId; object.objectId = relid; object.objectSubId = 0; @@ -2232,6 +2238,7 @@ do_autovacuum(void) * To commit the deletion, end current transaction and start a new * one. Note this also releases the locks we took. */ + PopActiveSnapshot(); CommitTransactionCommand(); StartTransactionCommand(); diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index db09978697f39..1bff6c92dda0c 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -4550,8 +4550,16 @@ run_apply_worker() walrcv_startstreaming(LogRepWorkerWalRcvConn, &options); StartTransactionCommand(); + + /* + * Updating pg_subscription might involve TOAST table access, so + * ensure we have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED); MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED; + PopActiveSnapshot(); CommitTransactionCommand(); } else @@ -4767,7 +4775,15 @@ DisableSubscriptionAndExit(void) /* Disable the subscription */ StartTransactionCommand(); + + /* + * Updating pg_subscription might involve TOAST table access, so ensure we + * have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + DisableSubscription(MySubscription->oid); + PopActiveSnapshot(); CommitTransactionCommand(); /* Ensure we remove no-longer-useful entry for worker's start time */ @@ -4871,6 +4887,12 @@ clear_subscription_skip_lsn(XLogRecPtr finish_lsn) started_tx = true; } + /* + * Updating pg_subscription might involve TOAST table access, so ensure we + * have a valid snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + /* * Protect subskiplsn of pg_subscription from being concurrently updated * while clearing it. @@ -4929,6 +4951,8 @@ clear_subscription_skip_lsn(XLogRecPtr finish_lsn) heap_freetuple(tup); table_close(rel, NoLock); + PopActiveSnapshot(); + if (started_tx) CommitTransactionCommand(); } From 1402fcaa615257464423c6bf9045599aa341024e Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Sat, 31 May 2025 12:17:30 +0100 Subject: [PATCH 661/796] Fix MERGE into a plain inheritance parent table. When a MERGE's target table is the parent of an inheritance tree, any INSERT actions insert into the parent table using ModifyTableState's rootResultRelInfo. However, there are two bugs in the way this is initialized: 1. ExecInitMerge() incorrectly uses a different ResultRelInfo entry from ModifyTableState's resultRelInfo array to build the insert projection, which may not be compatible with rootResultRelInfo. 2. ExecInitModifyTable() does not fully initialize rootResultRelInfo. Specifically, ri_WithCheckOptions, ri_WithCheckOptionExprs, ri_returningList, and ri_projectReturning are not initialized. This can lead to crashes, or incorrect query results due to failing to check WCO's or process the RETURNING list for INSERT actions. Fix both these bugs in ExecInitMerge(), noting that it is only necessary to fully initialize rootResultRelInfo if the MERGE has INSERT actions and the target table is a plain inheritance parent. Backpatch to v15, where MERGE was introduced. Reported-by: Andres Freund Author: Dean Rasheed Reviewed-by: Jian He Reviewed-by: Tender Wang Discussion: https://postgr.es/m/4rlmjfniiyffp6b3kv4pfy4jw3pciy6mq72rdgnedsnbsx7qe5@j5hlpiwdguvc Backpatch-through: 15 --- src/backend/executor/nodeModifyTable.c | 129 ++++++++++++++++++++++++- src/test/regress/expected/merge.out | 70 ++++++++++++++ src/test/regress/sql/merge.sql | 49 ++++++++++ 3 files changed, 245 insertions(+), 3 deletions(-) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index d499f8422e8cc..a0d1091ec014a 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -64,6 +64,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" #include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/datum.h" @@ -3551,6 +3552,7 @@ ExecInitMerge(ModifyTableState *mtstate, EState *estate) switch (action->commandType) { case CMD_INSERT: + /* INSERT actions always use rootRelInfo */ ExecCheckPlanOutput(rootRelInfo->ri_RelationDesc, action->targetList); @@ -3590,9 +3592,23 @@ ExecInitMerge(ModifyTableState *mtstate, EState *estate) } else { - /* not partitioned? use the stock relation and slot */ - tgtslot = resultRelInfo->ri_newTupleSlot; - tgtdesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + /* + * If the MERGE targets an inherited table, we insert + * into the root table, so we must initialize its + * "new" tuple slot, if not already done, and use its + * relation descriptor for the projection. + * + * For non-inherited tables, rootRelInfo and + * resultRelInfo are the same, and the "new" tuple + * slot will already have been initialized. + */ + if (rootRelInfo->ri_newTupleSlot == NULL) + rootRelInfo->ri_newTupleSlot = + table_slot_create(rootRelInfo->ri_RelationDesc, + &estate->es_tupleTable); + + tgtslot = rootRelInfo->ri_newTupleSlot; + tgtdesc = RelationGetDescr(rootRelInfo->ri_RelationDesc); } action_state->mas_proj = @@ -3625,6 +3641,113 @@ ExecInitMerge(ModifyTableState *mtstate, EState *estate) } } } + + /* + * If the MERGE targets an inherited table, any INSERT actions will use + * rootRelInfo, and rootRelInfo will not be in the resultRelInfo array. + * Therefore we must initialize its WITH CHECK OPTION constraints and + * RETURNING projection, as ExecInitModifyTable did for the resultRelInfo + * entries. + * + * Note that the planner does not build a withCheckOptionList or + * returningList for the root relation, but as in ExecInitPartitionInfo, + * we can use the first resultRelInfo entry as a reference to calculate + * the attno's for the root table. + */ + if (rootRelInfo != mtstate->resultRelInfo && + rootRelInfo->ri_RelationDesc->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && + (mtstate->mt_merge_subcommands & MERGE_INSERT) != 0) + { + Relation rootRelation = rootRelInfo->ri_RelationDesc; + Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc; + int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; + AttrMap *part_attmap = NULL; + bool found_whole_row; + + if (node->withCheckOptionLists != NIL) + { + List *wcoList; + List *wcoExprs = NIL; + + /* There should be as many WCO lists as result rels */ + Assert(list_length(node->withCheckOptionLists) == + list_length(node->resultRelations)); + + /* + * Use the first WCO list as a reference. In the most common case, + * this will be for the same relation as rootRelInfo, and so there + * will be no need to adjust its attno's. + */ + wcoList = linitial(node->withCheckOptionLists); + if (rootRelation != firstResultRel) + { + /* Convert any Vars in it to contain the root's attno's */ + part_attmap = + build_attrmap_by_name(RelationGetDescr(rootRelation), + RelationGetDescr(firstResultRel), + false); + + wcoList = (List *) + map_variable_attnos((Node *) wcoList, + firstVarno, 0, + part_attmap, + RelationGetForm(rootRelation)->reltype, + &found_whole_row); + } + + foreach(lc, wcoList) + { + WithCheckOption *wco = lfirst_node(WithCheckOption, lc); + ExprState *wcoExpr = ExecInitQual(castNode(List, wco->qual), + &mtstate->ps); + + wcoExprs = lappend(wcoExprs, wcoExpr); + } + + rootRelInfo->ri_WithCheckOptions = wcoList; + rootRelInfo->ri_WithCheckOptionExprs = wcoExprs; + } + + if (node->returningLists != NIL) + { + List *returningList; + + /* There should be as many returning lists as result rels */ + Assert(list_length(node->returningLists) == + list_length(node->resultRelations)); + + /* + * Use the first returning list as a reference. In the most common + * case, this will be for the same relation as rootRelInfo, and so + * there will be no need to adjust its attno's. + */ + returningList = linitial(node->returningLists); + if (rootRelation != firstResultRel) + { + /* Convert any Vars in it to contain the root's attno's */ + if (part_attmap == NULL) + part_attmap = + build_attrmap_by_name(RelationGetDescr(rootRelation), + RelationGetDescr(firstResultRel), + false); + + returningList = (List *) + map_variable_attnos((Node *) returningList, + firstVarno, 0, + part_attmap, + RelationGetForm(rootRelation)->reltype, + &found_whole_row); + } + rootRelInfo->ri_returningList = returningList; + + /* Initialize the RETURNING projection */ + rootRelInfo->ri_projectReturning = + ExecBuildProjectionInfo(returningList, econtext, + mtstate->ps.ps_ResultTupleSlot, + &mtstate->ps, + RelationGetDescr(rootRelation)); + } + } } /* diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index e9c9b15d3c80d..f9ad47f978fac 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -2700,6 +2700,76 @@ SELECT * FROM new_measurement ORDER BY city_id, logdate; 1 | 01-17-2007 | | (2 rows) +-- MERGE into inheritance root table +DROP TRIGGER insert_measurement_trigger ON measurement; +ALTER TABLE measurement ADD CONSTRAINT mcheck CHECK (city_id = 0) NO INHERIT; +EXPLAIN (COSTS OFF) +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); + QUERY PLAN +-------------------------------------------------------------------------- + Merge on measurement m + Merge on measurement_y2007m01 m_1 + -> Nested Loop Left Join + -> Result + -> Seq Scan on measurement_y2007m01 m_1 + Filter: ((city_id = 1) AND (logdate = '01-17-2007'::date)) +(6 rows) + +BEGIN; +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); +SELECT * FROM ONLY measurement ORDER BY city_id, logdate; + city_id | logdate | peaktemp | unitsales +---------+------------+----------+----------- + 0 | 07-21-2005 | 25 | 35 + 0 | 01-17-2007 | 25 | 100 +(2 rows) + +ROLLBACK; +ALTER TABLE measurement ENABLE ROW LEVEL SECURITY; +ALTER TABLE measurement FORCE ROW LEVEL SECURITY; +CREATE POLICY measurement_p ON measurement USING (peaktemp IS NOT NULL); +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, NULL, 100); -- should fail +ERROR: new row violates row-level security policy for table "measurement" +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); -- ok +SELECT * FROM ONLY measurement ORDER BY city_id, logdate; + city_id | logdate | peaktemp | unitsales +---------+------------+----------+----------- + 0 | 07-21-2005 | 25 | 35 + 0 | 01-17-2007 | 25 | 100 +(2 rows) + +MERGE INTO measurement m + USING (VALUES (1, '01-18-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 200) +RETURNING merge_action(), m.*; + merge_action | city_id | logdate | peaktemp | unitsales +--------------+---------+------------+----------+----------- + INSERT | 0 | 01-18-2007 | 25 | 200 +(1 row) + DROP TABLE measurement, new_measurement CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table measurement_y2006m02 diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index 556777e4f4b51..d8ac0a4dd45fc 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -1720,6 +1720,55 @@ WHEN MATCHED THEN DELETE; SELECT * FROM new_measurement ORDER BY city_id, logdate; +-- MERGE into inheritance root table +DROP TRIGGER insert_measurement_trigger ON measurement; +ALTER TABLE measurement ADD CONSTRAINT mcheck CHECK (city_id = 0) NO INHERIT; + +EXPLAIN (COSTS OFF) +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); + +BEGIN; +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); +SELECT * FROM ONLY measurement ORDER BY city_id, logdate; +ROLLBACK; + +ALTER TABLE measurement ENABLE ROW LEVEL SECURITY; +ALTER TABLE measurement FORCE ROW LEVEL SECURITY; +CREATE POLICY measurement_p ON measurement USING (peaktemp IS NOT NULL); + +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, NULL, 100); -- should fail + +MERGE INTO measurement m + USING (VALUES (1, '01-17-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 100); -- ok +SELECT * FROM ONLY measurement ORDER BY city_id, logdate; + +MERGE INTO measurement m + USING (VALUES (1, '01-18-2007'::date)) nm(city_id, logdate) ON + (m.city_id = nm.city_id and m.logdate=nm.logdate) +WHEN NOT MATCHED THEN INSERT + (city_id, logdate, peaktemp, unitsales) + VALUES (city_id - 1, logdate, 25, 200) +RETURNING merge_action(), m.*; + DROP TABLE measurement, new_measurement CASCADE; DROP FUNCTION measurement_insert_trigger(); From 62f56e4e36f505f9d21e73c0f94ff21776eb5f3a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 1 Jun 2025 14:48:35 -0400 Subject: [PATCH 662/796] Fix edge-case resource leaks in PL/Python error reporting. PLy_elog_impl and its subroutine PLy_traceback intended to avoid leaking any PyObject reference counts, but their coverage of the matter was sadly incomplete. In particular, out-of-memory errors in most of the string-construction subroutines could lead to reference count leaks, because those calls were outside the PG_TRY blocks responsible for dropping reference counts. Fix by (a) adjusting the scopes of the PG_TRY blocks, and (b) moving the responsibility for releasing the reference counts of the traceback-stack objects to PLy_elog_impl. This requires some additional "volatile" markers, but not too many. In passing, fix an ancient thinko: use of the "e_module_o" PyObject was guarded by "if (e_type_s)", where surely "if (e_module_o)" was meant. This would only have visible consequences if the "__name__" attribute were present but the "__module__" attribute wasn't, which apparently never happens; but someday it might. Rearranging the PG_TRY blocks requires indenting a fair amount of code one more tab stop, which I'll do separately for clarity. Author: Tom Lane Discussion: https://postgr.es/m/2954090.1748723636@sss.pgh.pa.us Backpatch-through: 13 --- src/pl/plpython/plpy_elog.c | 105 +++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c index 70de5ba13d74b..aedcf5dacc88f 100644 --- a/src/pl/plpython/plpy_elog.c +++ b/src/pl/plpython/plpy_elog.c @@ -18,7 +18,8 @@ PyObject *PLy_exc_spi_error = NULL; static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, - char **xmsg, char **tbmsg, int *tb_depth); + char *volatile *xmsg, char *volatile *tbmsg, + int *tb_depth); static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position, char **schema_name, char **table_name, char **column_name, @@ -43,13 +44,23 @@ void PLy_elog_impl(int elevel, const char *fmt,...) { int save_errno = errno; - char *xmsg; - char *tbmsg; + char *volatile xmsg = NULL; + char *volatile tbmsg = NULL; int tb_depth; StringInfoData emsg; PyObject *exc, *val, *tb; + + /* If we'll need emsg, must initialize it before entering PG_TRY */ + if (fmt) + initStringInfo(&emsg); + + PyErr_Fetch(&exc, &val, &tb); + + /* Use a PG_TRY block to ensure we release the PyObjects just acquired */ + PG_TRY(); + { const char *primary = NULL; int sqlerrcode = 0; char *detail = NULL; @@ -62,8 +73,6 @@ PLy_elog_impl(int elevel, const char *fmt,...) char *datatype_name = NULL; char *constraint_name = NULL; - PyErr_Fetch(&exc, &val, &tb); - if (exc != NULL) { PyErr_NormalizeException(&exc, &val, &tb); @@ -81,13 +90,11 @@ PLy_elog_impl(int elevel, const char *fmt,...) elevel = FATAL; } - /* this releases our refcount on tb! */ PLy_traceback(exc, val, tb, &xmsg, &tbmsg, &tb_depth); if (fmt) { - initStringInfo(&emsg); for (;;) { va_list ap; @@ -113,8 +120,6 @@ PLy_elog_impl(int elevel, const char *fmt,...) primary = xmsg; } - PG_TRY(); - { ereport(elevel, (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg_internal("%s", primary ? primary : "no exception data"), @@ -136,14 +141,23 @@ PLy_elog_impl(int elevel, const char *fmt,...) } PG_FINALLY(); { + Py_XDECREF(exc); + Py_XDECREF(val); + /* Must release all the objects in the traceback stack */ + while (tb != NULL && tb != Py_None) + { + PyObject *tb_prev = tb; + + tb = PyObject_GetAttrString(tb, "tb_next"); + Py_DECREF(tb_prev); + } + /* For neatness' sake, also release our string buffers */ if (fmt) pfree(emsg.data); if (xmsg) pfree(xmsg); if (tbmsg) pfree(tbmsg); - Py_XDECREF(exc); - Py_XDECREF(val); } PG_END_TRY(); } @@ -154,21 +168,14 @@ PLy_elog_impl(int elevel, const char *fmt,...) * The exception error message is returned in xmsg, the traceback in * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. - * - * We release refcounts on all the Python objects in the traceback stack, - * but not on e or v. */ static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, - char **xmsg, char **tbmsg, int *tb_depth) + char *volatile *xmsg, char *volatile *tbmsg, int *tb_depth) { - PyObject *e_type_o; - PyObject *e_module_o; - char *e_type_s = NULL; - char *e_module_s = NULL; - PyObject *vob = NULL; - char *vstr; - StringInfoData xstr; + PyObject *volatile e_type_o = NULL; + PyObject *volatile e_module_o = NULL; + PyObject *volatile vob = NULL; StringInfoData tbstr; /* @@ -186,12 +193,18 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, /* * Format the exception and its value and put it in xmsg. */ + PG_TRY(); + { + char *e_type_s = NULL; + char *e_module_s = NULL; + const char *vstr; + StringInfoData xstr; e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); if (e_type_o) e_type_s = PLyUnicode_AsString(e_type_o); - if (e_type_s) + if (e_module_o) e_module_s = PLyUnicode_AsString(e_module_o); if (v && ((vob = PyObject_Str(v)) != NULL)) @@ -215,18 +228,24 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, appendStringInfo(&xstr, ": %s", vstr); *xmsg = xstr.data; + } + PG_FINALLY(); + { + Py_XDECREF(e_type_o); + Py_XDECREF(e_module_o); + Py_XDECREF(vob); + } + PG_END_TRY(); /* * Now format the traceback and put it in tbmsg. */ - *tb_depth = 0; initStringInfo(&tbstr); /* Mimic Python traceback reporting as close as possible. */ appendStringInfoString(&tbstr, "Traceback (most recent call last):"); while (tb != NULL && tb != Py_None) { - PyObject *volatile tb_prev = NULL; PyObject *volatile frame = NULL; PyObject *volatile code = NULL; PyObject *volatile name = NULL; @@ -254,17 +273,6 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, filename = PyObject_GetAttrString(code, "co_filename"); if (filename == NULL) elog(ERROR, "could not get file name from Python code object"); - } - PG_CATCH(); - { - Py_XDECREF(frame); - Py_XDECREF(code); - Py_XDECREF(name); - Py_XDECREF(lineno); - Py_XDECREF(filename); - PG_RE_THROW(); - } - PG_END_TRY(); /* The first frame always points at , skip it. */ if (*tb_depth > 0) @@ -320,18 +328,19 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, } } } + } + PG_FINALLY(); + { + Py_XDECREF(frame); + Py_XDECREF(code); + Py_XDECREF(name); + Py_XDECREF(lineno); + Py_XDECREF(filename); + } + PG_END_TRY(); - Py_DECREF(frame); - Py_DECREF(code); - Py_DECREF(name); - Py_DECREF(lineno); - Py_DECREF(filename); - - /* Release the current frame and go to the next one. */ - tb_prev = tb; + /* Advance to the next frame. */ tb = PyObject_GetAttrString(tb, "tb_next"); - Assert(tb_prev != Py_None); - Py_DECREF(tb_prev); if (tb == NULL) elog(ERROR, "could not traverse Python traceback"); (*tb_depth)++; @@ -339,10 +348,6 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, /* Return the traceback. */ *tbmsg = tbstr.data; - - Py_XDECREF(e_type_o); - Py_XDECREF(e_module_o); - Py_XDECREF(vob); } /* From 258a650481b43d21b1557769c0ae4bd262045f9a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 1 Jun 2025 14:55:24 -0400 Subject: [PATCH 663/796] Run pgindent on the previous commit. Clean up after rearranging PG_TRY blocks. Author: Tom Lane Discussion: https://postgr.es/m/2954090.1748723636@sss.pgh.pa.us Backpatch-through: 13 --- src/pl/plpython/plpy_elog.c | 266 ++++++++++++++++++------------------ 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c index aedcf5dacc88f..13f6b87a9f20b 100644 --- a/src/pl/plpython/plpy_elog.c +++ b/src/pl/plpython/plpy_elog.c @@ -61,64 +61,64 @@ PLy_elog_impl(int elevel, const char *fmt,...) /* Use a PG_TRY block to ensure we release the PyObjects just acquired */ PG_TRY(); { - const char *primary = NULL; - int sqlerrcode = 0; - char *detail = NULL; - char *hint = NULL; - char *query = NULL; - int position = 0; - char *schema_name = NULL; - char *table_name = NULL; - char *column_name = NULL; - char *datatype_name = NULL; - char *constraint_name = NULL; - - if (exc != NULL) - { - PyErr_NormalizeException(&exc, &val, &tb); - - if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) - PLy_get_spi_error_data(val, &sqlerrcode, - &detail, &hint, &query, &position, + const char *primary = NULL; + int sqlerrcode = 0; + char *detail = NULL; + char *hint = NULL; + char *query = NULL; + int position = 0; + char *schema_name = NULL; + char *table_name = NULL; + char *column_name = NULL; + char *datatype_name = NULL; + char *constraint_name = NULL; + + if (exc != NULL) + { + PyErr_NormalizeException(&exc, &val, &tb); + + if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) + PLy_get_spi_error_data(val, &sqlerrcode, + &detail, &hint, &query, &position, + &schema_name, &table_name, &column_name, + &datatype_name, &constraint_name); + else if (PyErr_GivenExceptionMatches(val, PLy_exc_error)) + PLy_get_error_data(val, &sqlerrcode, &detail, &hint, &schema_name, &table_name, &column_name, &datatype_name, &constraint_name); - else if (PyErr_GivenExceptionMatches(val, PLy_exc_error)) - PLy_get_error_data(val, &sqlerrcode, &detail, &hint, - &schema_name, &table_name, &column_name, - &datatype_name, &constraint_name); - else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) - elevel = FATAL; - } + else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) + elevel = FATAL; + } - PLy_traceback(exc, val, tb, - &xmsg, &tbmsg, &tb_depth); + PLy_traceback(exc, val, tb, + &xmsg, &tbmsg, &tb_depth); - if (fmt) - { - for (;;) + if (fmt) { - va_list ap; - int needed; - - errno = save_errno; - va_start(ap, fmt); - needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); - va_end(ap); - if (needed == 0) - break; - enlargeStringInfo(&emsg, needed); - } - primary = emsg.data; + for (;;) + { + va_list ap; + int needed; + + errno = save_errno; + va_start(ap, fmt); + needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + if (needed == 0) + break; + enlargeStringInfo(&emsg, needed); + } + primary = emsg.data; - /* If there's an exception message, it goes in the detail. */ - if (xmsg) - detail = xmsg; - } - else - { - if (xmsg) - primary = xmsg; - } + /* If there's an exception message, it goes in the detail. */ + if (xmsg) + detail = xmsg; + } + else + { + if (xmsg) + primary = xmsg; + } ereport(elevel, (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), @@ -195,39 +195,39 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, */ PG_TRY(); { - char *e_type_s = NULL; - char *e_module_s = NULL; - const char *vstr; - StringInfoData xstr; - - e_type_o = PyObject_GetAttrString(e, "__name__"); - e_module_o = PyObject_GetAttrString(e, "__module__"); - if (e_type_o) - e_type_s = PLyUnicode_AsString(e_type_o); - if (e_module_o) - e_module_s = PLyUnicode_AsString(e_module_o); - - if (v && ((vob = PyObject_Str(v)) != NULL)) - vstr = PLyUnicode_AsString(vob); - else - vstr = "unknown"; - - initStringInfo(&xstr); - if (!e_type_s || !e_module_s) - { - /* shouldn't happen */ - appendStringInfoString(&xstr, "unrecognized exception"); - } - /* mimics behavior of traceback.format_exception_only */ - else if (strcmp(e_module_s, "builtins") == 0 - || strcmp(e_module_s, "__main__") == 0 - || strcmp(e_module_s, "exceptions") == 0) - appendStringInfoString(&xstr, e_type_s); - else - appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); - appendStringInfo(&xstr, ": %s", vstr); - - *xmsg = xstr.data; + char *e_type_s = NULL; + char *e_module_s = NULL; + const char *vstr; + StringInfoData xstr; + + e_type_o = PyObject_GetAttrString(e, "__name__"); + e_module_o = PyObject_GetAttrString(e, "__module__"); + if (e_type_o) + e_type_s = PLyUnicode_AsString(e_type_o); + if (e_module_o) + e_module_s = PLyUnicode_AsString(e_module_o); + + if (v && ((vob = PyObject_Str(v)) != NULL)) + vstr = PLyUnicode_AsString(vob); + else + vstr = "unknown"; + + initStringInfo(&xstr); + if (!e_type_s || !e_module_s) + { + /* shouldn't happen */ + appendStringInfoString(&xstr, "unrecognized exception"); + } + /* mimics behavior of traceback.format_exception_only */ + else if (strcmp(e_module_s, "builtins") == 0 + || strcmp(e_module_s, "__main__") == 0 + || strcmp(e_module_s, "exceptions") == 0) + appendStringInfoString(&xstr, e_type_s); + else + appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); + appendStringInfo(&xstr, ": %s", vstr); + + *xmsg = xstr.data; } PG_FINALLY(); { @@ -274,61 +274,61 @@ PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, if (filename == NULL) elog(ERROR, "could not get file name from Python code object"); - /* The first frame always points at , skip it. */ - if (*tb_depth > 0) - { - PLyExecutionContext *exec_ctx = PLy_current_execution_context(); - char *proname; - char *fname; - char *line; - char *plain_filename; - long plain_lineno; - - /* - * The second frame points at the internal function, but to mimic - * Python error reporting we want to say . - */ - if (*tb_depth == 1) - fname = ""; - else - fname = PLyUnicode_AsString(name); - - proname = PLy_procedure_name(exec_ctx->curr_proc); - plain_filename = PLyUnicode_AsString(filename); - plain_lineno = PyLong_AsLong(lineno); - - if (proname == NULL) - appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s", - plain_lineno - 1, fname); - else - appendStringInfo(&tbstr, "\n PL/Python function \"%s\", line %ld, in %s", - proname, plain_lineno - 1, fname); - - /* - * function code object was compiled with "" as the - * filename - */ - if (exec_ctx->curr_proc && plain_filename != NULL && - strcmp(plain_filename, "") == 0) + /* The first frame always points at , skip it. */ + if (*tb_depth > 0) { + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + char *proname; + char *fname; + char *line; + char *plain_filename; + long plain_lineno; + /* - * If we know the current procedure, append the exact line - * from the source, again mimicking Python's traceback.py - * module behavior. We could store the already line-split - * source to avoid splitting it every time, but producing a - * traceback is not the most important scenario to optimize - * for. But we do not go as far as traceback.py in reading - * the source of imported modules. + * The second frame points at the internal function, but to + * mimic Python error reporting we want to say . */ - line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); - if (line) + if (*tb_depth == 1) + fname = ""; + else + fname = PLyUnicode_AsString(name); + + proname = PLy_procedure_name(exec_ctx->curr_proc); + plain_filename = PLyUnicode_AsString(filename); + plain_lineno = PyLong_AsLong(lineno); + + if (proname == NULL) + appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s", + plain_lineno - 1, fname); + else + appendStringInfo(&tbstr, "\n PL/Python function \"%s\", line %ld, in %s", + proname, plain_lineno - 1, fname); + + /* + * function code object was compiled with "" as the + * filename + */ + if (exec_ctx->curr_proc && plain_filename != NULL && + strcmp(plain_filename, "") == 0) { - appendStringInfo(&tbstr, "\n %s", line); - pfree(line); + /* + * If we know the current procedure, append the exact line + * from the source, again mimicking Python's traceback.py + * module behavior. We could store the already line-split + * source to avoid splitting it every time, but producing + * a traceback is not the most important scenario to + * optimize for. But we do not go as far as traceback.py + * in reading the source of imported modules. + */ + line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); + if (line) + { + appendStringInfo(&tbstr, "\n %s", line); + pfree(line); + } } } } - } PG_FINALLY(); { Py_XDECREF(frame); From 3a57c9427f95e8f4a04bbc581eb6589f70f6e260 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 2 Jun 2025 12:04:04 +0900 Subject: [PATCH 664/796] Use replay LSN as target for cascading logical WAL senders A cascading WAL sender doing logical decoding (as known as doing its work on a standby) has been using as flush LSN the value returned by GetStandbyFlushRecPtr() (last position safely flushed to disk). This is incorrect as such processes are only able to decode changes up to the LSN that has been replayed by the startup process. This commit changes cascading logical WAL senders to use the replay LSN, as returned by GetXLogReplayRecPtr(). This distinction is important particularly during shutdown, when WAL senders need to send any remaining available data to their clients, switching WAL senders to a caught-up state. Using the latest flush LSN rather than the replay LSN could cause the WAL senders to be stuck in an infinite loop preventing them to shut down, as the startup process does not run when WAL senders attempt to catch up, so they could keep waiting for work that would never happen. Backpatch down to v16, where logical decoding on standbys has been introduced. Author: Alexey Makhmutov Reviewed-by: Ajin Cherian Reviewed-by: Bertrand Drouvot Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/52138028-7246-421c-9161-4fa108b88070@postgrespro.ru Backpatch-through: 16 --- src/backend/replication/walsender.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index e0257cc49b3ad..7be04cda5f9d0 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -3450,8 +3450,16 @@ XLogSendLogical(void) if (flushPtr == InvalidXLogRecPtr || logical_decoding_ctx->reader->EndRecPtr >= flushPtr) { + /* + * For cascading logical WAL senders, we use the replay LSN instead of + * the flush LSN, since logical decoding on a standby only processes + * WAL that has been replayed. This distinction becomes particularly + * important during shutdown, as new WAL is no longer replayed and the + * last replayed LSN marks the furthest point up to which decoding can + * proceed. + */ if (am_cascading_walsender) - flushPtr = GetStandbyFlushRecPtr(NULL); + flushPtr = GetXLogReplayRecPtr(NULL); else flushPtr = GetFlushRecPtr(NULL); } From 6c229b63425213409ae949e201b995188d92c6f2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 2 Jun 2025 15:22:44 -0400 Subject: [PATCH 665/796] Disallow "=" in names of reloptions and foreign-data options. We store values for these options as array elements with the syntax "name=value", hence a name containing "=" confuses matters when it's time to read the array back in. Since validation of the options is often done (long) after this conversion to array format, that leads to confusing and off-point error messages. We can improve matters by rejecting names containing "=" up-front. (Probably a better design would have involved pairs of array elements, but it's too late now --- and anyway, there's no evident use-case for option names like this. We already reject such names in some other contexts such as GUCs.) Reported-by: Chapman Flack Author: Tom Lane Reviewed-by: Chapman Flack Discussion: https://postgr.es/m/6830EB30.8090904@acm.org Backpatch-through: 13 --- contrib/file_fdw/expected/file_fdw.out | 4 ++++ contrib/file_fdw/sql/file_fdw.sql | 2 ++ src/backend/access/common/reloptions.c | 17 +++++++++++++---- src/backend/commands/foreigncmds.c | 15 +++++++++++++-- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out index 86c148a86ba3a..4a31613fa2910 100644 --- a/contrib/file_fdw/expected/file_fdw.out +++ b/contrib/file_fdw/expected/file_fdw.out @@ -48,6 +48,10 @@ SET ROLE regress_file_fdw_superuser; CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; -- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (foo 'bar'); -- ERROR +ERROR: invalid option "foo" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS ("a=b" 'true'); -- ERROR +ERROR: invalid option name "a=b": must not contain "=" CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR ERROR: COPY format "xml" not recognized CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR diff --git a/contrib/file_fdw/sql/file_fdw.sql b/contrib/file_fdw/sql/file_fdw.sql index f0548e14e1845..e91b9799c4293 100644 --- a/contrib/file_fdw/sql/file_fdw.sql +++ b/contrib/file_fdw/sql/file_fdw.sql @@ -55,6 +55,8 @@ CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; -- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (foo 'bar'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS ("a=b" 'true'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':'); -- ERROR diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index d6eb5d8559939..c6a2d13be8d3f 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1232,8 +1232,9 @@ transformRelOptions(Datum oldOptions, List *defList, const char *namspace, } else { - text *t; + const char *name; const char *value; + text *t; Size len; /* @@ -1280,11 +1281,19 @@ transformRelOptions(Datum oldOptions, List *defList, const char *namspace, * have just "name", assume "name=true" is meant. Note: the * namespace is not output. */ + name = def->defname; if (def->arg != NULL) value = defGetString(def); else value = "true"; + /* Insist that name not contain "=", else "a=b=c" is ambiguous */ + if (strchr(name, '=') != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid option name \"%s\": must not contain \"=\"", + name))); + /* * This is not a great place for this test, but there's no other * convenient place to filter the option out. As WITH (oids = @@ -1292,7 +1301,7 @@ transformRelOptions(Datum oldOptions, List *defList, const char *namspace, * amount of ugly. */ if (acceptOidsOff && def->defnamespace == NULL && - strcmp(def->defname, "oids") == 0) + strcmp(name, "oids") == 0) { if (defGetBoolean(def)) ereport(ERROR, @@ -1302,11 +1311,11 @@ transformRelOptions(Datum oldOptions, List *defList, const char *namspace, continue; } - len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); + len = VARHDRSZ + strlen(name) + 1 + strlen(value); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=%s", def->defname, value); + sprintf(VARDATA(t), "%s=%s", name, value); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index cf61bbac1fa10..17f40599d269f 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -71,15 +71,26 @@ optionListToArray(List *options) foreach(cell, options) { DefElem *def = lfirst(cell); + const char *name; const char *value; Size len; text *t; + name = def->defname; value = defGetString(def); - len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); + + /* Insist that name not contain "=", else "a=b=c" is ambiguous */ + if (strchr(name, '=') != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid option name \"%s\": must not contain \"=\"", + name))); + + len = VARHDRSZ + strlen(name) + 1 + strlen(value); + /* +1 leaves room for sprintf's trailing null */ t = palloc(len + 1); SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=%s", def->defname, value); + sprintf(VARDATA(t), "%s=%s", name, value); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, From a1b9268f6a59de65842b548556fbfe2a5d09fe72 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 4 Jun 2025 09:47:25 -0500 Subject: [PATCH 666/796] doc: Remove notes about "unencrypted" passwords. The documentation for the pg_authid system catalog and the pg_shadow system view indicates that passwords might be stored in cleartext, but that hasn't been possible for some time. Oversight in commit eb61136dc7. Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/aD2yKkZro4nbl5ol%40nathan Backpatch-through: 13 --- doc/src/sgml/catalogs.sgml | 7 +------ doc/src/sgml/system-views.sgml | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index a63cc71efa2f1..9f237a1a6f54e 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1590,7 +1590,7 @@ rolpassword text - Password (possibly encrypted); null if none. The format depends + Encrypted password; null if none. The format depends on the form of encryption used. @@ -1626,11 +1626,6 @@ SCRAM-SHA-256$<iteration count>:&l ServerKey are in Base64 encoded format. This format is the same as that specified by RFC 5803. - - - A password that does not follow either of those formats is assumed to be - unencrypted. - diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index a852bdb99a31f..f36a89cc74aea 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -3578,7 +3578,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx passwd text - Password (possibly encrypted); null if none. See + Encrypted password; null if none. See pg_authid for details of how encrypted passwords are stored. From d041ccfbf366138f817cc1af90d4f8653367eb2f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 5 Jun 2025 11:29:24 -0400 Subject: [PATCH 667/796] Doc: you must own the target object to use SECURITY LABEL. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some reason this wasn't mentioned before. Author: Patrick Stählin Reviewed-by: Tom Lane Discussion: https://postgr.es/m/931e012a-57ba-41ba-9b88-24323a46dec5@packi.ch Backpatch-through: 13 --- doc/src/sgml/ref/security_label.sgml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index e5e5fb483e94e..aa45c0af2487b 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -84,6 +84,10 @@ SECURITY LABEL [ FOR provider ] ON based on object labels, rather than traditional discretionary access control (DAC) concepts such as users and groups. + + + You must own the database object to use SECURITY LABEL. + From af2ea1af4bd59dc2ebbe07f7080bfe2568f48cb7 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Fri, 6 Jun 2025 08:18:26 -0400 Subject: [PATCH 668/796] pg_prewarm: Allow autoprewarm to use more than 1GB to dump blocks. Reported-by: Daria Shanina Author: Daria Shanina Author: Robert Haas Backpatch-through: 13 --- contrib/pg_prewarm/autoprewarm.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c index d061731706ac0..bc540e734b52b 100644 --- a/contrib/pg_prewarm/autoprewarm.c +++ b/contrib/pg_prewarm/autoprewarm.c @@ -597,8 +597,15 @@ apw_dump_now(bool is_bgworker, bool dump_unlogged) return 0; } - block_info_array = - (BlockInfoRecord *) palloc(sizeof(BlockInfoRecord) * NBuffers); + /* + * With sufficiently large shared_buffers, allocation will exceed 1GB, so + * allow for a huge allocation to prevent outright failure. + * + * (In the future, it might be a good idea to redesign this to use a more + * memory-efficient data structure.) + */ + block_info_array = (BlockInfoRecord *) + palloc_extended((sizeof(BlockInfoRecord) * NBuffers), MCXT_ALLOC_HUGE); for (num_blocks = 0, i = 0; i < NBuffers; i++) { From df3294f3c06ae851d167c9e49baa90147a36833a Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 6 Jun 2025 11:40:52 -0500 Subject: [PATCH 669/796] Fixed signed/unsigned mismatch in test_dsm_registry. Oversight in commit 8b2bcf3f28. Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/aECi_gSD9JnVWQ8T%40nathan Backpatch-through: 17 --- src/test/modules/test_dsm_registry/test_dsm_registry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c index 96eaa850bfa92..9aa96dc45d1bb 100644 --- a/src/test/modules/test_dsm_registry/test_dsm_registry.c +++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c @@ -54,7 +54,7 @@ set_val_in_shmem(PG_FUNCTION_ARGS) tdr_attach_shmem(); LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE); - tdr_state->val = PG_GETARG_UINT32(0); + tdr_state->val = PG_GETARG_INT32(0); LWLockRelease(&tdr_state->lck); PG_RETURN_VOID(); @@ -72,5 +72,5 @@ get_val_in_shmem(PG_FUNCTION_ARGS) ret = tdr_state->val; LWLockRelease(&tdr_state->lck); - PG_RETURN_UINT32(ret); + PG_RETURN_INT32(ret); } From 351db19cec5a91a7b7d56d425af0b09abb19960c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 8 Jun 2025 17:06:39 -0400 Subject: [PATCH 670/796] pg_restore: fix incompatibility with old directory-format dumps. pg_restore failed to restore large objects (blobs) out of directory-format dumps made by versions before PG v12. That's because, due to a bug fixed in commit 548e50976, those old versions put the wrong filename into the BLOBS TOC entry. Said bug was harmless before v17, because we ignored the incorrect filename field --- but commit a45c78e32 assumed it would be correct. Reported-by: Pavel Stehule Author: Pavel Stehule Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAFj8pRCrZ=_e1Rv1N+6vDaH+6gf=9A2mE2J4RvnvKA1bLiXvXA@mail.gmail.com Backpatch-through: 17 --- src/bin/pg_dump/pg_backup_directory.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c index 7be8d5487d4b2..6b93b0dc22867 100644 --- a/src/bin/pg_dump/pg_backup_directory.c +++ b/src/bin/pg_dump/pg_backup_directory.c @@ -444,10 +444,15 @@ _LoadLOs(ArchiveHandle *AH, TocEntry *te) /* * Note: before archive v16, there was always only one BLOBS TOC entry, - * now there can be multiple. We don't need to worry what version we are - * reading though, because tctx->filename should be correct either way. + * now there can be multiple. Furthermore, although the actual filename + * was always "blobs.toc" before v16, the value of tctx->filename did not + * match that before commit 548e50976 fixed it. For simplicity we assume + * it must be "blobs.toc" in all archives before v16. */ - setFilePath(AH, tocfname, tctx->filename); + if (AH->version < K_VERS_1_16) + setFilePath(AH, tocfname, "blobs.toc"); + else + setFilePath(AH, tocfname, tctx->filename); CFH = ctx->LOsTocFH = InitDiscoverCompressFileHandle(tocfname, PG_BINARY_R); From 232539382bd4548f674ce8ce5d3b4d8c78a5518a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 10 Jun 2025 18:39:34 -0400 Subject: [PATCH 671/796] Don't reduce output request size on non-Unix-socket connections. Traditionally, libpq's pqPutMsgEnd has rounded down the amount-to-send to be a multiple of 8K when it is eagerly writing some data. This still seems like a good idea when sending through a Unix socket, as pipes typically have a buffer size of 8K or some fraction/multiple of that. But there's not much argument for it on a TCP connection, since (a) standard MTU values are not commensurate with that, and (b) the kernel typically applies its own packet splitting/merging logic. Worse, our SSL and GSSAPI code paths both have API stipulations that if they fail to send all the data that was offered in the previous write attempt, we mustn't offer less data in the next attempt; else we may get "SSL error: bad length" or "GSSAPI caller failed to retransmit all data needing to be retried". The previous write attempt might've been pqFlush attempting to send everything in the buffer, so pqPutMsgEnd can't safely write less than the full buffer contents. (Well, we could add some more state to track exactly how much the previous write attempt was, but there's little value evident in such extra complication.) Hence, apply the round-down only on AF_UNIX sockets, where we never use SSL or GSSAPI. Interestingly, we had a very closely related bug report before, which I attempted to fix in commit d053a879b. But the test case we had then seemingly didn't trigger this pqFlush-then-pqPutMsgEnd scenario, or at least we failed to recognize this variant of the bug. Bug: #18907 Reported-by: Dorjpalam Batbaatar Author: Tom Lane Discussion: https://postgr.es/m/18907-d41b9bcf6f29edda@postgresql.org Backpatch-through: 13 --- src/backend/libpq/be-secure-gssapi.c | 6 +++--- src/interfaces/libpq/fe-misc.c | 28 ++++++++++++++++++++++++- src/interfaces/libpq/fe-secure-gssapi.c | 6 +++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c index e95786c681817..dfc1b756d253f 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -120,9 +120,9 @@ be_gssapi_write(Port *port, void *ptr, size_t len) * again, so if it offers a len less than that, something is wrong. * * Note: it may seem attractive to report partial write completion once - * we've successfully sent any encrypted packets. However, that can cause - * problems for callers; notably, pqPutMsgEnd's heuristic to send only - * full 8K blocks interacts badly with such a hack. We won't save much, + * we've successfully sent any encrypted packets. However, doing that + * expands the state space of this processing and has been responsible for + * bugs in the past (cf. commit d053a879b). We won't save much, * typically, by letting callers discard data early, so don't risk it. */ if (len < PqGSSSendConsumed) diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c index c76a9878cc5fc..3ed89450ff503 100644 --- a/src/interfaces/libpq/fe-misc.c +++ b/src/interfaces/libpq/fe-misc.c @@ -538,9 +538,35 @@ pqPutMsgEnd(PGconn *conn) /* Make message eligible to send */ conn->outCount = conn->outMsgEnd; + /* If appropriate, try to push out some data */ if (conn->outCount >= 8192) { - int toSend = conn->outCount - (conn->outCount % 8192); + int toSend = conn->outCount; + + /* + * On Unix-pipe connections, it seems profitable to prefer sending + * pipe-buffer-sized packets not randomly-sized ones, so retain the + * last partial-8K chunk in our buffer for now. On TCP connections, + * the advantage of that is far less clear. Moreover, it flat out + * isn't safe when using SSL or GSSAPI, because those code paths have + * API stipulations that if they fail to send all the data that was + * offered in the previous write attempt, we mustn't offer less data + * in this write attempt. The previous write attempt might've been + * pqFlush attempting to send everything in the buffer, so we mustn't + * offer less now. (Presently, we won't try to use SSL or GSSAPI on + * Unix connections, so those checks are just Asserts. They'll have + * to become part of the regular if-test if we ever change that.) + */ + if (conn->raddr.addr.ss_family == AF_UNIX) + { +#ifdef USE_SSL + Assert(!conn->ssl_in_use); +#endif +#ifdef ENABLE_GSS + Assert(!conn->gssenc); +#endif + toSend -= toSend % 8192; + } if (pqSendSome(conn, toSend) < 0) return EOF; diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c index 6dfca69a062ad..d093be9ba5aea 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -112,9 +112,9 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len) * again, so if it offers a len less than that, something is wrong. * * Note: it may seem attractive to report partial write completion once - * we've successfully sent any encrypted packets. However, that can cause - * problems for callers; notably, pqPutMsgEnd's heuristic to send only - * full 8K blocks interacts badly with such a hack. We won't save much, + * we've successfully sent any encrypted packets. However, doing that + * expands the state space of this processing and has been responsible for + * bugs in the past (cf. commit d053a879b). We won't save much, * typically, by letting callers discard data early, so don't risk it. */ if (len < PqGSSSendConsumed) From afbcb0fa29f59e6d7e5936491ef5a4466eacd4c9 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Wed, 11 Jun 2025 09:17:33 -0400 Subject: [PATCH 672/796] Make _bt_killitems drop pins it acquired itself. Teach nbtree's _bt_killitems to leave the so->currPos page that it sets LP_DEAD items on in whatever state it was in when _bt_killitems was called. In particular, make sure that so->dropPin scans don't acquire a pin whose reference is saved in so->currPos.buf. Allowing _bt_killitems to change so->currPos.buf like this is wrong. The immediate consequence of allowing it is that code in _bt_steppage (that copies so->currPos into so->markPos) will behave as if the scan is a !so->dropPin scan. so->markPos will therefore retain the buffer pin indefinitely, even though _bt_killitems only needs to acquire a pin (along with a lock) for long enough to mark known-dead items LP_DEAD. This issue came to light following a report of a failure of an assertion from recent commit e6eed40e. The test case in question involves the use of mark and restore. An initial call to _bt_killitems takes place that leaves so->currPos.buf in a state that is inconsistent with the scan being so->dropPin. A subsequent call to _bt_killitems for the same position (following so->currPos being saved in so->markPos, and then restored as so->currPos) resulted in the failure of an assertion that tests that so->currPos.buf is InvalidBuffer when the scan is so->dropPin (non-assert builds got a "resource was not closed" WARNING instead). The same problem exists on earlier releases, though the issue is far more subtle there. Recent commit e6eed40e introduced the so->dropPin field as a partial replacement for testing so->currPos.buf directly. Earlier releases won't get an assertion failure (or buffer pin leak), but they will allow the second _bt_killitems call from the test case to behave as if a buffer pin was consistently held since the original call to _bt_readpage. This is wrong; there will have been an initial window during which no pin was held on the so->currPos page, and yet the second _bt_killitems call will neglect to check if so->currPos.lsn continues to match the page's now-current LSN. As a result of all this, it's just about possible that _bt_killitems will set the wrong items LP_DEAD (on release branches). This could only happen with merge joins (the sole user of nbtree mark/restore support), when a concurrently inserted index tuple used a recently-recycled TID (and only when the new tuple was inserted onto the same page as a distinct concurrently-removed tuple with the same TID). This is exactly the scenario that _bt_killitems' check of the page's now-current LSN against the LSN stashed in currPos was supposed to prevent. A follow-up commit will make nbtree completely stop conditioning whether or not a position's pin needs to be dropped on whether the 'buf' field is set. All call sites that might need to drop a still-held pin will be taught to rely on the scan-level so->dropPin field recently introduced by commit e6eed40e. That will make bugs of the same general nature as this one impossible (or make them much easier to detect, at least). Author: Peter Geoghegan Reported-By: Alexander Lakhin Discussion: https://postgr.es/m/545be1e5-3786-439a-9257-a90d30f8b849@gmail.com Backpatch-through: 13 --- src/backend/access/nbtree/nbtutils.c | 34 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 878f4b2e7b843..a448a5470822d 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -4144,9 +4144,9 @@ _bt_checkkeys_look_ahead(IndexScanDesc scan, BTReadPageState *pstate, * current page and killed tuples thereon (generally, this should only be * called if so->numKilled > 0). * - * The caller does not have a lock on the page and may or may not have the - * page pinned in a buffer. Note that read-lock is sufficient for setting - * LP_DEAD status (which is only a hint). + * Caller should not have a lock on the so->currPos page, but may hold a + * buffer pin. When we return, it still won't be locked. It'll continue to + * hold whatever pins were held before calling here. * * We match items by heap TID before assuming they are the right ones to * delete. We cope with cases where items have moved right due to insertions. @@ -4178,7 +4178,8 @@ _bt_killitems(IndexScanDesc scan) int i; int numKilled = so->numKilled; bool killedsomething = false; - bool droppedpin PG_USED_FOR_ASSERTS_ONLY; + bool droppedpin; + Buffer buf; Assert(BTScanPosIsValid(so->currPos)); @@ -4197,29 +4198,31 @@ _bt_killitems(IndexScanDesc scan) * LSN. */ droppedpin = false; - _bt_lockbuf(scan->indexRelation, so->currPos.buf, BT_READ); - - page = BufferGetPage(so->currPos.buf); + buf = so->currPos.buf; + _bt_lockbuf(scan->indexRelation, buf, BT_READ); } else { - Buffer buf; + XLogRecPtr latestlsn; droppedpin = true; /* Attempt to re-read the buffer, getting pin and lock. */ buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ); - page = BufferGetPage(buf); - if (BufferGetLSNAtomic(buf) == so->currPos.lsn) - so->currPos.buf = buf; - else + latestlsn = BufferGetLSNAtomic(buf); + Assert(!XLogRecPtrIsInvalid(so->currPos.lsn)); + Assert(so->currPos.lsn <= latestlsn); + if (so->currPos.lsn != latestlsn) { /* Modified while not pinned means hinting is not safe. */ _bt_relbuf(scan->indexRelation, buf); return; } + + /* Unmodified, hinting is safe */ } + page = BufferGetPage(buf); opaque = BTPageGetOpaque(page); minoff = P_FIRSTDATAKEY(opaque); maxoff = PageGetMaxOffsetNumber(page); @@ -4336,10 +4339,13 @@ _bt_killitems(IndexScanDesc scan) if (killedsomething) { opaque->btpo_flags |= BTP_HAS_GARBAGE; - MarkBufferDirtyHint(so->currPos.buf, true); + MarkBufferDirtyHint(buf, true); } - _bt_unlockbuf(scan->indexRelation, so->currPos.buf); + if (!droppedpin) + _bt_unlockbuf(scan->indexRelation, buf); + else + _bt_relbuf(scan->indexRelation, buf); } From 68a325d40e1f6ba7a303e17ac289f88e7926f3fd Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sat, 14 Jun 2025 03:33:15 +0300 Subject: [PATCH 673/796] Keep WAL segments by the flushed value of the slot's restart LSN The patch fixes the issue with the unexpected removal of old WAL segments after checkpoint, followed by an immediate restart. The issue occurs when a slot is advanced after the start of the checkpoint and before old WAL segments are removed at the end of the checkpoint. The idea of the patch is to get the minimal restart_lsn at the beginning of checkpoint (or restart point) creation and use this value when calculating the oldest LSN for WAL segments removal at the end of checkpoint. This idea was proposed by Tomas Vondra in the discussion. Unlike 291221c46575, this fix doesn't affect ABI and is intended for back branches. Discussion: https://postgr.es/m/flat/1d12d2-67235980-35-19a406a0%4063439497 Author: Vitaly Davydov Reviewed-by: Tomas Vondra Reviewed-by: Alexander Korotkov Reviewed-by: Amit Kapila Backpatch-through: 13 --- src/backend/access/transam/xlog.c | 55 +++++++++++++++++++---- src/backend/replication/logical/logical.c | 10 ++++- src/backend/replication/walsender.c | 4 ++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index e0e8d2daf0071..57746a188974c 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -670,7 +670,8 @@ static XLogRecPtr CreateOverwriteContrecordRecord(XLogRecPtr aborted_lsn, XLogRecPtr pagePtr, TimeLineID newTLI); static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags); -static void KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo); +static void KeepLogSeg(XLogRecPtr recptr, XLogRecPtr slotsMinLSN, + XLogSegNo *logSegNo); static XLogRecPtr XLogGetReplicationSlotMinimumLSN(void); static void AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, @@ -6897,6 +6898,7 @@ CreateCheckPoint(int flags) VirtualTransactionId *vxids; int nvxids; int oldXLogAllowed = 0; + XLogRecPtr slotsMinReqLSN; /* * An end-of-recovery checkpoint is really a shutdown checkpoint, just @@ -7128,6 +7130,15 @@ CreateCheckPoint(int flags) if (checkpoint_create_hook != NULL) checkpoint_create_hook(&checkPoint); + /* + * Get the current minimum LSN to be used later in the WAL segment + * cleanup. We may clean up only WAL segments, which are not needed + * according to synchronized LSNs of replication slots. The slot's LSN + * might be advanced concurrently, so we call this before + * CheckPointReplicationSlots() synchronizes replication slots. + */ + slotsMinReqLSN = XLogGetReplicationSlotMinimumLSN(); + /* * In some cases there are groups of actions that must all occur on one * side or the other of a checkpoint record. Before flushing the @@ -7316,17 +7327,25 @@ CreateCheckPoint(int flags) * prevent the disk holding the xlog from growing full. */ XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size); - KeepLogSeg(recptr, &_logSegNo); + KeepLogSeg(recptr, slotsMinReqLSN, &_logSegNo); if (InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_REMOVED, _logSegNo, InvalidOid, InvalidTransactionId)) { + /* + * Recalculate the current minimum LSN to be used in the WAL segment + * cleanup. Then, we must synchronize the replication slots again in + * order to make this LSN safe to use. + */ + slotsMinReqLSN = XLogGetReplicationSlotMinimumLSN(); + CheckPointReplicationSlots(shutdown); + /* * Some slots have been invalidated; recalculate the old-segment * horizon, starting again from RedoRecPtr. */ XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size); - KeepLogSeg(recptr, &_logSegNo); + KeepLogSeg(recptr, slotsMinReqLSN, &_logSegNo); } _logSegNo--; RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr, @@ -7599,6 +7618,7 @@ CreateRestartPoint(int flags) XLogRecPtr endptr; XLogSegNo _logSegNo; TimestampTz xtime; + XLogRecPtr slotsMinReqLSN; /* Concurrent checkpoint/restartpoint cannot happen */ Assert(!IsUnderPostmaster || MyBackendType == B_CHECKPOINTER); @@ -7681,6 +7701,15 @@ CreateRestartPoint(int flags) MemSet(&CheckpointStats, 0, sizeof(CheckpointStats)); CheckpointStats.ckpt_start_t = GetCurrentTimestamp(); + /* + * Get the current minimum LSN to be used later in the WAL segment + * cleanup. We may clean up only WAL segments, which are not needed + * according to synchronized LSNs of replication slots. The slot's LSN + * might be advanced concurrently, so we call this before + * CheckPointReplicationSlots() synchronizes replication slots. + */ + slotsMinReqLSN = XLogGetReplicationSlotMinimumLSN(); + if (log_checkpoints) LogCheckpointStart(flags, true); @@ -7769,17 +7798,25 @@ CreateRestartPoint(int flags) receivePtr = GetWalRcvFlushRecPtr(NULL, NULL); replayPtr = GetXLogReplayRecPtr(&replayTLI); endptr = (receivePtr < replayPtr) ? replayPtr : receivePtr; - KeepLogSeg(endptr, &_logSegNo); + KeepLogSeg(endptr, slotsMinReqLSN, &_logSegNo); if (InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_REMOVED, _logSegNo, InvalidOid, InvalidTransactionId)) { + /* + * Recalculate the current minimum LSN to be used in the WAL segment + * cleanup. Then, we must synchronize the replication slots again in + * order to make this LSN safe to use. + */ + slotsMinReqLSN = XLogGetReplicationSlotMinimumLSN(); + CheckPointReplicationSlots(flags & CHECKPOINT_IS_SHUTDOWN); + /* * Some slots have been invalidated; recalculate the old-segment * horizon, starting again from RedoRecPtr. */ XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size); - KeepLogSeg(endptr, &_logSegNo); + KeepLogSeg(endptr, slotsMinReqLSN, &_logSegNo); } _logSegNo--; @@ -7874,6 +7911,7 @@ GetWALAvailability(XLogRecPtr targetLSN) XLogSegNo oldestSegMaxWalSize; /* oldest segid kept by max_wal_size */ XLogSegNo oldestSlotSeg; /* oldest segid kept by slot */ uint64 keepSegs; + XLogRecPtr slotsMinReqLSN; /* * slot does not reserve WAL. Either deactivated, or has never been active @@ -7887,8 +7925,9 @@ GetWALAvailability(XLogRecPtr targetLSN) * oldestSlotSeg to the current segment. */ currpos = GetXLogWriteRecPtr(); + slotsMinReqLSN = XLogGetReplicationSlotMinimumLSN(); XLByteToSeg(currpos, oldestSlotSeg, wal_segment_size); - KeepLogSeg(currpos, &oldestSlotSeg); + KeepLogSeg(currpos, slotsMinReqLSN, &oldestSlotSeg); /* * Find the oldest extant segment file. We get 1 until checkpoint removes @@ -7949,7 +7988,7 @@ GetWALAvailability(XLogRecPtr targetLSN) * invalidation is optionally done here, instead. */ static void -KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo) +KeepLogSeg(XLogRecPtr recptr, XLogRecPtr slotsMinReqLSN, XLogSegNo *logSegNo) { XLogSegNo currSegNo; XLogSegNo segno; @@ -7962,7 +8001,7 @@ KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo) * Calculate how many segments are kept by slots first, adjusting for * max_slot_wal_keep_size. */ - keep = XLogGetReplicationSlotMinimumLSN(); + keep = slotsMinReqLSN; if (keep != InvalidXLogRecPtr && keep < recptr) { XLByteToSeg(keep, segno, wal_segment_size); diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 97b6aa899ee10..4407df84a1c9c 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -1897,7 +1897,15 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) SpinLockRelease(&MyReplicationSlot->mutex); - /* first write new xmin to disk, so we know what's up after a crash */ + /* + * First, write new xmin and restart_lsn to disk so we know what's up + * after a crash. Even when we do this, the checkpointer can see the + * updated restart_lsn value in the shared memory; then, a crash can + * happen before we manage to write that value to the disk. Thus, + * checkpointer still needs to make special efforts to keep WAL + * segments required by the restart_lsn written to the disk. See + * CreateCheckPoint() and CreateRestartPoint() for details. + */ if (updated_xmin || updated_restart) { ReplicationSlotMarkDirty(); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 7be04cda5f9d0..4019eb9029251 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -2392,6 +2392,10 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn) * be energy wasted - the worst thing lost information could cause here is * to give wrong information in a statistics view - we'll just potentially * be more conservative in removing files. + * + * Checkpointer makes special efforts to keep the WAL segments required by + * the restart_lsn written to the disk. See CreateCheckPoint() and + * CreateRestartPoint() for details. */ } From a65dc9aae4204b046cac215bccbeeeb922879ffa Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sat, 14 Jun 2025 03:35:27 +0300 Subject: [PATCH 674/796] Add TAP tests to check replication slot advance during the checkpoint The new tests verify that logical and physical replication slots are still valid after an immediate restart on checkpoint completion when the slot was advanced during the checkpoint. This commit introduces two new injection points to make these tests possible: * checkpoint-before-old-wal-removal - triggered in the checkpointer process just before old WAL segments cleanup; * logical-replication-slot-advance-segment - triggered in LogicalConfirmReceivedLocation() when restart_lsn was changed enough to point to the next WAL segment. Discussion: https://postgr.es/m/flat/1d12d2-67235980-35-19a406a0%4063439497 Author: Vitaly Davydov Author: Tomas Vondra Reviewed-by: Alexander Korotkov Reviewed-by: Amit Kapila Backpatch-through: 17 --- src/backend/access/transam/xlog.c | 4 + src/backend/replication/logical/logical.c | 18 +++ src/test/recovery/meson.build | 2 + .../recovery/t/046_checkpoint_logical_slot.pl | 139 ++++++++++++++++++ .../t/047_checkpoint_physical_slot.pl | 133 +++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 src/test/recovery/t/046_checkpoint_logical_slot.pl create mode 100644 src/test/recovery/t/047_checkpoint_physical_slot.pl diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 57746a188974c..281d6a7f5f4af 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7322,6 +7322,10 @@ CreateCheckPoint(int flags) if (PriorRedoPtr != InvalidXLogRecPtr) UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr); +#ifdef USE_INJECTION_POINTS + INJECTION_POINT("checkpoint-before-old-wal-removal"); +#endif + /* * Delete old log files, those no longer needed for last checkpoint to * prevent the disk holding the xlog from growing full. diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 4407df84a1c9c..206fb932484ca 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -29,6 +29,7 @@ #include "postgres.h" #include "access/xact.h" +#include "access/xlog_internal.h" #include "access/xlogutils.h" #include "fmgr.h" #include "miscadmin.h" @@ -41,6 +42,7 @@ #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/memutils.h" @@ -1844,9 +1846,13 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) { bool updated_xmin = false; bool updated_restart = false; + XLogRecPtr restart_lsn pg_attribute_unused(); SpinLockAcquire(&MyReplicationSlot->mutex); + /* remember the old restart lsn */ + restart_lsn = MyReplicationSlot->data.restart_lsn; + /* * Prevent moving the confirmed_flush backwards, as this could lead to * data duplication issues caused by replicating already replicated @@ -1908,6 +1914,18 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) */ if (updated_xmin || updated_restart) { +#ifdef USE_INJECTION_POINTS + XLogSegNo seg1, + seg2; + + XLByteToSeg(restart_lsn, seg1, wal_segment_size); + XLByteToSeg(MyReplicationSlot->data.restart_lsn, seg2, wal_segment_size); + + /* trigger injection point, but only if segment changes */ + if (seg1 != seg2) + INJECTION_POINT("logical-replication-slot-advance-segment"); +#endif + ReplicationSlotMarkDirty(); ReplicationSlotSave(); elog(DEBUG1, "updated xmin: %u restart: %u", updated_xmin, updated_restart); diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 25dd6e5014cf3..77e8deb142fd1 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -53,6 +53,8 @@ tests += { 't/042_low_level_backup.pl', 't/043_no_contrecord_switch.pl', 't/045_archive_restartpoint.pl', + 't/046_checkpoint_logical_slot.pl', + 't/047_checkpoint_physical_slot.pl' ], }, } diff --git a/src/test/recovery/t/046_checkpoint_logical_slot.pl b/src/test/recovery/t/046_checkpoint_logical_slot.pl new file mode 100644 index 0000000000000..b4265c4a6a53f --- /dev/null +++ b/src/test/recovery/t/046_checkpoint_logical_slot.pl @@ -0,0 +1,139 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group +# +# This test verifies the case when the logical slot is advanced during +# checkpoint. The test checks that the logical slot's restart_lsn still refers +# to an existed WAL segment after immediate restart. +# +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +my ($node, $result); + +$node = PostgreSQL::Test::Cluster->new('mike'); +$node->init; +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'injection_points'"); +$node->append_conf('postgresql.conf', "wal_level = 'logical'"); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); + +# Create a simple table to generate data into. +$node->safe_psql('postgres', + q{create table t (id serial primary key, b text)}); + +# Create the two slots we'll need. +$node->safe_psql('postgres', + q{select pg_create_logical_replication_slot('slot_logical', 'test_decoding')} +); +$node->safe_psql('postgres', + q{select pg_create_physical_replication_slot('slot_physical', true)}); + +# Advance both slots to the current position just to have everything "valid". +$node->safe_psql('postgres', + q{select count(*) from pg_logical_slot_get_changes('slot_logical', null, null)} +); +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} +); + +# Run checkpoint to flush current state to disk and set a baseline. +$node->safe_psql('postgres', q{checkpoint}); + +# Generate some transactions to get RUNNING_XACTS. +my $xacts = $node->background_psql('postgres'); +$xacts->query_until( + qr/run_xacts/, + q(\echo run_xacts +SELECT 1 \watch 0.1 +\q +)); + +# Insert 2M rows; that's about 260MB (~20 segments) worth of WAL. +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} +); + +# Run another checkpoint to set a new restore LSN. +$node->safe_psql('postgres', q{checkpoint}); + +# Another 2M rows; that's about 260MB (~20 segments) worth of WAL. +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} +); + +# Run another checkpoint, this time in the background, and make it wait +# on the injection point) so that the checkpoint stops right before +# removing old WAL segments. +note('starting checkpoint\n'); + +my $checkpoint = $node->background_psql('postgres'); +$checkpoint->query_safe( + q(select injection_points_attach('checkpoint-before-old-wal-removal','wait')) +); +$checkpoint->query_until( + qr/starting_checkpoint/, + q(\echo starting_checkpoint +checkpoint; +\q +)); + +# Wait until the checkpoint stops right before removing WAL segments. +note('waiting for injection_point\n'); +$node->wait_for_event('checkpointer', 'checkpoint-before-old-wal-removal'); +note('injection_point is reached'); + +# Try to advance the logical slot, but make it stop when it moves to the next +# WAL segment (this has to happen in the background, too). +my $logical = $node->background_psql('postgres'); +$logical->query_safe( + q{select injection_points_attach('logical-replication-slot-advance-segment','wait');} +); +$logical->query_until( + qr/get_changes/, + q( +\echo get_changes +select count(*) from pg_logical_slot_get_changes('slot_logical', null, null) \watch 1 +\q +)); + +# Wait until the slot's restart_lsn points to the next WAL segment. +note('waiting for injection_point\n'); +$node->wait_for_event('client backend', + 'logical-replication-slot-advance-segment'); +note('injection_point is reached'); + +# OK, we're in the right situation: time to advance the physical slot, which +# recalculates the required LSN, and then unblock the checkpoint, which +# removes the WAL still needed by the logical slot. +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} +); + +# Continue the checkpoint. +$node->safe_psql('postgres', + q{select injection_points_wakeup('checkpoint-before-old-wal-removal')}); + +# Abruptly stop the server (1 second should be enough for the checkpoint +# to finish; it would be better). +$node->stop('immediate'); + +$node->start; + +eval { + $node->safe_psql('postgres', + q{select count(*) from pg_logical_slot_get_changes('slot_logical', null, null);} + ); +}; +is($@, '', "Logical slot still valid"); + +done_testing(); diff --git a/src/test/recovery/t/047_checkpoint_physical_slot.pl b/src/test/recovery/t/047_checkpoint_physical_slot.pl new file mode 100644 index 0000000000000..454e56b9bd2da --- /dev/null +++ b/src/test/recovery/t/047_checkpoint_physical_slot.pl @@ -0,0 +1,133 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group +# +# This test verifies the case when the physical slot is advanced during +# checkpoint. The test checks that the physical slot's restart_lsn still refers +# to an existed WAL segment after immediate restart. +# +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +my ($node, $result); + +$node = PostgreSQL::Test::Cluster->new('mike'); +$node->init; +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'injection_points'"); +$node->append_conf('postgresql.conf', "wal_level = 'replica'"); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); + +# Create a simple table to generate data into. +$node->safe_psql('postgres', + q{create table t (id serial primary key, b text)}); + +# Create a physical replication slot. +$node->safe_psql('postgres', + q{select pg_create_physical_replication_slot('slot_physical', true)}); + +# Advance slot to the current position, just to have everything "valid". +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} +); + +# Run checkpoint to flush current state to disk and set a baseline. +$node->safe_psql('postgres', q{checkpoint}); + +# Insert 2M rows; that's about 260MB (~20 segments) worth of WAL. +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,100000) s(i)} +); + +# Advance slot to the current position, just to have everything "valid". +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} +); + +# Run another checkpoint to set a new restore LSN. +$node->safe_psql('postgres', q{checkpoint}); + +# Another 2M rows; that's about 260MB (~20 segments) worth of WAL. +$node->safe_psql('postgres', + q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} +); + +my $restart_lsn_init = $node->safe_psql('postgres', + q{select restart_lsn from pg_replication_slots where slot_name = 'slot_physical'} +); +chomp($restart_lsn_init); +note("restart lsn before checkpoint: $restart_lsn_init"); + +# Run another checkpoint, this time in the background, and make it wait +# on the injection point) so that the checkpoint stops right before +# removing old WAL segments. +note('starting checkpoint'); + +my $checkpoint = $node->background_psql('postgres'); +$checkpoint->query_safe( + q{select injection_points_attach('checkpoint-before-old-wal-removal','wait')} +); +$checkpoint->query_until( + qr/starting_checkpoint/, + q(\echo starting_checkpoint +checkpoint; +\q +)); + +# Wait until the checkpoint stops right before removing WAL segments. +note('waiting for injection_point'); +$node->wait_for_event('checkpointer', 'checkpoint-before-old-wal-removal'); +note('injection_point is reached'); + +# OK, we're in the right situation: time to advance the physical slot, which +# recalculates the required LSN and then unblock the checkpoint, which +# removes the WAL still needed by the physical slot. +$node->safe_psql('postgres', + q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} +); + +# Continue the checkpoint. +$node->safe_psql('postgres', + q{select injection_points_wakeup('checkpoint-before-old-wal-removal')}); + +my $restart_lsn_old = $node->safe_psql('postgres', + q{select restart_lsn from pg_replication_slots where slot_name = 'slot_physical'} +); +chomp($restart_lsn_old); +note("restart lsn before stop: $restart_lsn_old"); + +# Abruptly stop the server (1 second should be enough for the checkpoint +# to finish; it would be better). +$node->stop('immediate'); + +$node->start; + +# Get the restart_lsn of the slot right after restarting. +my $restart_lsn = $node->safe_psql('postgres', + q{select restart_lsn from pg_replication_slots where slot_name = 'slot_physical'} +); +chomp($restart_lsn); +note("restart lsn: $restart_lsn"); + +# Get the WAL segment name for the slot's restart_lsn. +my $restart_lsn_segment = $node->safe_psql('postgres', + "SELECT pg_walfile_name('$restart_lsn'::pg_lsn)"); +chomp($restart_lsn_segment); + +# Check if the required wal segment exists. +note("required by slot segment name: $restart_lsn_segment"); +my $datadir = $node->data_dir; +ok( -f "$datadir/pg_wal/$restart_lsn_segment", + "WAL segment $restart_lsn_segment for physical slot's restart_lsn $restart_lsn exists" +); + +done_testing(); From c839d83678ebf62a856194cc4e23cb9526b61b3b Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Mon, 16 Jun 2025 17:35:58 -0700 Subject: [PATCH 675/796] Fix re-distributing previously distributed invalidation messages during logical decoding. Commit 4909b38af0 introduced logic to distribute invalidation messages from catalog-modifying transactions to all concurrent in-progress transactions. However, since each transaction distributes not only its original invalidation messages but also previously distributed messages to other transactions, this leads to an exponential increase in allocation request size for invalidation messages, ultimately causing memory allocation failure. This commit fixes this issue by tracking distributed invalidation messages separately per decoded transaction and not redistributing these messages to other in-progress transactions. The maximum size of distributed invalidation messages that one transaction can store is limited to MAX_DISTR_INVAL_MSG_PER_TXN (8MB). Once the size of the distributed invalidation messages exceeds this threshold, we invalidate all caches in locations where distributed invalidation messages need to be executed. Back-patch to all supported versions where we introduced the fix by commit 4909b38af0. Note that this commit adds two new fields to ReorderBufferTXN to store the distributed transactions. This change breaks ABI compatibility in back branches, affecting third-party extensions that depend on the size of the ReorderBufferTXN struct, though this scenario seems unlikely. Additionally, it adds a new flag to the txn_flags field of ReorderBufferTXN to indicate distributed invalidation message overflow. This should not affect existing implementations, as it is unlikely that third-party extensions use unused bits in the txn_flags field. Bug: #18938 #18942 Author: vignesh C Reported-by: Duncan Sands Reported-by: John Hutchins Reported-by: Laurence Parry Reported-by: Max Madden Reported-by: Braulio Fdo Gonzalez Reviewed-by: Masahiko Sawada Reviewed-by: Amit Kapila Reviewed-by: Hayato Kuroda Discussion: https://postgr.es/m/680bdaf6-f7d1-4536-b580-05c2760c67c6@deepbluecap.com Discussion: https://postgr.es/m/18942-0ab1e5ae156613ad@postgresql.org Discussion: https://postgr.es/m/18938-57c9a1c463b68ce0@postgresql.org Discussion: https://postgr.es/m/CAD1FGCT2sYrP_70RTuo56QTizyc+J3wJdtn2gtO3VttQFpdMZg@mail.gmail.com Discussion: https://postgr.es/m/CANO2=B=2BT1hSYCE=nuuTnVTnjidMg0+-FfnRnqM6kd23qoygg@mail.gmail.com Backpatch-through: 13 --- .../expected/invalidation_distribution.out | 23 +- .../specs/invalidation_distribution.spec | 11 + .../replication/logical/reorderbuffer.c | 196 +++++++++++++++--- src/backend/replication/logical/snapbuild.c | 12 +- src/include/replication/reorderbuffer.h | 34 ++- 5 files changed, 231 insertions(+), 45 deletions(-) diff --git a/contrib/test_decoding/expected/invalidation_distribution.out b/contrib/test_decoding/expected/invalidation_distribution.out index ad0a944cbf303..ae53b1e61de3e 100644 --- a/contrib/test_decoding/expected/invalidation_distribution.out +++ b/contrib/test_decoding/expected/invalidation_distribution.out @@ -1,4 +1,4 @@ -Parsed test spec with 2 sessions +Parsed test spec with 3 sessions starting permutation: s1_insert_tbl1 s1_begin s1_insert_tbl1 s2_alter_pub_add_tbl s1_commit s1_insert_tbl1 s2_get_binary_changes step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); @@ -18,3 +18,24 @@ count stop (1 row) + +starting permutation: s1_begin s1_insert_tbl1 s3_begin s3_insert_tbl1 s2_alter_pub_add_tbl s1_insert_tbl1 s1_commit s3_commit s2_get_binary_changes +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s3_begin: BEGIN; +step s3_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (2, 2); +step s2_alter_pub_add_tbl: ALTER PUBLICATION pub ADD TABLE tbl1; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s3_commit: COMMIT; +step s2_get_binary_changes: SELECT count(data) FROM pg_logical_slot_get_binary_changes('isolation_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub') WHERE get_byte(data, 0) = 73; +count +----- + 1 +(1 row) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/specs/invalidation_distribution.spec b/contrib/test_decoding/specs/invalidation_distribution.spec index decbed627e327..67d41969ac1d6 100644 --- a/contrib/test_decoding/specs/invalidation_distribution.spec +++ b/contrib/test_decoding/specs/invalidation_distribution.spec @@ -28,5 +28,16 @@ setup { SET synchronous_commit=on; } step "s2_alter_pub_add_tbl" { ALTER PUBLICATION pub ADD TABLE tbl1; } step "s2_get_binary_changes" { SELECT count(data) FROM pg_logical_slot_get_binary_changes('isolation_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub') WHERE get_byte(data, 0) = 73; } +session "s3" +setup { SET synchronous_commit=on; } +step "s3_begin" { BEGIN; } +step "s3_insert_tbl1" { INSERT INTO tbl1 (val1, val2) VALUES (2, 2); } +step "s3_commit" { COMMIT; } + # Expect to get one insert change. LOGICAL_REP_MSG_INSERT = 'I' permutation "s1_insert_tbl1" "s1_begin" "s1_insert_tbl1" "s2_alter_pub_add_tbl" "s1_commit" "s1_insert_tbl1" "s2_get_binary_changes" + +# Expect to get one insert change with LOGICAL_REP_MSG_INSERT = 'I' from +# the second "s1_insert_tbl1" executed after adding the table tbl1 to the +# publication in "s2_alter_pub_add_tbl". +permutation "s1_begin" "s1_insert_tbl1" "s3_begin" "s3_insert_tbl1" "s2_alter_pub_add_tbl" "s1_insert_tbl1" "s1_commit" "s3_commit" "s2_get_binary_changes" diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 03eb005c39d75..d85a9f2bb2026 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -108,10 +108,22 @@ #include "storage/fd.h" #include "storage/sinval.h" #include "utils/builtins.h" +#include "utils/inval.h" #include "utils/memutils.h" #include "utils/rel.h" #include "utils/relfilenumbermap.h" +/* + * Each transaction has an 8MB limit for invalidation messages distributed from + * other transactions. This limit is set considering scenarios with many + * concurrent logical decoding operations. When the distributed invalidation + * messages reach this threshold, the transaction is marked as + * RBTXN_DISTR_INVAL_OVERFLOWED to invalidate the complete cache as we have lost + * some inval messages and hence don't know what needs to be invalidated. + */ +#define MAX_DISTR_INVAL_MSG_PER_TXN \ + ((8 * 1024 * 1024) / sizeof(SharedInvalidationMessage)) + /* entry for a hash table we use to map from xid to our transaction state */ typedef struct ReorderBufferTXNByIdEnt { @@ -469,6 +481,12 @@ ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) txn->invalidations = NULL; } + if (txn->invalidations_distributed) + { + pfree(txn->invalidations_distributed); + txn->invalidations_distributed = NULL; + } + /* Reset the toast hash */ ReorderBufferToastReset(rb, txn); @@ -2574,7 +2592,17 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, AbortCurrentTransaction(); /* make sure there's no cache pollution */ - ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); + if (rbtxn_distr_inval_overflowed(txn)) + { + Assert(txn->ninvalidations_distributed == 0); + InvalidateSystemCaches(); + } + else + { + ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); + ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed, + txn->invalidations_distributed); + } if (using_subtxn) RollbackAndReleaseCurrentSubTransaction(); @@ -2620,8 +2648,17 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, AbortCurrentTransaction(); /* make sure there's no cache pollution */ - ReorderBufferExecuteInvalidations(txn->ninvalidations, - txn->invalidations); + if (rbtxn_distr_inval_overflowed(txn)) + { + Assert(txn->ninvalidations_distributed == 0); + InvalidateSystemCaches(); + } + else + { + ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); + ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed, + txn->invalidations_distributed); + } if (using_subtxn) RollbackAndReleaseCurrentSubTransaction(); @@ -2951,7 +2988,8 @@ ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn, * We might have decoded changes for this transaction that could load * the cache as per the current transaction's view (consider DDL's * happened in this transaction). We don't want the decoding of future - * transactions to use those cache entries so execute invalidations. + * transactions to use those cache entries so execute only the inval + * messages in this transaction. */ if (txn->ninvalidations > 0) ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, @@ -3038,9 +3076,10 @@ ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn) txn->final_lsn = lsn; /* - * Process cache invalidation messages if there are any. Even if we're not - * interested in the transaction's contents, it could have manipulated the - * catalog and we need to update the caches according to that. + * Process only cache invalidation messages in this transaction if there + * are any. Even if we're not interested in the transaction's contents, it + * could have manipulated the catalog and we need to update the caches + * according to that. */ if (txn->base_snapshot != NULL && txn->ninvalidations > 0) ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, @@ -3312,6 +3351,57 @@ ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid, txn->ntuplecids++; } +/* + * Add new invalidation messages to the reorder buffer queue. + */ +static void +ReorderBufferQueueInvalidations(ReorderBuffer *rb, TransactionId xid, + XLogRecPtr lsn, Size nmsgs, + SharedInvalidationMessage *msgs) +{ + ReorderBufferChange *change; + + change = ReorderBufferGetChange(rb); + change->action = REORDER_BUFFER_CHANGE_INVALIDATION; + change->data.inval.ninvalidations = nmsgs; + change->data.inval.invalidations = (SharedInvalidationMessage *) + palloc(sizeof(SharedInvalidationMessage) * nmsgs); + memcpy(change->data.inval.invalidations, msgs, + sizeof(SharedInvalidationMessage) * nmsgs); + + ReorderBufferQueueChange(rb, xid, lsn, change, false); +} + +/* + * A helper function for ReorderBufferAddInvalidations() and + * ReorderBufferAddDistributedInvalidations() to accumulate the invalidation + * messages to the **invals_out. + */ +static void +ReorderBufferAccumulateInvalidations(SharedInvalidationMessage **invals_out, + uint32 *ninvals_out, + SharedInvalidationMessage *msgs_new, + Size nmsgs_new) +{ + if (*ninvals_out == 0) + { + *ninvals_out = nmsgs_new; + *invals_out = (SharedInvalidationMessage *) + palloc(sizeof(SharedInvalidationMessage) * nmsgs_new); + memcpy(*invals_out, msgs_new, sizeof(SharedInvalidationMessage) * nmsgs_new); + } + else + { + /* Enlarge the array of inval messages */ + *invals_out = (SharedInvalidationMessage *) + repalloc(*invals_out, sizeof(SharedInvalidationMessage) * + (*ninvals_out + nmsgs_new)); + memcpy(*invals_out + *ninvals_out, msgs_new, + nmsgs_new * sizeof(SharedInvalidationMessage)); + *ninvals_out += nmsgs_new; + } +} + /* * Accumulate the invalidations for executing them later. * @@ -3332,7 +3422,6 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, { ReorderBufferTXN *txn; MemoryContext oldcontext; - ReorderBufferChange *change; txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); @@ -3347,35 +3436,76 @@ ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, Assert(nmsgs > 0); - /* Accumulate invalidations. */ - if (txn->ninvalidations == 0) - { - txn->ninvalidations = nmsgs; - txn->invalidations = (SharedInvalidationMessage *) - palloc(sizeof(SharedInvalidationMessage) * nmsgs); - memcpy(txn->invalidations, msgs, - sizeof(SharedInvalidationMessage) * nmsgs); - } - else + ReorderBufferAccumulateInvalidations(&txn->invalidations, + &txn->ninvalidations, + msgs, nmsgs); + + ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs); + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Accumulate the invalidations distributed by other committed transactions + * for executing them later. + * + * This function is similar to ReorderBufferAddInvalidations() but stores + * the given inval messages to the txn->invalidations_distributed with the + * overflow check. + * + * This needs to be called by committed transactions to distribute their + * inval messages to in-progress transactions. + */ +void +ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid, + XLogRecPtr lsn, Size nmsgs, + SharedInvalidationMessage *msgs) +{ + ReorderBufferTXN *txn; + MemoryContext oldcontext; + + txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); + + oldcontext = MemoryContextSwitchTo(rb->context); + + /* + * Collect all the invalidations under the top transaction, if available, + * so that we can execute them all together. See comments + * ReorderBufferAddInvalidations. + */ + txn = rbtxn_get_toptxn(txn); + + Assert(nmsgs > 0); + + if (!rbtxn_distr_inval_overflowed(txn)) { - txn->invalidations = (SharedInvalidationMessage *) - repalloc(txn->invalidations, sizeof(SharedInvalidationMessage) * - (txn->ninvalidations + nmsgs)); + /* + * Check the transaction has enough space for storing distributed + * invalidation messages. + */ + if (txn->ninvalidations_distributed + nmsgs >= MAX_DISTR_INVAL_MSG_PER_TXN) + { + /* + * Mark the invalidation message as overflowed and free up the + * messages accumulated so far. + */ + txn->txn_flags |= RBTXN_DISTR_INVAL_OVERFLOWED; - memcpy(txn->invalidations + txn->ninvalidations, msgs, - nmsgs * sizeof(SharedInvalidationMessage)); - txn->ninvalidations += nmsgs; + if (txn->invalidations_distributed) + { + pfree(txn->invalidations_distributed); + txn->invalidations_distributed = NULL; + txn->ninvalidations_distributed = 0; + } + } + else + ReorderBufferAccumulateInvalidations(&txn->invalidations_distributed, + &txn->ninvalidations_distributed, + msgs, nmsgs); } - change = ReorderBufferGetChange(rb); - change->action = REORDER_BUFFER_CHANGE_INVALIDATION; - change->data.inval.ninvalidations = nmsgs; - change->data.inval.invalidations = (SharedInvalidationMessage *) - palloc(sizeof(SharedInvalidationMessage) * nmsgs); - memcpy(change->data.inval.invalidations, msgs, - sizeof(SharedInvalidationMessage) * nmsgs); - - ReorderBufferQueueChange(rb, xid, lsn, change, false); + /* Queue the invalidation messages into the transaction */ + ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs); MemoryContextSwitchTo(oldcontext); } diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 110e0b0a0449e..5c3bbf0e93f8c 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -932,6 +932,13 @@ SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, Transact * contents built by the current transaction even after its decoding, * which should have been invalidated due to concurrent catalog * changing transaction. + * + * Distribute only the invalidation messages generated by the current + * committed transaction. Invalidation messages received from other + * transactions would have already been propagated to the relevant + * in-progress transactions. This transaction would have processed + * those invalidations, ensuring that subsequent transactions observe + * a consistent cache state. */ if (txn->xid != xid) { @@ -945,8 +952,9 @@ SnapBuildDistributeSnapshotAndInval(SnapBuild *builder, XLogRecPtr lsn, Transact { Assert(msgs != NULL); - ReorderBufferAddInvalidations(builder->reorder, txn->xid, lsn, - ninvalidations, msgs); + ReorderBufferAddDistributedInvalidations(builder->reorder, + txn->xid, lsn, + ninvalidations, msgs); } } } diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h index 4c56f219fd8fd..0cfa9005141a1 100644 --- a/src/include/replication/reorderbuffer.h +++ b/src/include/replication/reorderbuffer.h @@ -159,15 +159,16 @@ typedef struct ReorderBufferChange } ReorderBufferChange; /* ReorderBufferTXN txn_flags */ -#define RBTXN_HAS_CATALOG_CHANGES 0x0001 -#define RBTXN_IS_SUBXACT 0x0002 -#define RBTXN_IS_SERIALIZED 0x0004 -#define RBTXN_IS_SERIALIZED_CLEAR 0x0008 -#define RBTXN_IS_STREAMED 0x0010 -#define RBTXN_HAS_PARTIAL_CHANGE 0x0020 -#define RBTXN_PREPARE 0x0040 -#define RBTXN_SKIPPED_PREPARE 0x0080 -#define RBTXN_HAS_STREAMABLE_CHANGE 0x0100 +#define RBTXN_HAS_CATALOG_CHANGES 0x0001 +#define RBTXN_IS_SUBXACT 0x0002 +#define RBTXN_IS_SERIALIZED 0x0004 +#define RBTXN_IS_SERIALIZED_CLEAR 0x0008 +#define RBTXN_IS_STREAMED 0x0010 +#define RBTXN_HAS_PARTIAL_CHANGE 0x0020 +#define RBTXN_PREPARE 0x0040 +#define RBTXN_SKIPPED_PREPARE 0x0080 +#define RBTXN_HAS_STREAMABLE_CHANGE 0x0100 +#define RBTXN_DISTR_INVAL_OVERFLOWED 0x0200 /* Does the transaction have catalog changes? */ #define rbtxn_has_catalog_changes(txn) \ @@ -231,6 +232,12 @@ typedef struct ReorderBufferChange ((txn)->txn_flags & RBTXN_SKIPPED_PREPARE) != 0 \ ) +/* Is the array of distributed inval messages overflowed? */ +#define rbtxn_distr_inval_overflowed(txn) \ +( \ + ((txn)->txn_flags & RBTXN_DISTR_INVAL_OVERFLOWED) != 0 \ +) + /* Is this a top-level transaction? */ #define rbtxn_is_toptxn(txn) \ ( \ @@ -422,6 +429,12 @@ typedef struct ReorderBufferTXN * Private data pointer of the output plugin. */ void *output_plugin_private; + + /* + * Stores cache invalidation messages distributed by other transactions. + */ + uint32 ninvalidations_distributed; + SharedInvalidationMessage *invalidations_distributed; } ReorderBufferTXN; /* so we can define the callbacks used inside struct ReorderBuffer itself */ @@ -709,6 +722,9 @@ extern void ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid, CommandId cmin, CommandId cmax, CommandId combocid); extern void ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn, Size nmsgs, SharedInvalidationMessage *msgs); +extern void ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid, + XLogRecPtr lsn, Size nmsgs, + SharedInvalidationMessage *msgs); extern void ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations, SharedInvalidationMessage *invalidations); extern void ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn); From 6823c98ebded580b5fceaed836b7763ebde184f2 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 19 Jun 2025 09:32:30 +0530 Subject: [PATCH 676/796] Improve log messages and docs for slot synchronization. Improve the clarity of LOG messages when a failover logical slot synchronization fails, making the reasons more explicit for easier debugging. Update the documentation to outline scenarios where slot synchronization can fail, especially during the initial sync, and emphasize that pg_sync_replication_slot() is primarily intended for testing and debugging purposes. We also discussed improving the functionality of pg_sync_replication_slot() so that it can be used reliably, but we would take up that work for next version after some more discussion and review. Reported-by: Suraj Kharage Author: shveta malik Reviewed-by: Zhijie Hou Reviewed-by: Peter Smith Reviewed-by: Amit Kapila Backpatch-through: 17, where it was introduced Discussion: https://postgr.es/m/CAF1DzPWTcg+m+x+oVVB=y4q9=PYYsL_mujVp7uJr-_oUtWNGbA@mail.gmail.com --- doc/src/sgml/func.sgml | 6 ++- doc/src/sgml/logicaldecoding.sgml | 54 ++++++++++++++++++++-- src/backend/replication/logical/slotsync.c | 6 +-- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 697c1a02891d5..f12272e5a407e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29177,7 +29177,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset - + pg_logical_slot_get_binary_changes @@ -29448,7 +29448,9 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset standby server. Temporary synced slots, if any, cannot be used for logical decoding and must be dropped after promotion. See for details. - Note that this function cannot be executed if + Note that this function is primarily intended for testing and + debugging purposes and should be used with caution. Additionaly, + this function cannot be executed if sync_replication_slots is enabled and the slotsync worker is already running to perform the synchronization of slots. diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index 0b4f1fffb6aa1..fc3ed623294b6 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -370,10 +370,10 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU pg_create_logical_replication_slot, or by using the failover option of - CREATE SUBSCRIPTION during slot creation, and then calling - - pg_sync_replication_slots - on the standby. By setting + CREATE SUBSCRIPTION during slot creation. + Additionally, enabling + sync_replication_slots on the standby + is required. By enabling sync_replication_slots on the standby, the failover slots can be synchronized periodically in the slotsync worker. For the synchronization to work, it is mandatory to @@ -398,6 +398,52 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU receiving the WAL up to the latest flushed position on the primary server. + + + While enabling + sync_replication_slots allows for automatic + periodic synchronization of failover slots, they can also be manually + synchronized using the + pg_sync_replication_slots function on the standby. + However, this function is primarily intended for testing and debugging and + should be used with caution. Unlike automatic synchronization, it does not + include cyclic retries, making it more prone to synchronization failures, + particularly during initial sync scenarios where the required WAL files + or catalog rows for the slot may have already been removed or are at risk + of being removed on the standby. In contrast, automatic synchronization + via sync_replication_slots provides continuous slot + updates, enabling seamless failover and supporting high availability. + Therefore, it is the recommended method for synchronizing slots. + + + + + When slot synchronization is configured as recommended, + and the initial synchronization is performed either automatically or + manually via pg_sync_replication_slot, the standby can persist the + synchronized slot only if the following condition is met: The logical + replication slot on the primary must retain WALs and system catalog + rows that are still available on the standby. This ensures data + integrity and allows logical replication to continue smoothly after + promotion. + If the required WALs or catalog rows have already been purged from the + standby, the slot will not be persisted to avoid data loss. In such + cases, the following log message may appear: + + LOG: could not synchronize replication slot "failover_slot" + DETAIL: Synchronization could lead to data loss as the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756 + + If the logical replication slot is actively used by a consumer, no + manual intervention is needed; the slot will advance automatically, + and synchronization will resume in the next cycle. However, if no + consumer is configured, it is advisable to manually advance the slot + on the primary using + pg_logical_slot_get_changes or + + pg_logical_slot_get_binary_changes, + allowing synchronization to proceed. + + The ability to resume logical replication after failover depends upon the pg_replication_slots.synced diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index 73fe3f56af2b3..e6dcb79231974 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -212,9 +212,9 @@ update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid, * impact the users, so we used DEBUG1 level to log the message. */ ereport(slot->data.persistency == RS_TEMPORARY ? LOG : DEBUG1, - errmsg("could not synchronize replication slot \"%s\" because remote slot precedes local slot", + errmsg("could not synchronize replication slot \"%s\"", remote_slot->name), - errdetail("The remote slot has LSN %X/%X and catalog xmin %u, but the local slot has LSN %X/%X and catalog xmin %u.", + errdetail("Synchronization could lead to data loss as the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u.", LSN_FORMAT_ARGS(remote_slot->restart_lsn), remote_slot->catalog_xmin, LSN_FORMAT_ARGS(slot->data.restart_lsn), @@ -577,7 +577,7 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid) { ereport(LOG, errmsg("could not synchronize replication slot \"%s\"", remote_slot->name), - errdetail("Logical decoding could not find consistent point from local slot's LSN %X/%X.", + errdetail("Synchronization could lead to data loss as standby could not build a consistent snapshot to decode WALs at LSN %X/%X.", LSN_FORMAT_ARGS(slot->data.restart_lsn))); return false; From 812702fe20821d6d00e53dc99114b2915006b193 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Thu, 19 Jun 2025 12:43:27 -0700 Subject: [PATCH 677/796] Correct docs about partitions and EXCLUDE constraints. In version 17 we added support for cross-partition EXCLUDE constraints, as long as they included all partition key columns and compared them with equality (see 8c852ba9a4). I updated the docs for exclusion constraints, but I missed that the docs for CREATE TABLE still said that they were not supported. This commit fixes that. Author: Paul A. Jungwirth Co-authored-by: Jeff Davis Discussion: https://postgr.es/m/c955d292-b92d-42d1-a2a0-1ec6715a2546@illuminatedcomputing.com Backpatch-through: 17 --- doc/src/sgml/ref/create_table.sgml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 38be1a5a710e1..a664b7fd02473 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -442,11 +442,6 @@ WITH ( MODULUS numeric_literal, REM the values in the new row, an error will be reported. - - Partitioned tables do not support EXCLUDE constraints; - however, you can define these constraints on individual partitions. - - See for more discussion on table partitioning. @@ -1125,6 +1120,18 @@ WITH ( MODULUS numeric_literal, REM exclusion constraint on a subset of the table; internally this creates a partial index. Note that parentheses are required around the predicate. + + + When establishing an exclusion constraint for a multi-level partition + hierarchy, all the columns in the partition key of the target + partitioned table, as well as those of all its descendant partitioned + tables, must be included in the constraint definition. Additionally, + those columns must be compared using the equality operator. These + restrictions ensure that potentially-conflicting rows will exist in the + same partition. The constraint may also refer to other columns which + are not a part of any partition key, which can be compared using any + appropriate operator. + From 3e1dae17ad532c23087a3c48313507b1f995f778 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Fri, 20 Jun 2025 01:41:28 +0300 Subject: [PATCH 678/796] Improve runtime and output of tests for replication slots checkpointing. The TAP tests that verify logical and physical replication slot behavior during checkpoints (046_checkpoint_logical_slot.pl and 047_checkpoint_physical_slot.pl) inserted two batches of 2 million rows each, generating approximately 520 MB of WAL. On slow machines, or when compiled with '-DRELCACHE_FORCE_RELEASE -DCATCACHE_FORCE_RELEASE', this caused the tests to run for 8-9 minutes and occasionally time out, as seen on the buildfarm animal prion. This commit modifies the mentioned tests to utilize the $node->advance_wal() function, thereby reducing runtime. Once we do not use the generated data, the proposed function is a good alternative, which cuts the total wall-clock run time. While here, remove superfluous '\n' characters from several note() calls; these appeared literally in the build-farm logs and looked odd. Also, remove excessive 'shared_preload_libraries' GUC from the config and add a check for 'injection_points' extension availability. Reported-by: Alexander Lakhin Reported-by: Tom Lane Author: Alexander Korotkov Author: Vitaly Davydov Reviewed-by: Hayato Kuroda Discussion: https://postgr.es/m/fbc5d94e-6fbd-4a64-85d4-c9e284a58eb2%40gmail.com Backpatch-through: 17 --- .../recovery/t/046_checkpoint_logical_slot.pl | 34 +++++++++---------- .../t/047_checkpoint_physical_slot.pl | 26 +++++++------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/test/recovery/t/046_checkpoint_logical_slot.pl b/src/test/recovery/t/046_checkpoint_logical_slot.pl index b4265c4a6a53f..0468d4609e43b 100644 --- a/src/test/recovery/t/046_checkpoint_logical_slot.pl +++ b/src/test/recovery/t/046_checkpoint_logical_slot.pl @@ -21,15 +21,21 @@ $node = PostgreSQL::Test::Cluster->new('mike'); $node->init; -$node->append_conf('postgresql.conf', - "shared_preload_libraries = 'injection_points'"); $node->append_conf('postgresql.conf', "wal_level = 'logical'"); $node->start; -$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); -# Create a simple table to generate data into. -$node->safe_psql('postgres', - q{create table t (id serial primary key, b text)}); +# Check if the extension injection_points is available, as it may be +# possible that this script is run with installcheck, where the module +# would not be installed by default. +$result = $node->safe_psql('postgres', + "SELECT count(*) > 0 FROM pg_available_extensions WHERE name = 'injection_points';" +); +if ($result eq 'f') +{ + plan skip_all => 'Extension injection_points not installed'; +} + +$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); # Create the two slots we'll need. $node->safe_psql('postgres', @@ -58,23 +64,17 @@ \q )); -# Insert 2M rows; that's about 260MB (~20 segments) worth of WAL. -$node->safe_psql('postgres', - q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} -); +$node->advance_wal(20); # Run another checkpoint to set a new restore LSN. $node->safe_psql('postgres', q{checkpoint}); -# Another 2M rows; that's about 260MB (~20 segments) worth of WAL. -$node->safe_psql('postgres', - q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} -); +$node->advance_wal(20); # Run another checkpoint, this time in the background, and make it wait # on the injection point) so that the checkpoint stops right before # removing old WAL segments. -note('starting checkpoint\n'); +note('starting checkpoint'); my $checkpoint = $node->background_psql('postgres'); $checkpoint->query_safe( @@ -88,7 +88,7 @@ )); # Wait until the checkpoint stops right before removing WAL segments. -note('waiting for injection_point\n'); +note('waiting for injection_point'); $node->wait_for_event('checkpointer', 'checkpoint-before-old-wal-removal'); note('injection_point is reached'); @@ -107,7 +107,7 @@ )); # Wait until the slot's restart_lsn points to the next WAL segment. -note('waiting for injection_point\n'); +note('waiting for injection_point'); $node->wait_for_event('client backend', 'logical-replication-slot-advance-segment'); note('injection_point is reached'); diff --git a/src/test/recovery/t/047_checkpoint_physical_slot.pl b/src/test/recovery/t/047_checkpoint_physical_slot.pl index 454e56b9bd2da..643cb6a7bcb9b 100644 --- a/src/test/recovery/t/047_checkpoint_physical_slot.pl +++ b/src/test/recovery/t/047_checkpoint_physical_slot.pl @@ -21,15 +21,21 @@ $node = PostgreSQL::Test::Cluster->new('mike'); $node->init; -$node->append_conf('postgresql.conf', - "shared_preload_libraries = 'injection_points'"); $node->append_conf('postgresql.conf', "wal_level = 'replica'"); $node->start; -$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); -# Create a simple table to generate data into. -$node->safe_psql('postgres', - q{create table t (id serial primary key, b text)}); +# Check if the extension injection_points is available, as it may be +# possible that this script is run with installcheck, where the module +# would not be installed by default. +$result = $node->safe_psql('postgres', + "SELECT count(*) > 0 FROM pg_available_extensions WHERE name = 'injection_points';" +); +if ($result eq 'f') +{ + plan skip_all => 'Extension injection_points not installed'; +} + +$node->safe_psql('postgres', q(CREATE EXTENSION injection_points)); # Create a physical replication slot. $node->safe_psql('postgres', @@ -44,9 +50,7 @@ $node->safe_psql('postgres', q{checkpoint}); # Insert 2M rows; that's about 260MB (~20 segments) worth of WAL. -$node->safe_psql('postgres', - q{insert into t (b) select md5(i::text) from generate_series(1,100000) s(i)} -); +$node->advance_wal(20); # Advance slot to the current position, just to have everything "valid". $node->safe_psql('postgres', @@ -57,9 +61,7 @@ $node->safe_psql('postgres', q{checkpoint}); # Another 2M rows; that's about 260MB (~20 segments) worth of WAL. -$node->safe_psql('postgres', - q{insert into t (b) select md5(i::text) from generate_series(1,1000000) s(i)} -); +$node->advance_wal(20); my $restart_lsn_init = $node->safe_psql('postgres', q{select restart_lsn from pg_replication_slots where slot_name = 'slot_physical'} From 7540a3cf2d0bd774d86c1a1a510cdabc4cf82f49 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 20 Jun 2025 13:41:11 -0400 Subject: [PATCH 679/796] Use SnapshotDirty when checking for conflicting index names. While choosing an autogenerated name for an index, look for pre-existing relations using a SnapshotDirty snapshot, instead of the previous behavior that considered only committed-good pg_class rows. This allows us to detect and avoid conflicts against indexes that are still being built. It's still possible to fail due to a race condition, but the window is now just the amount of time that it takes DefineIndex to validate all its parameters, call smgrcreate(), and enter the index's pg_class row. Formerly the race window covered the entire time needed to create and fill an index, which could be very long if the table is large. Worse, if the conflicting index creation is part of a larger transaction, it wouldn't be visible till COMMIT. So this isn't a complete solution, but it should greatly ameliorate the problem, and the patch is simple enough to be back-patchable. It might at some point be useful to do the same for pg_constraint entries (cf. ChooseConstraintName, ConstraintNameExists, and related functions). However, in the absence of field complaints, I'll leave that alone for now. The relation-name test should be good enough for index-based constraints, while foreign-key constraints seem to be okay since they require exclusive locks to create. Bug: #18959 Reported-by: Maximilian Chrzan Author: Tom Lane Reviewed-by: Dilip Kumar Discussion: https://postgr.es/m/18959-f63b53b864bb1417@postgresql.org Backpatch-through: 13 --- src/backend/commands/indexcmds.c | 38 ++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 56a425a171e4d..b3f0917015196 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -2461,7 +2461,9 @@ makeObjectName(const char *name1, const char *name2, const char *label) * constraint names.) * * Note: it is theoretically possible to get a collision anyway, if someone - * else chooses the same name concurrently. This is fairly unlikely to be + * else chooses the same name concurrently. We shorten the race condition + * window by checking for conflicting relations using SnapshotDirty, but + * that doesn't close the window entirely. This is fairly unlikely to be * a problem in practice, especially if one is holding an exclusive lock on * the relation identified by name1. However, if choosing multiple names * within a single command, you'd better create the new object and do @@ -2477,15 +2479,45 @@ ChooseRelationName(const char *name1, const char *name2, int pass = 0; char *relname = NULL; char modlabel[NAMEDATALEN]; + SnapshotData SnapshotDirty; + Relation pgclassrel; + + /* prepare to search pg_class with a dirty snapshot */ + InitDirtySnapshot(SnapshotDirty); + pgclassrel = table_open(RelationRelationId, AccessShareLock); /* try the unmodified label first */ strlcpy(modlabel, label, sizeof(modlabel)); for (;;) { + ScanKeyData key[2]; + SysScanDesc scan; + bool collides; + relname = makeObjectName(name1, name2, modlabel); - if (!OidIsValid(get_relname_relid(relname, namespaceid))) + /* is there any conflicting relation name? */ + ScanKeyInit(&key[0], + Anum_pg_class_relname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(relname)); + ScanKeyInit(&key[1], + Anum_pg_class_relnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceid)); + + scan = systable_beginscan(pgclassrel, ClassNameNspIndexId, + true /* indexOK */ , + &SnapshotDirty, + 2, key); + + collides = HeapTupleIsValid(systable_getnext(scan)); + + systable_endscan(scan); + + /* break out of loop if no conflict */ + if (!collides) { if (!isconstraint || !ConstraintNameExists(relname, namespaceid)) @@ -2497,6 +2529,8 @@ ChooseRelationName(const char *name1, const char *name2, snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass); } + table_close(pgclassrel, AccessShareLock); + return relname; } From 8005baaada7a821192b19585f82809036a5221cb Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 21 Jun 2025 12:52:37 -0400 Subject: [PATCH 680/796] Doc: improve documentation about width_bucket(). Specify whether the bucket bounds are inclusive or exclusive, and improve some other vague language. Explain the behavior that occurs when the "low" bound is greater than the "high" bound. Make width_bucket_numeric's comment more like that for width_bucket_float8, in particular noting that infinite bounds are rejected (since they became possible in v14). Reported-by: Ben Peachey Higdon Author: Robert Treat Co-authored-by: Tom Lane Reviewed-by: Dean Rasheed Discussion: https://postgr.es/m/2BD74F86-5B89-4AC1-8F13-23CED3546AC1@gmail.com Backpatch-through: 13 --- doc/src/sgml/func.sgml | 18 ++++++++++++++---- src/backend/utils/adt/float.c | 4 ++-- src/backend/utils/adt/numeric.c | 7 ++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index f12272e5a407e..061330a53c5c9 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1786,13 +1786,23 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in which operand falls in a histogram having count equal-width buckets spanning the range low to high. - Returns 0 + The buckets have inclusive lower bounds and exclusive upper bounds. + Returns 0 for an input less + than low, or count+1 for an input - outside that range. + greater than or equal to high. + If low > high, + the behavior is mirror-reversed, with bucket 1 + now being the one just below low, and the + inclusive bounds now being on the upper side. width_bucket(5.35, 0.024, 10.06, 5) 3 + + + width_bucket(9, 10, 0, 10) + 2 @@ -1804,8 +1814,8 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in Returns the number of the bucket in which operand falls given an array listing the - lower bounds of the buckets. Returns 0 for an - input less than the first lower + inclusive lower bounds of the buckets. + Returns 0 for an input less than the first lower bound. operand and the array elements can be of any type having standard comparison operators. The thresholds array must be diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index cbbb8aecafcc7..22421cb20befe 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -3980,8 +3980,8 @@ float84ge(PG_FUNCTION_ARGS) * in the histogram. width_bucket() returns an integer indicating the * bucket number that 'operand' belongs to in an equiwidth histogram * with the specified characteristics. An operand smaller than the - * lower bound is assigned to bucket 0. An operand greater than the - * upper bound is assigned to an additional bucket (with number + * lower bound is assigned to bucket 0. An operand greater than or equal + * to the upper bound is assigned to an additional bucket (with number * count+1). We don't allow "NaN" for any of the float8 inputs, and we * don't allow either of the histogram bounds to be +/- infinity. */ diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 57386aabdfed9..118e222e9be6c 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -1836,9 +1836,10 @@ generate_series_step_numeric(PG_FUNCTION_ARGS) * in the histogram. width_bucket() returns an integer indicating the * bucket number that 'operand' belongs to in an equiwidth histogram * with the specified characteristics. An operand smaller than the - * lower bound is assigned to bucket 0. An operand greater than the - * upper bound is assigned to an additional bucket (with number - * count+1). We don't allow "NaN" for any of the numeric arguments. + * lower bound is assigned to bucket 0. An operand greater than or equal + * to the upper bound is assigned to an additional bucket (with number + * count+1). We don't allow "NaN" for any of the numeric inputs, and we + * don't allow either of the histogram bounds to be +/- infinity. */ Datum width_bucket_numeric(PG_FUNCTION_ARGS) From ec12fdd21dfe419fb0e81e69ba56d7fd43d4b676 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Sun, 22 Jun 2025 14:13:46 +0200 Subject: [PATCH 681/796] meson: Fix meson warning WARNING: You should add the boolean check kwarg to the run_command call. It currently defaults to false, but it will default to true in meson 2.0. Introduced by commit bc46104fc9a. (This only happens in the msvc branch. All the other run_command calls are ok.) Reviewed-by: Andres Freund Discussion: https://www.postgresql.org/message-id/flat/42e13eb0-862a-441e-8d84-4f0fd5f6def0%40eisentraut.org --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 65c3961980fe0..731bb38a1eb90 100644 --- a/meson.build +++ b/meson.build @@ -1059,7 +1059,7 @@ if not perlopt.disabled() if cc.get_id() == 'msvc' # prevent binary mismatch between MSVC built plperl and Strawberry or # msys ucrt perl libraries - perl_v = run_command(perl, '-V').stdout() + perl_v = run_command(perl, '-V', check: false).stdout() if not perl_v.contains('USE_THREAD_SAFE_LOCALE') perl_ccflags += ['-DNO_THREAD_SAFE_LOCALE'] endif From 6baa7266e9b649352467a2c0a2d1b3db5dac4329 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 24 Jun 2025 14:21:10 +0900 Subject: [PATCH 682/796] doc: Fix incorrect UUID index entry in function documentation. Previously, the UUID functions documentation defined the "UUID" index entry to link to the UUID data type page, even though that entry already exists there. Instead, the UUID functions page should define its own index entry linking to itself. This commit updates the UUID index entry in the UUID functions documentation to point to the correct section, improving navigation and avoiding duplication. Back-patch to all supported versions. Author: Fujii Masao Reviewed-by: Masahiko Sawada Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/f33e0493-5773-4296-87c5-7ce459054cfe@oss.nttdata.com Backpatch-through: 13 --- doc/src/sgml/func.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 061330a53c5c9..1b16914fa7e68 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -14161,7 +14161,7 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple UUID Functions - + UUID generating From 6142775561a3b4609eb3476c6742784dbebe5483 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Tue, 24 Jun 2025 11:49:37 +0200 Subject: [PATCH 683/796] doc: Remove dead link to NewbieDoc Docbook Guide The link returns 404 and no replacement is available in the project on Sourceforge where the content once was. Since we already link to resources for both beginner and experienced docs hackers, remove the the dead link. Backpatch to all supported versions as the link was added in 8.1. Author: Daniel Gustafsson Reviewed-by: Magnus Hagander Reviewed-by: Michael Paquier Reported-by: jian he Discussion: https://postgr.es/m/CACJufxH=YzQPDOe+2WuYZ7seD-BOyjCBmP6JiErpoSiVZWDRnw@mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/docguide.sgml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/src/sgml/docguide.sgml b/doc/src/sgml/docguide.sgml index db4bcce56eac6..7b61b4841aa03 100644 --- a/doc/src/sgml/docguide.sgml +++ b/doc/src/sgml/docguide.sgml @@ -60,9 +60,7 @@ maintained by the OASIS group. The official DocBook site has good introductory and reference documentation and - a complete O'Reilly book for your online reading pleasure. The - - NewbieDoc Docbook Guide is very helpful for beginners. + a complete O'Reilly book for your online reading pleasure. The FreeBSD Documentation Project also uses DocBook and has some good information, including a number of style guidelines that might be From d7761fd69f3d2f2bd2b737999add70c8014ca9e2 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Tue, 24 Jun 2025 09:21:55 -0400 Subject: [PATCH 684/796] Test that vacuum removes tuples older than OldestXmin If vacuum fails to prune a tuple killed before OldestXmin, it will decide to freeze its xmax and later error out in pre-freeze checks. Add a test reproducing this scenario to the recovery suite which creates a table on a primary, updates the table to generate dead tuples for vacuum, and then, during the vacuum, uses a replica to force GlobalVisState->maybe_needed on the primary to move backwards and precede the value of OldestXmin set at the beginning of vacuuming the table. This test is coverage for a case fixed in 83c39a1f7f3. The test was originally committed to master in aa607980aee but later reverted in efcbb76efe4 due to test instability. The test requires multiple index passes. In Postgres 17+, vacuum uses a TID store for the dead TIDs that is very space efficient. With the old minimum maintenance_work_mem of 1 MB, it required a large number of dead rows to generate enough dead TIDs to force multiple index vacuuming passes. Once the source code changes were made to allow a minimum maintenance_work_mem value of 64kB, the test could be made much faster and more stable. Author: Melanie Plageman Reviewed-by: John Naylor Reviewed-by: Peter Geoghegan Discussion: https://postgr.es/m/CAAKRu_ZJBkidusDut6i%3DbDCiXzJEp93GC1%2BNFaZt4eqanYF3Kw%40mail.gmail.com Backpatch-through: 17 --- src/test/recovery/meson.build | 3 +- .../recovery/t/048_vacuum_horizon_floor.pl | 278 ++++++++++++++++++ 2 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 src/test/recovery/t/048_vacuum_horizon_floor.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 77e8deb142fd1..9f01d71dc3b10 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -54,7 +54,8 @@ tests += { 't/043_no_contrecord_switch.pl', 't/045_archive_restartpoint.pl', 't/046_checkpoint_logical_slot.pl', - 't/047_checkpoint_physical_slot.pl' + 't/047_checkpoint_physical_slot.pl', + 't/048_vacuum_horizon_floor.pl' ], }, } diff --git a/src/test/recovery/t/048_vacuum_horizon_floor.pl b/src/test/recovery/t/048_vacuum_horizon_floor.pl new file mode 100644 index 0000000000000..d48a6ef7e0f24 --- /dev/null +++ b/src/test/recovery/t/048_vacuum_horizon_floor.pl @@ -0,0 +1,278 @@ +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use Test::More; + +# Test that vacuum prunes away all dead tuples killed before OldestXmin +# +# This test creates a table on a primary, updates the table to generate dead +# tuples for vacuum, and then, during the vacuum, uses the replica to force +# GlobalVisState->maybe_needed on the primary to move backwards and precede +# the value of OldestXmin set at the beginning of vacuuming the table. + +# Set up nodes +my $node_primary = PostgreSQL::Test::Cluster->new('primary'); +$node_primary->init(allows_streaming => 'physical'); + +# io_combine_limit is set to 1 to avoid pinning more than one buffer at a time +# to ensure test determinism. +$node_primary->append_conf( + 'postgresql.conf', qq[ +hot_standby_feedback = on +autovacuum = off +log_min_messages = INFO +maintenance_work_mem = 64 +io_combine_limit = 1 +]); +$node_primary->start; + +my $node_replica = PostgreSQL::Test::Cluster->new('standby'); + +$node_primary->backup('my_backup'); +$node_replica->init_from_backup($node_primary, 'my_backup', + has_streaming => 1); + +$node_replica->start; + +my $test_db = "test_db"; +$node_primary->safe_psql('postgres', "CREATE DATABASE $test_db"); + +# Save the original connection info for later use +my $orig_conninfo = $node_primary->connstr(); + +my $table1 = "vac_horizon_floor_table"; + +# Long-running Primary Session A +my $psql_primaryA = + $node_primary->background_psql($test_db, on_error_stop => 1); + +# Long-running Primary Session B +my $psql_primaryB = + $node_primary->background_psql($test_db, on_error_stop => 1); + +# Our test relies on two rounds of index vacuuming for reasons elaborated +# later. To trigger two rounds of index vacuuming, we must fill up the +# TIDStore with dead items partway through a vacuum of the table. The number +# of rows is just enough to ensure we exceed maintenance_work_mem on all +# supported platforms, while keeping test runtime as short as we can. +my $nrows = 2000; + +# Because vacuum's first pass, pruning, is where we use the GlobalVisState to +# check tuple visibility, GlobalVisState->maybe_needed must move backwards +# during pruning before checking the visibility for a tuple which would have +# been considered HEAPTUPLE_DEAD prior to maybe_needed moving backwards but +# HEAPTUPLE_RECENTLY_DEAD compared to the new, older value of maybe_needed. +# +# We must not only force the horizon on the primary to move backwards but also +# force the vacuuming backend's GlobalVisState to be updated. GlobalVisState +# is forced to update during index vacuuming. +# +# _bt_pendingfsm_finalize() calls GetOldestNonRemovableTransactionId() at the +# end of a round of index vacuuming, updating the backend's GlobalVisState +# and, in our case, moving maybe_needed backwards. +# +# Then vacuum's first (pruning) pass will continue and pruning will find our +# later inserted and updated tuple HEAPTUPLE_RECENTLY_DEAD when compared to +# maybe_needed but HEAPTUPLE_DEAD when compared to OldestXmin. +# +# Thus, we must force at least two rounds of index vacuuming to ensure that +# some tuple visibility checks will happen after a round of index vacuuming. +# To accomplish this, we set maintenance_work_mem to its minimum value and +# insert and delete enough rows that we force at least one round of index +# vacuuming before getting to a dead tuple which was killed after the standby +# is disconnected. +$node_primary->safe_psql($test_db, qq[ + CREATE TABLE ${table1}(col1 int) + WITH (autovacuum_enabled=false, fillfactor=10); + INSERT INTO $table1 VALUES(7); + INSERT INTO $table1 SELECT generate_series(1, $nrows) % 3; + CREATE INDEX on ${table1}(col1); + DELETE FROM $table1 WHERE col1 = 0; + INSERT INTO $table1 VALUES(7); +]); + +# We will later move the primary forward while the standby is disconnected. +# For now, however, there is no reason not to wait for the standby to catch +# up. +my $primary_lsn = $node_primary->lsn('flush'); +$node_primary->wait_for_catchup($node_replica, 'replay', $primary_lsn); + +# Test that the WAL receiver is up and running. +$node_replica->poll_query_until($test_db, qq[ + SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);] , 't'); + +# Set primary_conninfo to something invalid on the replica and reload the +# config. Once the config is reloaded, the startup process will force the WAL +# receiver to restart and it will be unable to reconnect because of the +# invalid connection information. +$node_replica->safe_psql($test_db, qq[ + ALTER SYSTEM SET primary_conninfo = ''; + SELECT pg_reload_conf(); + ]); + +# Wait until the WAL receiver has shut down and been unable to start up again. +$node_replica->poll_query_until($test_db, qq[ + SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);] , 'f'); + +# Now insert and update a tuple which will be visible to the vacuum on the +# primary but which will have xmax newer than the oldest xmin on the standby +# that was recently disconnected. +my $res = $psql_primaryA->query_safe( + qq[ + INSERT INTO $table1 VALUES (99); + UPDATE $table1 SET col1 = 100 WHERE col1 = 99; + SELECT 'after_update'; + ] + ); + +# Make sure the UPDATE finished +like($res, qr/^after_update$/m, "UPDATE occurred on primary session A"); + +# Open a cursor on the primary whose pin will keep VACUUM from getting a +# cleanup lock on the first page of the relation. We want VACUUM to be able to +# start, calculate initial values for OldestXmin and GlobalVisState and then +# be unable to proceed with pruning our dead tuples. This will allow us to +# reconnect the standby and push the horizon back before we start actual +# pruning and vacuuming. +my $primary_cursor1 = "vac_horizon_floor_cursor1"; + +# The first value inserted into the table was a 7, so FETCH FORWARD should +# return a 7. That's how we know the cursor has a pin. +# Disable index scans so the cursor pins heap pages and not index pages. +$res = $psql_primaryB->query_safe( + qq[ + BEGIN; + SET enable_bitmapscan = off; + SET enable_indexscan = off; + SET enable_indexonlyscan = off; + DECLARE $primary_cursor1 CURSOR FOR SELECT * FROM $table1 WHERE col1 = 7; + FETCH $primary_cursor1; + ] + ); + +is($res, 7, qq[Cursor query returned $res. Expected value 7.]); + +# Get the PID of the session which will run the VACUUM FREEZE so that we can +# use it to filter pg_stat_activity later. +my $vacuum_pid = $psql_primaryA->query_safe("SELECT pg_backend_pid();"); + +# Now start a VACUUM FREEZE on the primary. It will call vacuum_get_cutoffs() +# and establish values of OldestXmin and GlobalVisState which are newer than +# all of our dead tuples. Then it will be unable to get a cleanup lock to +# start pruning, so it will hang. +# +# We use VACUUM FREEZE because it will wait for a cleanup lock instead of +# skipping the page pinned by the cursor. Note that works because the target +# tuple's xmax precedes OldestXmin which ensures that lazy_scan_noprune() will +# return false and we will wait for the cleanup lock. +# +# Disable any prefetching, parallelism, or other concurrent I/O by vacuum. The +# pages of the heap must be processed in order by a single worker to ensure +# test stability (PARALLEL 0 shouldn't be necessary but guards against the +# possibility of parallel heap vacuuming). +$psql_primaryA->{stdin} .= qq[ + SET maintenance_io_concurrency = 0; + VACUUM (VERBOSE, FREEZE, PARALLEL 0) $table1; + \\echo VACUUM + ]; + +# Make sure the VACUUM command makes it to the server. +$psql_primaryA->{run}->pump_nb(); + +# Make sure that the VACUUM has already called vacuum_get_cutoffs() and is +# just waiting on the lock to start vacuuming. We don't want the standby to +# re-establish a connection to the primary and push the horizon back until +# we've saved initial values in GlobalVisState and calculated OldestXmin. +$node_primary->poll_query_until($test_db, + qq[ + SELECT count(*) >= 1 FROM pg_stat_activity + WHERE pid = $vacuum_pid + AND wait_event = 'BufferPin'; + ], + 't'); + +# Ensure the WAL receiver is still not active on the replica. +$node_replica->poll_query_until($test_db, qq[ + SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);] , 'f'); + +# Allow the WAL receiver connection to re-establish. +$node_replica->safe_psql( + $test_db, qq[ + ALTER SYSTEM SET primary_conninfo = '$orig_conninfo'; + SELECT pg_reload_conf(); + ]); + +# Ensure the new WAL receiver has connected. +$node_replica->poll_query_until($test_db, qq[ + SELECT EXISTS (SELECT * FROM pg_stat_wal_receiver);] , 't'); + +# Once the WAL sender is shown on the primary, the replica should have +# connected with the primary and pushed the horizon backward. Primary Session +# A won't see that until the VACUUM FREEZE proceeds and does its first round +# of index vacuuming. +$node_primary->poll_query_until($test_db, qq[ + SELECT EXISTS (SELECT * FROM pg_stat_replication);] , 't'); + +# Move the cursor forward to the next 7. We inserted the 7 much later, so +# advancing the cursor should allow vacuum to proceed vacuuming most pages of +# the relation. Because we set maintanence_work_mem sufficiently low, we +# expect that a round of index vacuuming has happened and that the vacuum is +# now waiting for the cursor to release its pin on the last page of the +# relation. +$res = $psql_primaryB->query_safe("FETCH $primary_cursor1"); +is($res, 7, + qq[Cursor query returned $res from second fetch. Expected value 7.]); + +# Prevent the test from incorrectly passing by confirming that we did indeed +# do a pass of index vacuuming. +$node_primary->poll_query_until($test_db, qq[ + SELECT index_vacuum_count > 0 + FROM pg_stat_progress_vacuum + WHERE datname='$test_db' AND relid::regclass = '$table1'::regclass; + ] , 't'); + +# Commit the transaction with the open cursor so that the VACUUM can finish. +$psql_primaryB->query_until( + qr/^commit$/m, + qq[ + COMMIT; + \\echo commit + ] + ); + +# VACUUM proceeds with pruning and does a visibility check on each tuple. In +# older versions of Postgres, pruning found our final dead tuple +# non-removable (HEAPTUPLE_RECENTLY_DEAD) since its xmax is after the new +# value of maybe_needed. Then heap_prepare_freeze_tuple() would decide the +# tuple xmax should be frozen because it precedes OldestXmin. Vacuum would +# then error out in heap_pre_freeze_checks() with "cannot freeze committed +# xmax". This was fixed by changing pruning to find all +# HEAPTUPLE_RECENTLY_DEAD tuples with xmaxes preceding OldestXmin +# HEAPTUPLE_DEAD and removing them. + +# With the fix, VACUUM should finish successfully, incrementing the table +# vacuum_count. +$node_primary->poll_query_until($test_db, + qq[ + SELECT vacuum_count > 0 + FROM pg_stat_all_tables WHERE relname = '${table1}'; + ] + , 't'); + +$primary_lsn = $node_primary->lsn('flush'); + +# Make sure something causes us to flush +$node_primary->safe_psql($test_db, "INSERT INTO $table1 VALUES (1);"); + +# Nothing on the replica should cause a recovery conflict, so this should +# finish successfully. +$node_primary->wait_for_catchup($node_replica, 'replay', $primary_lsn); + +## Shut down psqls +$psql_primaryA->quit; +$psql_primaryB->quit; + +$node_replica->stop(); +$node_primary->stop(); + +done_testing(); From c985a5e13bb9112a420dc98c7d72822a69733547 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 24 Jun 2025 14:14:04 -0400 Subject: [PATCH 685/796] Prevent excessive delays before launching new logrep workers. The logical replication launcher process would sometimes sleep for as much as 3 minutes before noticing that it is supposed to launch a new worker. This could happen if (1) WaitForReplicationWorkerAttach absorbed a process latch wakeup that was meant to cause ApplyLauncherMain to do work, or (2) logicalrep_worker_launch reported failure, either because of resource limits or because the new worker terminated immediately. In case (2), the expected behavior is that we retry the launch after wal_retrieve_retry_interval, but that didn't reliably happen. It's not clear how often such conditions would occur in the field, but in our subscription test suite they are somewhat common, especially in tests that exercise cases that cause quick worker failure. That causes the tests to take substantially longer than they ought to do on typical setups. To fix (1), make WaitForReplicationWorkerAttach re-set the latch before returning if it cleared it while looping. To fix (2), ensure that we reduce wait_time to no more than wal_retrieve_retry_interval when logicalrep_worker_launch reports failure. In passing, fix a couple of perhaps-hypothetical race conditions, e.g. examining worker->in_use without a lock. Backpatch to v16. Problem (2) didn't exist before commit 5a3a95385 because the previous code always set wait_time to wal_retrieve_retry_interval when launching a worker, regardless of success or failure of the launch. That behavior also greatly mitigated problem (1), so I'm not excited about adapting the remainder of the patch to the substantially-different code in older branches. Author: Tom Lane Reviewed-by: Amit Kapila Reviewed-by: Ashutosh Bapat Discussion: https://postgr.es/m/817604.1750723007@sss.pgh.pa.us Backpatch-through: 16 --- src/backend/replication/logical/launcher.c | 40 ++++++++++++++++----- src/backend/replication/logical/tablesync.c | 19 ++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index 27c3a91fb75ea..294d0d74d8c6b 100644 --- a/src/backend/replication/logical/launcher.c +++ b/src/backend/replication/logical/launcher.c @@ -184,12 +184,14 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker, uint16 generation, BackgroundWorkerHandle *handle) { - BgwHandleStatus status; - int rc; + bool result = false; + bool dropped_latch = false; for (;;) { + BgwHandleStatus status; pid_t pid; + int rc; CHECK_FOR_INTERRUPTS(); @@ -198,8 +200,9 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker, /* Worker either died or has started. Return false if died. */ if (!worker->in_use || worker->proc) { + result = worker->in_use; LWLockRelease(LogicalRepWorkerLock); - return worker->in_use; + break; } LWLockRelease(LogicalRepWorkerLock); @@ -214,7 +217,7 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker, if (generation == worker->generation) logicalrep_worker_cleanup(worker); LWLockRelease(LogicalRepWorkerLock); - return false; + break; /* result is already false */ } /* @@ -229,8 +232,18 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker, { ResetLatch(MyLatch); CHECK_FOR_INTERRUPTS(); + dropped_latch = true; } } + + /* + * If we had to clear a latch event in order to wait, be sure to restore + * it before exiting. Otherwise caller may miss events. + */ + if (dropped_latch) + SetLatch(MyLatch); + + return result; } /* @@ -1197,10 +1210,21 @@ ApplyLauncherMain(Datum main_arg) (elapsed = TimestampDifferenceMilliseconds(last_start, now)) >= wal_retrieve_retry_interval) { ApplyLauncherSetWorkerStartTime(sub->oid, now); - logicalrep_worker_launch(WORKERTYPE_APPLY, - sub->dbid, sub->oid, sub->name, - sub->owner, InvalidOid, - DSM_HANDLE_INVALID); + if (!logicalrep_worker_launch(WORKERTYPE_APPLY, + sub->dbid, sub->oid, sub->name, + sub->owner, InvalidOid, + DSM_HANDLE_INVALID)) + { + /* + * We get here either if we failed to launch a worker + * (perhaps for resource-exhaustion reasons) or if we + * launched one but it immediately quit. Either way, it + * seems appropriate to try again after + * wal_retrieve_retry_interval. + */ + wait_time = Min(wait_time, + wal_retrieve_retry_interval); + } } else { diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index b00267f042708..c8893ffad965a 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -604,14 +604,19 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) TimestampDifferenceExceeds(hentry->last_start_time, now, wal_retrieve_retry_interval)) { - logicalrep_worker_launch(WORKERTYPE_TABLESYNC, - MyLogicalRepWorker->dbid, - MySubscription->oid, - MySubscription->name, - MyLogicalRepWorker->userid, - rstate->relid, - DSM_HANDLE_INVALID); + /* + * Set the last_start_time even if we fail to start + * the worker, so that we won't retry until + * wal_retrieve_retry_interval has elapsed. + */ hentry->last_start_time = now; + (void) logicalrep_worker_launch(WORKERTYPE_TABLESYNC, + MyLogicalRepWorker->dbid, + MySubscription->oid, + MySubscription->name, + MyLogicalRepWorker->userid, + rstate->relid, + DSM_HANDLE_INVALID); } } } From 9adbeea0f48335f1ba2ed7822fe3607a2e1ef9a2 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 25 Jun 2025 10:03:50 +0900 Subject: [PATCH 686/796] Avoid scribbling of VACUUM options This fixes two issues with the handling of VacuumParams in vacuum_rel(). This code path has the idea to change the passed-in pointer of VacuumParams for the "truncate" and "index_cleanup" options for the relation worked on, impacting the two following scenarios where incorrect options may be used because a VacuumParams pointer is shared across multiple relations: - Multiple relations in a single VACUUM command. - TOAST relations vacuumed with their main relation. The problem is avoided by providing to the two callers of vacuum_rel() copies of VacuumParams, before the pointer is updated for the "truncate" and "index_cleanup" options. The refactoring of the VACUUM option and parameters done in 0d831389749a did not introduce an issue, but it has encouraged the problem we are dealing with in this commit, with b84dbc8eb80b for "truncate" and a96c41feec6b for "index_cleanup" that have been added a couple of years after the initial refactoring. HEAD will be improved with a different patch that hardens the uses of VacuumParams across the tree. This cannot be backpatched as it introduces an ABI breakage. The backend portion of the patch has been authored by Nathan, while I have implemented the tests. The tests rely on injection points to check the option values, making them faster, more reliable than the tests originally proposed by Shihao, and they also provide more coverage. This part can only be backpatched down to v17. Reported-by: Shihao Zhong Author: Nathan Bossart Co-authored-by: Michael Paquier Discussion: https://postgr.es/m/CAGRkXqTo+aK=GTy5pSc-9cy8H2F2TJvcrZ-zXEiNJj93np1UUw@mail.gmail.com Backpatch-through: 13 --- src/backend/commands/vacuum.c | 39 ++++++- src/test/modules/injection_points/Makefile | 2 +- .../injection_points/expected/vacuum.out | 103 ++++++++++++++++++ src/test/modules/injection_points/meson.build | 1 + .../modules/injection_points/sql/vacuum.sql | 35 ++++++ 5 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 src/test/modules/injection_points/expected/vacuum.out create mode 100644 src/test/modules/injection_points/sql/vacuum.sql diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 9f3af7a22c82e..a2132ecedaf85 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -56,6 +56,7 @@ #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/guc_hooks.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/syscache.h" @@ -619,7 +620,15 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, if (params->options & VACOPT_VACUUM) { - if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy)) + VacuumParams params_copy; + + /* + * vacuum_rel() scribbles on the parameters, so give it a copy + * to avoid affecting other relations. + */ + memcpy(¶ms_copy, params, sizeof(VacuumParams)); + + if (!vacuum_rel(vrel->oid, vrel->relation, ¶ms_copy, bstrategy)) continue; } @@ -1972,9 +1981,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, Oid save_userid; int save_sec_context; int save_nestlevel; + VacuumParams toast_vacuum_params; Assert(params != NULL); + /* + * This function scribbles on the parameters, so make a copy early to + * avoid affecting the TOAST table (if we do end up recursing to it). + */ + memcpy(&toast_vacuum_params, params, sizeof(VacuumParams)); + /* Begin a transaction for vacuuming this relation */ StartTransactionCommand(); @@ -2155,6 +2171,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, } } +#ifdef USE_INJECTION_POINTS + if (params->index_cleanup == VACOPTVALUE_AUTO) + INJECTION_POINT("vacuum-index-cleanup-auto"); + else if (params->index_cleanup == VACOPTVALUE_DISABLED) + INJECTION_POINT("vacuum-index-cleanup-disabled"); + else if (params->index_cleanup == VACOPTVALUE_ENABLED) + INJECTION_POINT("vacuum-index-cleanup-enabled"); +#endif + /* * Set truncate option based on truncate reloption if it wasn't specified * in VACUUM command, or when running in an autovacuum worker @@ -2168,6 +2193,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, params->truncate = VACOPTVALUE_DISABLED; } +#ifdef USE_INJECTION_POINTS + if (params->truncate == VACOPTVALUE_AUTO) + INJECTION_POINT("vacuum-truncate-auto"); + else if (params->truncate == VACOPTVALUE_DISABLED) + INJECTION_POINT("vacuum-truncate-disabled"); + else if (params->truncate == VACOPTVALUE_ENABLED) + INJECTION_POINT("vacuum-truncate-enabled"); +#endif + /* * Remember the relation's TOAST relation for later, if the caller asked * us to process it. In VACUUM FULL, though, the toast table is @@ -2247,15 +2281,12 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, */ if (toast_relid != InvalidOid) { - VacuumParams toast_vacuum_params; - /* * Force VACOPT_PROCESS_MAIN so vacuum_rel() processes it. Likewise, * set toast_parent so that the privilege checks are done on the main * relation. NB: This is only safe to do because we hold a session * lock on the main relation that prevents concurrent deletion. */ - memcpy(&toast_vacuum_params, params, sizeof(VacuumParams)); toast_vacuum_params.options |= VACOPT_PROCESS_MAIN; toast_vacuum_params.toast_parent = relid; diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index f19c9643ba968..84ff27c5c5672 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -9,7 +9,7 @@ EXTENSION = injection_points DATA = injection_points--1.0.sql PGFILEDESC = "injection_points - facility for injection points" -REGRESS = injection_points reindex_conc +REGRESS = injection_points reindex_conc vacuum REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress ISOLATION = inplace syscache-update-pruned diff --git a/src/test/modules/injection_points/expected/vacuum.out b/src/test/modules/injection_points/expected/vacuum.out new file mode 100644 index 0000000000000..f07152342d5ce --- /dev/null +++ b/src/test/modules/injection_points/expected/vacuum.out @@ -0,0 +1,103 @@ +-- Tests for VACUUM +CREATE EXTENSION injection_points; +SELECT injection_points_set_local(); + injection_points_set_local +---------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-index-cleanup-auto', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-index-cleanup-disabled', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-index-cleanup-enabled', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-truncate-auto', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-truncate-disabled', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_attach('vacuum-truncate-enabled', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +-- Check state of index_cleanup and truncate in VACUUM. +CREATE TABLE vac_tab_on_toast_off(i int, j text) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=true, toast.vacuum_index_cleanup=false, + vacuum_truncate=true, toast.vacuum_truncate=false); +CREATE TABLE vac_tab_off_toast_on(i int, j text) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=false, toast.vacuum_index_cleanup=true, + vacuum_truncate=false, toast.vacuum_truncate=true); +-- Multiple relations should use their options in isolation. +VACUUM vac_tab_on_toast_off, vac_tab_off_toast_on; +NOTICE: notice triggered for injection point vacuum-index-cleanup-enabled +NOTICE: notice triggered for injection point vacuum-truncate-enabled +NOTICE: notice triggered for injection point vacuum-index-cleanup-disabled +NOTICE: notice triggered for injection point vacuum-truncate-disabled +NOTICE: notice triggered for injection point vacuum-index-cleanup-disabled +NOTICE: notice triggered for injection point vacuum-truncate-disabled +NOTICE: notice triggered for injection point vacuum-index-cleanup-enabled +NOTICE: notice triggered for injection point vacuum-truncate-enabled +DROP TABLE vac_tab_on_toast_off; +DROP TABLE vac_tab_off_toast_on; +-- Cleanup +SELECT injection_points_detach('vacuum-index-cleanup-auto'); + injection_points_detach +------------------------- + +(1 row) + +SELECT injection_points_detach('vacuum-index-cleanup-disabled'); + injection_points_detach +------------------------- + +(1 row) + +SELECT injection_points_detach('vacuum-index-cleanup-enabled'); + injection_points_detach +------------------------- + +(1 row) + +SELECT injection_points_detach('vacuum-truncate-auto'); + injection_points_detach +------------------------- + +(1 row) + +SELECT injection_points_detach('vacuum-truncate-disabled'); + injection_points_detach +------------------------- + +(1 row) + +SELECT injection_points_detach('vacuum-truncate-enabled'); + injection_points_detach +------------------------- + +(1 row) + +DROP EXTENSION injection_points; diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index 169c415f9c478..c1892d760aa55 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -34,6 +34,7 @@ tests += { 'sql': [ 'injection_points', 'reindex_conc', + 'vacuum', ], 'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'], # The injection points are cluster-wide, so disable installcheck diff --git a/src/test/modules/injection_points/sql/vacuum.sql b/src/test/modules/injection_points/sql/vacuum.sql new file mode 100644 index 0000000000000..a799116b6cb6f --- /dev/null +++ b/src/test/modules/injection_points/sql/vacuum.sql @@ -0,0 +1,35 @@ +-- Tests for VACUUM + +CREATE EXTENSION injection_points; + +SELECT injection_points_set_local(); +SELECT injection_points_attach('vacuum-index-cleanup-auto', 'notice'); +SELECT injection_points_attach('vacuum-index-cleanup-disabled', 'notice'); +SELECT injection_points_attach('vacuum-index-cleanup-enabled', 'notice'); +SELECT injection_points_attach('vacuum-truncate-auto', 'notice'); +SELECT injection_points_attach('vacuum-truncate-disabled', 'notice'); +SELECT injection_points_attach('vacuum-truncate-enabled', 'notice'); + +-- Check state of index_cleanup and truncate in VACUUM. +CREATE TABLE vac_tab_on_toast_off(i int, j text) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=true, toast.vacuum_index_cleanup=false, + vacuum_truncate=true, toast.vacuum_truncate=false); +CREATE TABLE vac_tab_off_toast_on(i int, j text) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=false, toast.vacuum_index_cleanup=true, + vacuum_truncate=false, toast.vacuum_truncate=true); +-- Multiple relations should use their options in isolation. +VACUUM vac_tab_on_toast_off, vac_tab_off_toast_on; + +DROP TABLE vac_tab_on_toast_off; +DROP TABLE vac_tab_off_toast_on; + +-- Cleanup +SELECT injection_points_detach('vacuum-index-cleanup-auto'); +SELECT injection_points_detach('vacuum-index-cleanup-disabled'); +SELECT injection_points_detach('vacuum-index-cleanup-enabled'); +SELECT injection_points_detach('vacuum-truncate-auto'); +SELECT injection_points_detach('vacuum-truncate-disabled'); +SELECT injection_points_detach('vacuum-truncate-enabled'); +DROP EXTENSION injection_points; From 09f53712994b5132b8b8ad3f07c0e39d5b72d0f5 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Wed, 25 Jun 2025 10:07:03 +0530 Subject: [PATCH 687/796] Doc: Improve documentation of stream abort. Protocol v4 introduces parallel streaming, which allows Stream Abort messages to include additional abort information such as LSN and timestamp. However, the current documentation only states, "This field is available since protocol version 4," which may misleadingly suggest that the fields are always present when using protocol v4. This patch clarifies that the abort LSN and timestamp are included only when parallel streaming is enabled, even under protocol v4. Author: Anthonin Bonnefoy Reviewed-by: Amit Kapila Backpatch-through: 16, where it was introduced Discussion: https://postgr.es/m/CAO6_XqoKteQR1AnaR8iPcegbBE+HkAc2-g12rxN04yOt4-2ORg@mail.gmail.com --- doc/src/sgml/protocol.sgml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 0396d3a9e986f..1b1894e97511f 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -7052,8 +7052,8 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" Int64 (XLogRecPtr) - The LSN of the abort. This field is available since protocol version - 4. + The LSN of the abort operation, present only when streaming is set to parallel. + This field is available since protocol version 4. @@ -7062,9 +7062,9 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" Int64 (TimestampTz) - Abort timestamp of the transaction. The value is in number - of microseconds since PostgreSQL epoch (2000-01-01). This field is - available since protocol version 4. + Abort timestamp of the transaction, present only when streaming is set to + parallel. The value is in number of microseconds since PostgreSQL epoch (2000-01-01). + This field is available since protocol version 4. From 5c2b3ab9ae7bdc1a60d9da0a9263feab11ae4119 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 26 Jun 2025 09:37:39 +0100 Subject: [PATCH 688/796] doc: Fix indentation of MERGE synopsis. The convention in the documentation for other SQL commands is to indent continuation lines and sub-clauses in the "Synopsis" section by 4 spaces, so do the same for MERGE. Author: Dean Rasheed Reviewed-by: Nathan Bossart Discussion: https://postgr.es/m/CAEZATCV+9tR9+WM-SCcdBEZ3x7WVxUpADD5jX9WeGX97z4LCGA@mail.gmail.com Backpatch-through: 15 --- doc/src/sgml/ref/merge.sgml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/src/sgml/ref/merge.sgml b/doc/src/sgml/ref/merge.sgml index 97b34b9fcaf7a..5d49d91e67a8d 100644 --- a/doc/src/sgml/ref/merge.sgml +++ b/doc/src/sgml/ref/merge.sgml @@ -23,36 +23,36 @@ PostgreSQL documentation [ WITH with_query [, ...] ] MERGE INTO [ ONLY ] target_table_name [ * ] [ [ AS ] target_alias ] -USING data_source ON join_condition -when_clause [...] -[ RETURNING { * | output_expression [ [ AS ] output_name ] } [, ...] ] + USING data_source ON join_condition + when_clause [...] + [ RETURNING { * | output_expression [ [ AS ] output_name ] } [, ...] ] where data_source is: -{ [ ONLY ] source_table_name [ * ] | ( source_query ) } [ [ AS ] source_alias ] + { [ ONLY ] source_table_name [ * ] | ( source_query ) } [ [ AS ] source_alias ] and when_clause is: -{ WHEN MATCHED [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } | - WHEN NOT MATCHED BY SOURCE [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } | - WHEN NOT MATCHED [ BY TARGET ] [ AND condition ] THEN { merge_insert | DO NOTHING } } + { WHEN MATCHED [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } | + WHEN NOT MATCHED BY SOURCE [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } | + WHEN NOT MATCHED [ BY TARGET ] [ AND condition ] THEN { merge_insert | DO NOTHING } } and merge_insert is: -INSERT [( column_name [, ...] )] -[ OVERRIDING { SYSTEM | USER } VALUE ] -{ VALUES ( { expression | DEFAULT } [, ...] ) | DEFAULT VALUES } + INSERT [( column_name [, ...] )] + [ OVERRIDING { SYSTEM | USER } VALUE ] + { VALUES ( { expression | DEFAULT } [, ...] ) | DEFAULT VALUES } and merge_update is: -UPDATE SET { column_name = { expression | DEFAULT } | - ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) | - ( column_name [, ...] ) = ( sub-SELECT ) - } [, ...] + UPDATE SET { column_name = { expression | DEFAULT } | + ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) | + ( column_name [, ...] ) = ( sub-SELECT ) + } [, ...] and merge_delete is: -DELETE + DELETE From de3c54ddc53c8de7bd3535cad3828ccf1d85b4d2 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Thu, 26 Jun 2025 15:03:41 -0400 Subject: [PATCH 689/796] Remove unused check in heap_xlog_insert() 8e03eb92e9a reverted the commit 39b66a91bd which allowed freezing in the heap_insert() code path but forgot to remove the corresponding check in heap_xlog_insert(). This code is extraneous but not harmful. However, cleaning it up makes it very clear that, as of now, we do not support any freezing of pages in the heap_insert() path. Author: Melanie Plageman Reviewed-by: Tomas Vondra Discussion: https://postgr.es/m/flat/CAAKRu_Zp4Pi-t51OFWm1YZ-cctDfBhHCMZ%3DEx6PKxv0o8y2GvA%40mail.gmail.com Backpatch-through: 14 --- src/backend/access/heap/heapam.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index ccf151f548b46..cce38f482bd69 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -9613,6 +9613,9 @@ heap_xlog_insert(XLogReaderState *record) ItemPointerSetBlockNumber(&target_tid, blkno); ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum); + /* No freezing in the heap_insert() code path */ + Assert(!(xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)); + /* * The visibility map may need to be fixed even if the heap page is * already up-to-date. @@ -9683,10 +9686,6 @@ heap_xlog_insert(XLogReaderState *record) if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) PageClearAllVisible(page); - /* XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible */ - if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET) - PageSetAllVisible(page); - MarkBufferDirty(buffer); } if (BufferIsValid(buffer)) From ffbebc93b283fac0f22d34409ee46ee4270b5180 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 27 Jun 2025 13:37:26 -0500 Subject: [PATCH 690/796] Use correct DatumGet*() function in test_shm_mq_main(). This is purely cosmetic, as dsm_attach() interprets its argument as a dsm_handle (i.e., an unsigned integer), but we might as well fix it. Oversight in commit 4db3744f1f. Author: Jianghua Yang Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAAZLFmRxkUD5jRs0W3K%3DUe4_ZS%2BRcAb0PCE1S0vVJBn3sWH2UQ%40mail.gmail.com Backpatch-through: 13 --- src/test/modules/test_shm_mq/worker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c index 6c4fbc7827471..ff6e6712bb376 100644 --- a/src/test/modules/test_shm_mq/worker.c +++ b/src/test/modules/test_shm_mq/worker.c @@ -77,7 +77,7 @@ test_shm_mq_main(Datum main_arg) * exit, which is fine. If there were a ResourceOwner, it would acquire * ownership of the mapping, but we have no need for that. */ - seg = dsm_attach(DatumGetInt32(main_arg)); + seg = dsm_attach(DatumGetUInt32(main_arg)); if (seg == NULL) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), From 02c13cc7f159da9345f957110e9e4b9702593dce Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 29 Jun 2025 13:56:03 -0400 Subject: [PATCH 691/796] Obtain required table lock during cross-table constraint updates. Sometimes a table's constraint may depend on a column of another table, so that we have to update the constraint when changing the referenced column's type. We need to have lock on the constraint's table to do that. ATPostAlterTypeCleanup believed that this case was only possible for FOREIGN KEY constraints, but it's wrong at least for CHECK and EXCLUDE constraints; and in general, we'd probably need exclusive lock to alter any sort of constraint. So just remove the contype check and acquire lock for any other table. This prevents a "you don't have lock" assertion failure, though no ill effect is observed in production builds. We'll error out later anyway because we don't presently support physically altering column types within stored composite columns. But the catalog-munging is basically all there, so we may as well make that part work. Bug: #18970 Reported-by: Alexander Lakhin Diagnosed-by: jian he Author: Tom Lane Discussion: https://postgr.es/m/18970-a7d1cfe1f8d5d8d9@postgresql.org Backpatch-through: 13 --- src/backend/commands/tablecmds.c | 21 +++++++++++---------- src/test/regress/expected/alter_table.out | 7 +++++++ src/test/regress/sql/alter_table.sql | 9 +++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 06a0067f829fc..1dfa4d75ad9f2 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -13854,9 +13854,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) /* * Re-parse the index and constraint definitions, and attach them to the * appropriate work queue entries. We do this before dropping because in - * the case of a FOREIGN KEY constraint, we might not yet have exclusive - * lock on the table the constraint is attached to, and we need to get - * that before reparsing/dropping. + * the case of a constraint on another table, we might not yet have + * exclusive lock on the table the constraint is attached to, and we need + * to get that before reparsing/dropping. (That's possible at least for + * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it + * requires a dependency on the target table's composite type in the other + * table's constraint expressions.) * * We can't rely on the output of deparsing to tell us which relation to * operate on, because concurrent activity might have made the name @@ -13872,7 +13875,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) Form_pg_constraint con; Oid relid; Oid confrelid; - char contype; bool conislocal; tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId)); @@ -13889,7 +13891,6 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) elog(ERROR, "could not identify relation associated with constraint %u", oldId); } confrelid = con->confrelid; - contype = con->contype; conislocal = con->conislocal; ReleaseSysCache(tup); @@ -13906,12 +13907,12 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) continue; /* - * When rebuilding an FK constraint that references the table we're - * modifying, we might not yet have any lock on the FK's table, so get - * one now. We'll need AccessExclusiveLock for the DROP CONSTRAINT - * step, so there's no value in asking for anything weaker. + * When rebuilding another table's constraint that references the + * table we're modifying, we might not yet have any lock on the other + * table, so get one now. We'll need AccessExclusiveLock for the DROP + * CONSTRAINT step, so there's no value in asking for anything weaker. */ - if (relid != tab->relid && contype == CONSTRAINT_FOREIGN) + if (relid != tab->relid) LockRelationOid(relid, AccessExclusiveLock); ATPostAlterTypeParse(oldId, relid, confrelid, diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 6de74a26a95f2..9d90d5505a87a 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -4615,6 +4615,13 @@ alter table attbl alter column p1 set data type bigint; alter table atref alter column c1 set data type bigint; drop table attbl, atref; /* End test case for bug #17409 */ +/* Test case for bug #18970 */ +create table attbl(a int); +create table atref(b attbl check ((b).a is not null)); +alter table attbl alter column a type numeric; -- someday this should work +ERROR: cannot alter table "attbl" because column "atref.b" uses its row type +drop table attbl, atref; +/* End test case for bug #18970 */ -- Test that ALTER TABLE rewrite preserves a clustered index -- for normal indexes and indexes on constraints. create table alttype_cluster (a int); diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index da1272447304f..df08e93b70fe0 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -3027,6 +3027,15 @@ drop table attbl, atref; /* End test case for bug #17409 */ +/* Test case for bug #18970 */ + +create table attbl(a int); +create table atref(b attbl check ((b).a is not null)); +alter table attbl alter column a type numeric; -- someday this should work +drop table attbl, atref; + +/* End test case for bug #18970 */ + -- Test that ALTER TABLE rewrite preserves a clustered index -- for normal indexes and indexes on constraints. create table alttype_cluster (a int); From 3967a8acf3b5a04459d7f21a94a5199b6be25a8c Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Mon, 30 Jun 2025 10:12:31 +0200 Subject: [PATCH 692/796] doc: Fix typo in pg_sync_replication_slots documentation Commit 1546e17f9d0 accidentally misspelled additionally as additionaly. Backpatch to v17 to match where the original commit was backpatched. Author: Daniel Gustafsson Backpatch-through: 17 --- doc/src/sgml/func.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1b16914fa7e68..d7e9fe4478f0d 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29459,7 +29459,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset logical decoding and must be dropped after promotion. See for details. Note that this function is primarily intended for testing and - debugging purposes and should be used with caution. Additionaly, + debugging purposes and should be used with caution. Additionally, this function cannot be executed if sync_replication_slots is enabled and the slotsync From abc64a5da2f954b1339e1afd6c6e9baf44082e2e Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Tue, 1 Jul 2025 13:13:21 +0900 Subject: [PATCH 693/796] Fix typos in comments Commit 19d8e2308bc added enum values with the prefix TU_, but a few comments still referred to TUUI_, which was used in development versions of the patches committed as 19d8e2308bc. Author: Yugo Nagata Discussion: https://postgr.es/m/20250701110216.8ac8a9e4c6f607f1d954f44a@sraoss.co.jp Backpatch-through: 16 --- src/backend/executor/execIndexing.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 9f05b3654c186..dded5e793740a 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -268,7 +268,7 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) * executor is performing an UPDATE that could not use an * optimization like heapam's HOT (in more general terms a * call to table_tuple_update() took place and set - * 'update_indexes' to TUUI_All). Receiving this hint makes + * 'update_indexes' to TU_All). Receiving this hint makes * us consider if we should pass down the 'indexUnchanged' * hint in turn. That's something that we figure out for * each index_insert() call iff 'update' is true. @@ -279,7 +279,7 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) * HOT has been applied and any updated columns are indexed * only by summarizing indexes (or in more general terms a * call to table_tuple_update() took place and set - * 'update_indexes' to TUUI_Summarizing). We can (and must) + * 'update_indexes' to TU_Summarizing). We can (and must) * therefore only update the indexes that have * 'amsummarizing' = true. * From d6629c90415773d37992bff5c08689d1bde7a614 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Jul 2025 12:08:20 -0400 Subject: [PATCH 694/796] Make safeguard against incorrect flags for fsync more portable. The existing code assumed that O_RDONLY is defined as 0, but this is not required by POSIX and is not true on GNU Hurd. We can avoid the assumption by relying on O_ACCMODE to mask the fcntl() result. (Hopefully, all supported platforms define that.) Author: Michael Banck Co-authored-by: Samuel Thibault Reviewed-by: Tom Lane Discussion: https://postgr.es/m/6862e8d1.050a0220.194b8d.76fa@mx.google.com Discussion: https://postgr.es/m/68480868.5d0a0220.1e214d.68a6@mx.google.com Backpatch-through: 13 --- src/backend/storage/file/fd.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index a7c05b0a6fd86..66008275becf5 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -400,25 +400,22 @@ pg_fsync(int fd) * portable, even if it runs ok on the current system. * * We assert here that a descriptor for a file was opened with write - * permissions (either O_RDWR or O_WRONLY) and for a directory without - * write permissions (O_RDONLY). + * permissions (i.e., not O_RDONLY) and for a directory without write + * permissions (O_RDONLY). Notice that the assertion check is made even + * if fsync() is disabled. * - * Ignore any fstat errors and let the follow-up fsync() do its work. - * Doing this sanity check here counts for the case where fsync() is - * disabled. + * If fstat() fails, ignore it and let the follow-up fsync() complain. */ if (fstat(fd, &st) == 0) { int desc_flags = fcntl(fd, F_GETFL); - /* - * O_RDONLY is historically 0, so just make sure that for directories - * no write flags are used. - */ + desc_flags &= O_ACCMODE; + if (S_ISDIR(st.st_mode)) - Assert((desc_flags & (O_RDWR | O_WRONLY)) == 0); + Assert(desc_flags == O_RDONLY); else - Assert((desc_flags & (O_RDWR | O_WRONLY)) != 0); + Assert(desc_flags != O_RDONLY); } errno = 0; #endif From ed2a87541097692c5c5ce7230fefcbbb1129b7e6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Jul 2025 12:40:35 -0400 Subject: [PATCH 695/796] Make sure IOV_MAX is defined. We stopped defining IOV_MAX on non-Windows systems in 75357ab94, on the assumption that every non-Windows system defines it in as required by X/Open. GNU Hurd, however, doesn't follow that standard either. Put back the old logic to assume 16 if it's not defined. Author: Michael Banck Co-authored-by: Christoph Berg Reviewed-by: Tom Lane Discussion: https://postgr.es/m/6862e8d1.050a0220.194b8d.76fa@mx.google.com Discussion: https://postgr.es/m/6846e0c3.df0a0220.39ef9b.c60e@mx.google.com Backpatch-through: 16 --- src/include/port/pg_iovec.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/include/port/pg_iovec.h b/src/include/port/pg_iovec.h index e5fe677b3711e..10fecdd42ac37 100644 --- a/src/include/port/pg_iovec.h +++ b/src/include/port/pg_iovec.h @@ -21,9 +21,6 @@ #else -/* POSIX requires at least 16 as a maximum iovcnt. */ -#define IOV_MAX 16 - /* Define our own POSIX-compatible iovec struct. */ struct iovec { @@ -33,6 +30,15 @@ struct iovec #endif +/* + * If didn't define IOV_MAX, define our own. X/Open requires at + * least 16. (GNU Hurd apparently feel that they're not bound by X/Open, + * because they don't define this symbol at all.) + */ +#ifndef IOV_MAX +#define IOV_MAX 16 +#endif + /* Define a reasonable maximum that is safe to use on the stack. */ #define PG_IOV_MAX Min(IOV_MAX, 32) From f761ad48c1288cd0b8e86527c9eaefa8de1be560 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 1 Jul 2025 20:12:36 +0200 Subject: [PATCH 696/796] Fix outdated comment for IndexInfo Commit 78416235713 removed the ii_OpclassOptions field, but the comment was not updated. Author: Japin Li Reviewed-by: Richard Guo Discussion: https://www.postgresql.org/message-id/flat/ME0P300MB04453E6C7EA635F0ECF41BFCB6832%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM --- src/include/nodes/execnodes.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 17b0ec513864a..45c71c15e8d32 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -164,7 +164,6 @@ typedef struct ExprState * UniqueProcs * UniqueStrats * Unique is it a unique index? - * OpclassOptions opclass-specific options, or NULL if none * ReadyForInserts is it valid for inserts? * CheckedUnchanged IndexUnchanged status determined yet? * IndexUnchanged aminsert hint, cached for retail inserts From 5e5d03d5286c7149fdfaf09e9e44634c9784c7e8 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Tue, 1 Jul 2025 13:54:38 -0500 Subject: [PATCH 697/796] Document pg_get_multixact_members(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oversight in commit 0ac5ad5134. Author: Sami Imseih Co-authored-by: Álvaro Herrera Reviewed-by: Ashutosh Bapat Discussion: https://postgr.es/m/20150619215231.GT133018%40postgresql.org Discussion: https://postgr.es/m/CAA5RZ0sjQDDwJfMRb%3DZ13nDLuRpF13ME2L_BdGxi0op8RKjmDg%40mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/func.sgml | 28 +++++++++++++++++++++++++++- doc/src/sgml/maintenance.sgml | 5 ++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index d7e9fe4478f0d..9db19c5d4abcc 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -27184,6 +27184,31 @@ SELECT currval(pg_get_serial_sequence('sometable', 'id')); details. + + + + + pg_get_multixact_members + + pg_get_multixact_members ( multixid xid ) + setof record + ( xid xid, + mode text ) + + + Returns the transaction ID and lock mode for each member of the + specified multixact ID. The lock modes forupd, + fornokeyupd, sh, and + keysh correspond to the row-level locks + FOR UPDATE, FOR NO KEY UPDATE, + FOR SHARE, and FOR KEY SHARE, + respectively, as described in . Two + additional modes are specific to multixacts: + nokeyupd, used by updates that do not modify key + columns, and upd, used by updates or deletes that + modify key columns. + +
@@ -27192,7 +27217,8 @@ SELECT currval(pg_get_serial_sequence('sometable', 'id')); The internal transaction ID type xid is 32 bits wide and wraps around every 4 billion transactions. However, the functions shown in , except - age and mxid_age, use a + age, mxid_age, and + pg_get_multixact_members, use a 64-bit type xid8 that does not wrap around during the life of an installation and can be converted to xid by casting if required; see for details. diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 89040942be2fe..b81a5806eb6d3 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -761,7 +761,10 @@ HINT: Execute a database-wide VACUUM in that database. careful aging management, storage cleanup, and wraparound handling. There is a separate storage area which holds the list of members in each multixact, which also uses a 32-bit counter and which must also - be managed. + be managed. The system function + pg_get_multixact_members() described in + can be used to examine the + transaction IDs associated with a multixact ID.
From 7c9d2308fa884538582e21b68b6538b97f766ca0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 1 Jul 2025 22:15:26 +0200 Subject: [PATCH 698/796] Update comment for IndexInfo.ii_NullsNotDistinct Commit 7a7b3e11e61 added the ii_NullsNotDistinct field, but the comment was not updated. Author: Japin Li Reviewed-by: Richard Guo Discussion: https://www.postgresql.org/message-id/flat/ME0P300MB04453E6C7EA635F0ECF41BFCB6832%40ME0P300MB0445.AUSP300.PROD.OUTLOOK.COM --- src/include/nodes/execnodes.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 45c71c15e8d32..0696ec05b1629 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -164,6 +164,7 @@ typedef struct ExprState * UniqueProcs * UniqueStrats * Unique is it a unique index? + * NullsNotDistinct is NULLS NOT DISTINCT? * ReadyForInserts is it valid for inserts? * CheckedUnchanged IndexUnchanged status determined yet? * IndexUnchanged aminsert hint, cached for retail inserts From 71c61eabffcf14c62bc62e89adc5c06ebd134c6f Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 2 Jul 2025 13:48:43 +0900 Subject: [PATCH 699/796] Fix bug in archive streamer with LZ4 decompression When decompressing some input data, the calculation for the initial starting point and the initial size were incorrect, potentially leading to failures when decompressing contents with LZ4. These initialization points are fixed in this commit, bringing the logic closer to what exists for gzip and zstd. The contents of the compressed data is clear (for example backups taken with LZ4 can still be decompressed with a "lz4" command), only the decompression part reading the input data was impacted by this issue. This code path impacts pg_basebackup and pg_verifybackup, which can use the LZ4 decompression routines with an archive streamer, or any tools that try to use the archive streamers in src/fe_utils/. The issue is easier to reproduce with files that have a low-compression rate, like ones filled with random data, for a size of at least 512kB, but this could happen with anything as long as it is stored in a data folder. Some tests are added based on this idea, with a file filled with random bytes grabbed from the backend, written at the root of the data folder. This is proving good enough to reproduce the original problem. Author: Mikhail Gribkov Discussion: https://postgr.es/m/CAMEv5_uQS1Hg6KCaEP2JkrTBbZ-nXQhxomWrhYQvbdzR-zy-wA@mail.gmail.com Backpatch-through: 15 --- src/bin/pg_basebackup/bbstreamer_lz4.c | 4 +-- src/bin/pg_verifybackup/t/008_untar.pl | 24 ++++++++++++++++++ src/bin/pg_verifybackup/t/010_client_untar.pl | 25 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/bin/pg_basebackup/bbstreamer_lz4.c b/src/bin/pg_basebackup/bbstreamer_lz4.c index eda62caeded4d..96e74ae7649d0 100644 --- a/src/bin/pg_basebackup/bbstreamer_lz4.c +++ b/src/bin/pg_basebackup/bbstreamer_lz4.c @@ -320,9 +320,9 @@ bbstreamer_lz4_decompressor_content(bbstreamer *streamer, mystreamer = (bbstreamer_lz4_frame *) streamer; next_in = (uint8 *) data; - next_out = (uint8 *) mystreamer->base.bbs_buffer.data; + next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written; avail_in = len; - avail_out = mystreamer->base.bbs_buffer.maxlen; + avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; while (avail_in > 0) { diff --git a/src/bin/pg_verifybackup/t/008_untar.pl b/src/bin/pg_verifybackup/t/008_untar.pl index 7a09f3b75b2a7..7b589dd4bf13e 100644 --- a/src/bin/pg_verifybackup/t/008_untar.pl +++ b/src/bin/pg_verifybackup/t/008_untar.pl @@ -16,6 +16,22 @@ $primary->init(allows_streaming => 1); $primary->start; +# Create file with some random data and an arbitrary size, useful to check +# the solidity of the compression and decompression logic. The size of the +# file is chosen to be around 640kB. This has proven to be large enough to +# detect some issues related to LZ4, and low enough to not impact the runtime +# of the test significantly. +my $junk_data = $primary->safe_psql( + 'postgres', qq( + SELECT string_agg(encode(sha256(i::text::bytea), 'hex'), '') + FROM generate_series(1, 10240) s(i);)); +my $data_dir = $primary->data_dir; +my $junk_file = "$data_dir/junk"; +open my $jf, '>', $junk_file + or die "Could not create junk file: $!"; +print $jf $junk_data; +close $jf; + my $backup_path = $primary->backup_dir . '/server-backup'; my $extract_path = $primary->backup_dir . '/extracted-backup'; @@ -42,6 +58,14 @@ 'decompress_flags' => [ '-d', '-m' ], 'enabled' => check_pg_config("#define USE_LZ4 1") }, + { + 'compression_method' => 'lz4', + 'backup_flags' => [ '--compress', 'server-lz4:5' ], + 'backup_archive' => 'base.tar.lz4', + 'decompress_program' => $ENV{'LZ4'}, + 'decompress_flags' => [ '-d', '-m' ], + 'enabled' => check_pg_config("#define USE_LZ4 1") + }, { 'compression_method' => 'zstd', 'backup_flags' => [ '--compress', 'server-zstd' ], diff --git a/src/bin/pg_verifybackup/t/010_client_untar.pl b/src/bin/pg_verifybackup/t/010_client_untar.pl index 8c076d46dee00..dc26c296ad771 100644 --- a/src/bin/pg_verifybackup/t/010_client_untar.pl +++ b/src/bin/pg_verifybackup/t/010_client_untar.pl @@ -15,6 +15,22 @@ $primary->init(allows_streaming => 1); $primary->start; +# Create file with some random data and an arbitrary size, useful to check +# the solidity of the compression and decompression logic. The size of the +# file is chosen to be around 640kB. This has proven to be large enough to +# detect some issues related to LZ4, and low enough to not impact the runtime +# of the test significantly. +my $junk_data = $primary->safe_psql( + 'postgres', qq( + SELECT string_agg(encode(sha256(i::text::bytea), 'hex'), '') + FROM generate_series(1, 10240) s(i);)); +my $data_dir = $primary->data_dir; +my $junk_file = "$data_dir/junk"; +open my $jf, '>', $junk_file + or die "Could not create junk file: $!"; +print $jf $junk_data; +close $jf; + my $backup_path = $primary->backup_dir . '/client-backup'; my $extract_path = $primary->backup_dir . '/extracted-backup'; @@ -42,6 +58,15 @@ 'output_file' => 'base.tar', 'enabled' => check_pg_config("#define USE_LZ4 1") }, + { + 'compression_method' => 'lz4', + 'backup_flags' => [ '--compress', 'client-lz4:1' ], + 'backup_archive' => 'base.tar.lz4', + 'decompress_program' => $ENV{'LZ4'}, + 'decompress_flags' => ['-d'], + 'output_file' => 'base.tar', + 'enabled' => check_pg_config("#define USE_LZ4 1") + }, { 'compression_method' => 'zstd', 'backup_flags' => [ '--compress', 'client-zstd:5' ], From ce1bb135336c8ba55d307c6f77769c426198fa4e Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Tue, 1 Jul 2025 23:25:15 -0700 Subject: [PATCH 700/796] Fix missing FSM vacuum opportunities on tables without indexes. Commit c120550edb86 optimized the vacuuming of relations without indexes (a.k.a. one-pass strategy) by directly marking dead item IDs as LP_UNUSED. However, the periodic FSM vacuum was still checking if dead item IDs had been marked as LP_DEAD when attempting to vacuum the FSM every VACUUM_FSM_EVERY_PAGES blocks. This condition was never met due to the optimization, resulting in missed FSM vacuum opportunities. This commit modifies the periodic FSM vacuum condition to use the number of tuples deleted during HOT pruning. This count includes items marked as either LP_UNUSED or LP_REDIRECT, both of which are expected to result in new free space to report. Back-patch to v17 where the vacuum optimization for tables with no indexes was introduced. Reviewed-by: Melanie Plageman Discussion: https://postgr.es/m/CAD21AoBL8m6B9GSzQfYxVaEgvD7-Kr3AJaS-hJPHC+avm-29zw@mail.gmail.com Backpatch-through: 17 --- src/backend/access/heap/vacuumlazy.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index b22604e96003b..ef03d1acc545b 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -235,7 +235,7 @@ static void find_next_unskippable_block(LVRelState *vacrel, bool *skipsallvis); static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, bool sharelock, Buffer vmbuffer); -static void lazy_scan_prune(LVRelState *vacrel, Buffer buf, +static int lazy_scan_prune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, Buffer vmbuffer, bool all_visible_according_to_vm, bool *has_lpdead_items); @@ -844,6 +844,7 @@ lazy_scan_heap(LVRelState *vacrel) { Buffer buf; Page page; + int ndeleted = 0; bool has_lpdead_items; bool got_cleanup_lock = false; @@ -973,9 +974,9 @@ lazy_scan_heap(LVRelState *vacrel) * line pointers previously marked LP_DEAD. */ if (got_cleanup_lock) - lazy_scan_prune(vacrel, buf, blkno, page, - vmbuffer, all_visible_according_to_vm, - &has_lpdead_items); + ndeleted = lazy_scan_prune(vacrel, buf, blkno, page, + vmbuffer, all_visible_according_to_vm, + &has_lpdead_items); /* * Now drop the buffer lock and, potentially, update the FSM. @@ -1011,7 +1012,7 @@ lazy_scan_heap(LVRelState *vacrel) * table has indexes. There will only be newly-freed space if we * held the cleanup lock and lazy_scan_prune() was called. */ - if (got_cleanup_lock && vacrel->nindexes == 0 && has_lpdead_items && + if (got_cleanup_lock && vacrel->nindexes == 0 && ndeleted > 0 && blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES) { FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum, @@ -1402,8 +1403,10 @@ cmpOffsetNumbers(const void *a, const void *b) * * *has_lpdead_items is set to true or false depending on whether, upon return * from this function, any LP_DEAD items are still present on the page. + * + * Returns the number of tuples deleted from the page during HOT pruning. */ -static void +static int lazy_scan_prune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, @@ -1623,6 +1626,8 @@ lazy_scan_prune(LVRelState *vacrel, VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN); } + + return presult.ndeleted; } /* From a7e214f319d4686cb71249e9ecc56d9592d771a1 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 2 Jul 2025 15:47:59 -0400 Subject: [PATCH 701/796] Correctly copy the target host identification in PQcancelCreate. PQcancelCreate failed to copy struct pg_conn_host's "type" field, instead leaving it zero (a/k/a CHT_HOST_NAME). This seemingly has no great ill effects if it should have been CHT_UNIX_SOCKET instead, but if it should have been CHT_HOST_ADDRESS then a null-pointer dereference will occur when the cancelConn is used. Bug: #18974 Reported-by: Maxim Boguk Author: Sergei Kornilov Reviewed-by: Tom Lane Discussion: https://postgr.es/m/18974-575f02b2168b36b3@postgresql.org Backpatch-through: 17 --- src/interfaces/libpq/fe-cancel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interfaces/libpq/fe-cancel.c b/src/interfaces/libpq/fe-cancel.c index 213a6f43c2d76..8c7aff42b5a54 100644 --- a/src/interfaces/libpq/fe-cancel.c +++ b/src/interfaces/libpq/fe-cancel.c @@ -119,6 +119,7 @@ PQcancelCreate(PGconn *conn) goto oom_error; originalHost = conn->connhost[conn->whichhost]; + cancelConn->connhost[0].type = originalHost.type; if (originalHost.host) { cancelConn->connhost[0].host = strdup(originalHost.host); From 3d1f4d647dddcb4fed3b8fc8ecebcc4769430b3d Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 3 Jul 2025 16:03:19 +0900 Subject: [PATCH 702/796] doc: Remove incorrect note about wal_status in pg_replication_slots. The documentation previously stated that the wal_status column is NULL if restart_lsn is NULL in the pg_replication_slots view. This is incorrect, and wal_status can be "lost" even when restart_lsn is NULL. This commit removes the incorrect description. Back-patched to all supported versions. Author: Fujii Masao Reviewed-by: Nisha Moond Discussion: https://postgr.es/m/c9d23cdc-b5dd-455a-8ee9-f1f24d701d89@oss.nttdata.com Backpatch-through: 13 --- doc/src/sgml/system-views.sgml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index f36a89cc74aea..7b36190145a72 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -2495,8 +2495,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx The last two states are seen only when is - non-negative. If restart_lsn is NULL, this - field is null. + non-negative. From e199289c1c2d14d848e8a8b6fd31134eda388873 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Thu, 3 Jul 2025 23:07:23 +0900 Subject: [PATCH 703/796] doc: Update outdated descriptions of wal_status in pg_replication_slots. The documentation for pg_replication_slots previously mentioned only max_slot_wal_keep_size as a condition under which the wal_status column could show unreserved or lost. However, since commit be87200, replication slots can also be invalidated due to horizon or wal_level, and since commit ac0e33136ab, idle_replication_slot_timeout can also trigger this state. This commit updates the description of the wal_status column to reflect that max_slot_wal_keep_size is not the only cause of the lost state. Back-patched to v16, where the additional invalidation cases were introduced. Author: Fujii Masao Reviewed-by: Hayato Kuroda Reviewed-by: Nisha Moond Discussion: https://postgr.es/m/78b34e84-2195-4f28-a151-5d204a382fdd@oss.nttdata.com Backpatch-through: 16 --- doc/src/sgml/system-views.sgml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 7b36190145a72..560e95caec4c9 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -2482,20 +2482,18 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx unreserved means that the slot no longer retains the required WAL files and some of them are to be removed at - the next checkpoint. This state can return + the next checkpoint. This typically occurs when + is set to + a non-negative value. This state can return to reserved or extended.
- lost means that some required WAL files have - been removed and this slot is no longer usable. + lost means that this slot is no longer usable. - The last two states are seen only when - is - non-negative.
From fe6436ab3e56487056ed2ba2225cf56872a15614 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 3 Jul 2025 13:46:07 -0400 Subject: [PATCH 704/796] Obtain required table lock during cross-table updates, redux. Commits 8319e5cb5 et al missed the fact that ATPostAlterTypeCleanup contains three calls to ATPostAlterTypeParse, and the other two also need protection against passing a relid that we don't yet have lock on. Add similar logic to those code paths, and add some test cases demonstrating the need for it. In v18 and master, the test cases demonstrate that there's a behavioral discrepancy between stored generated columns and virtual generated columns: we disallow changing the expression of a stored column if it's used in any composite-type columns, but not that of a virtual column. Since the expression isn't actually relevant to either sort of composite-type usage, this prohibition seems unnecessary; but changing it is a matter for separate discussion. For now we are just documenting the existing behavior. Reported-by: jian he Author: jian he Reviewed-by: Tom Lane Discussion: CACJufxGKJtGNRRSXfwMW9SqVOPEMdP17BJ7DsBf=tNsv9pWU9g@mail.gmail.com Backpatch-through: 13 --- src/backend/commands/tablecmds.c | 22 ++++++++++++++++++++ src/test/regress/expected/alter_table.out | 8 ++++++++ src/test/regress/expected/generated.out | 24 ++++++++++++++++++++++ src/test/regress/sql/alter_table.sql | 8 ++++++++ src/test/regress/sql/generated.sql | 25 +++++++++++++++++++++++ 5 files changed, 87 insertions(+) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1dfa4d75ad9f2..2ab9d90fb8ac9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -13926,6 +13926,14 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) Oid relid; relid = IndexGetRelation(oldId, false); + + /* + * As above, make sure we have lock on the index's table if it's not + * the same table. + */ + if (relid != tab->relid) + LockRelationOid(relid, AccessExclusiveLock); + ATPostAlterTypeParse(oldId, relid, InvalidOid, (char *) lfirst(def_item), wqueue, lockmode, tab->rewrite); @@ -13942,6 +13950,20 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) Oid relid; relid = StatisticsGetRelation(oldId, false); + + /* + * As above, make sure we have lock on the statistics object's table + * if it's not the same table. However, we take + * ShareUpdateExclusiveLock here, aligning with the lock level used in + * CreateStatistics and RemoveStatisticsById. + * + * CAUTION: this should be done after all cases that grab + * AccessExclusiveLock, else we risk causing deadlock due to needing + * to promote our table lock. + */ + if (relid != tab->relid) + LockRelationOid(relid, ShareUpdateExclusiveLock); + ATPostAlterTypeParse(oldId, relid, InvalidOid, (char *) lfirst(def_item), wqueue, lockmode, tab->rewrite); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 9d90d5505a87a..7cebd50a673c2 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -4620,6 +4620,14 @@ create table attbl(a int); create table atref(b attbl check ((b).a is not null)); alter table attbl alter column a type numeric; -- someday this should work ERROR: cannot alter table "attbl" because column "atref.b" uses its row type +alter table atref drop constraint atref_b_check; +create statistics atref_stat on ((b).a is not null) from atref; +alter table attbl alter column a type numeric; -- someday this should work +ERROR: cannot alter table "attbl" because column "atref.b" uses its row type +drop statistics atref_stat; +create index atref_idx on atref (((b).a)); +alter table attbl alter column a type numeric; -- someday this should work +ERROR: cannot alter table "attbl" because column "atref.b" uses its row type drop table attbl, atref; /* End test case for bug #18970 */ -- Test that ALTER TABLE rewrite preserves a clustered index diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index a1f67abb688cf..f7c867e054c14 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -1190,6 +1190,30 @@ Inherits: gtest30 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: cannot drop generation expression from inherited column +-- composite type dependencies +CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text); +CREATE TABLE gtest31_2 (x int, y gtest31_1); +ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +-- bug #18970: these cases are unsupported, but make sure they fail cleanly +ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +ALTER TABLE gtest31_2 DROP CONSTRAINT cc; +CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2'); +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +DROP STATISTICS gtest31_2_stat; +CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b)); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3'); +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +DROP TABLE gtest31_1, gtest31_2; +-- Check it for a partitioned table, too +CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a); +CREATE TABLE gtest31_2 (x int, y gtest31_1); +ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +DROP TABLE gtest31_1, gtest31_2; -- triggers CREATE TABLE gtest26 ( a int PRIMARY KEY, diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index df08e93b70fe0..16ed0782fa173 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -3032,6 +3032,14 @@ drop table attbl, atref; create table attbl(a int); create table atref(b attbl check ((b).a is not null)); alter table attbl alter column a type numeric; -- someday this should work +alter table atref drop constraint atref_b_check; + +create statistics atref_stat on ((b).a is not null) from atref; +alter table attbl alter column a type numeric; -- someday this should work +drop statistics atref_stat; + +create index atref_idx on atref (((b).a)); +alter table attbl alter column a type numeric; -- someday this should work drop table attbl, atref; /* End test case for bug #18970 */ diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index ba59325da87ee..2816ac4a74ab4 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -550,6 +550,31 @@ ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error \d gtest30_1 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error +-- composite type dependencies +CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text); +CREATE TABLE gtest31_2 (x int, y gtest31_1); +ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails + +-- bug #18970: these cases are unsupported, but make sure they fail cleanly +ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); +ALTER TABLE gtest31_2 DROP CONSTRAINT cc; + +CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2'); +DROP STATISTICS gtest31_2_stat; + +CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b)); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3'); + +DROP TABLE gtest31_1, gtest31_2; + +-- Check it for a partitioned table, too +CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a); +CREATE TABLE gtest31_2 (x int, y gtest31_1); +ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails +DROP TABLE gtest31_1, gtest31_2; + -- triggers CREATE TABLE gtest26 ( a int PRIMARY KEY, From 0da82443b9400dac515eb09a440f10dc8e2685e9 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 4 Jul 2025 15:10:19 +0900 Subject: [PATCH 705/796] Disable commit timestamps during bootstrap Attempting to use commit timestamps during bootstrapping leads to an assertion failure, that can be reached for example with an initdb -c that enables track_commit_timestamp. It makes little sense to register a commit timestamp for a BootstrapTransactionId, so let's disable the activation of the module in this case. This problem has been independently reported once by each author of this commit. Each author has proposed basically the same patch, relying on IsBootstrapProcessingMode() to skip the use of commit_ts during bootstrap. The test addition is a suggestion by me, and is applied down to v16. Author: Hayato Kuroda Author: Andy Fan Reviewed-by: Bertrand Drouvot Reviewed-by: Fujii Masao Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/OSCPR01MB14966FF9E4C4145F37B937E52F5102@OSCPR01MB14966.jpnprd01.prod.outlook.com Discussion: https://postgr.es/m/87plejmnpy.fsf@163.com Backpatch-through: 13 --- src/backend/access/transam/commit_ts.c | 7 +++++++ src/test/modules/commit_ts/t/001_base.pl | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c index 77e1899d7ad21..86a295ca9b195 100644 --- a/src/backend/access/transam/commit_ts.c +++ b/src/backend/access/transam/commit_ts.c @@ -707,6 +707,13 @@ ActivateCommitTs(void) TransactionId xid; int64 pageno; + /* + * During bootstrap, we should not register commit timestamps so skip the + * activation in this case. + */ + if (IsBootstrapProcessingMode()) + return; + /* If we've done this already, there's nothing to do */ LWLockAcquire(CommitTsLock, LW_EXCLUSIVE); if (commitTsShared->commitTsActive) diff --git a/src/test/modules/commit_ts/t/001_base.pl b/src/test/modules/commit_ts/t/001_base.pl index e608ec67704af..638e3f15cec9d 100644 --- a/src/test/modules/commit_ts/t/001_base.pl +++ b/src/test/modules/commit_ts/t/001_base.pl @@ -11,8 +11,7 @@ use PostgreSQL::Test::Cluster; my $node = PostgreSQL::Test::Cluster->new('foxtrot'); -$node->init; -$node->append_conf('postgresql.conf', 'track_commit_timestamp = on'); +$node->init(extra => [ '-c', "track_commit_timestamp=on" ]); $node->start; # Create a table, compare "now()" to the commit TS of its xmin From f5876ad9f68fb69edd912ae40104d928be6ddd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 4 Jul 2025 18:05:43 +0200 Subject: [PATCH 706/796] pg_upgrade: check for inconsistencies in not-null constraints w/inheritance With tables defined like this, CREATE TABLE ip (id int PRIMARY KEY); CREATE TABLE ic (id int) INHERITS (ip); ALTER TABLE ic ALTER id DROP NOT NULL; pg_upgrade fails during the schema restore phase due to this error: ERROR: column "id" in child table must be marked NOT NULL This can only be fixed by marking the child column as NOT NULL before the upgrade, which could take an arbitrary amount of time (because ic's data must be scanned). Have pg_upgrade's check mode warn if that condition is found, so that users know what to adjust before running the upgrade for real. Author: Ali Akbar Reviewed-by: Justin Pryzby Backpatch-through: 13 Discussion: https://postgr.es/m/CACQjQLoMsE+1pyLe98pi0KvPG2jQQ94LWJ+PTiLgVRK4B=i_jg@mail.gmail.com --- src/bin/pg_upgrade/check.c | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index 1cf84cc1bb5e3..070f6ec21ed01 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -24,6 +24,7 @@ static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster); static void check_for_user_defined_postfix_ops(ClusterInfo *cluster); static void check_for_incompatible_polymorphics(ClusterInfo *cluster); static void check_for_tables_with_oids(ClusterInfo *cluster); +static void check_for_not_null_inheritance(ClusterInfo *cluster); static void check_for_pg_role_prefix(ClusterInfo *cluster); static void check_for_new_tablespace_dir(void); static void check_for_user_defined_encoding_conversions(ClusterInfo *cluster); @@ -648,6 +649,13 @@ check_and_dump_old_cluster(bool live_check) if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1100) check_for_tables_with_oids(&old_cluster); + /* + * Pre-PG 18 allowed child tables to omit not-null constraints that their + * parents columns have, but schema restore fails for them. Verify there + * are none. + */ + check_for_not_null_inheritance(&old_cluster); + /* * Pre-PG 10 allowed tables with 'unknown' type columns and non WAL logged * hash indexes @@ -1576,6 +1584,84 @@ check_for_tables_with_oids(ClusterInfo *cluster) check_ok(); } +/* + * check_for_not_null_inheritance() + * + * An attempt to create child tables lacking not-null constraints that are + * present in their parents errors out. This can no longer occur since 18, + * but previously there were various ways for that to happen. Check that + * the cluster to be upgraded doesn't have any of those problems. + */ +static void +check_for_not_null_inheritance(ClusterInfo *cluster) +{ + FILE *script = NULL; + char output_path[MAXPGPATH]; + int ntup; + + prep_status("Checking for not-null constraint inconsistencies"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "not_null_inconsistent_columns.txt"); + for (int dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int i_nspname, + i_relname, + i_attname; + DbInfo *active_db = &old_cluster.dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(&old_cluster, active_db->db_name); + + res = executeQueryOrDie(conn, + "SELECT cc.relnamespace::pg_catalog.regnamespace AS nspname, " + " cc.relname, ac.attname " + "FROM pg_catalog.pg_inherits i, pg_catalog.pg_attribute ac, " + " pg_catalog.pg_attribute ap, pg_catalog.pg_class cc " + "WHERE cc.oid = ac.attrelid AND i.inhrelid = ac.attrelid " + " AND i.inhparent = ap.attrelid AND ac.attname = ap.attname " + " AND ap.attnum > 0 and ap.attnotnull AND NOT ac.attnotnull"); + + ntup = PQntuples(res); + i_nspname = PQfnumber(res, "nspname"); + i_relname = PQfnumber(res, "relname"); + i_attname = PQfnumber(res, "attname"); + for (int i = 0; i < ntup; i++) + { + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %m", output_path); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + + fprintf(script, " %s.%s.%s\n", + PQgetvalue(res, i, i_nspname), + PQgetvalue(res, i, i_relname), + PQgetvalue(res, i, i_attname)); + } + + PQclear(res); + PQfinish(conn); + } + + if (script) + { + fclose(script); + pg_log(PG_REPORT, "fatal"); + pg_fatal("Your installation contains inconsistent NOT NULL constraints.\n" + "If the parent column(s) are NOT NULL, then the child column must\n" + "also be marked NOT NULL, or the upgrade will fail.\n" + "You can fix this by running\n" + " ALTER TABLE tablename ALTER column SET NOT NULL;\n" + "on each column listed in the file:\n" + " %s", output_path); + } + else + check_ok(); +} /* * check_for_pg_role_prefix() From 33c457fd9e9e06b2e0e092877f99ada155cb553d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 4 Jul 2025 16:07:11 +0200 Subject: [PATCH 707/796] pg_upgrade: Add missing newline in error message Minor oversight in 347758b12063 --- src/bin/pg_upgrade/check.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index 070f6ec21ed01..290fdae7c9b17 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -479,7 +479,7 @@ check_for_data_types_usage(ClusterInfo *cluster, DataTypesUsageChecks *checks) if (!results[checknum]) { pg_log(PG_REPORT, "failed check: %s", _(cur_check->status)); - appendPQExpBuffer(&report, "\n%s\n%s %s\n", + appendPQExpBuffer(&report, "\n%s\n%s\n %s\n", _(cur_check->report_text), _("A list of the problem columns is in the file:"), output_path); From c758299b84a5dba8bd94bae9576d3243533db032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Fri, 4 Jul 2025 21:30:05 +0200 Subject: [PATCH 708/796] Fix new pg_upgrade query not to rely on regnamespace That was invented in 9.5, and pg_upgrade claims to support back to 9.0. But we don't need that with a simple query change, tested by Tom Lane. Discussion: https://postgr.es/m/202507041645.afjl5rssvrgu@alvherre.pgsql --- src/bin/pg_upgrade/check.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index 290fdae7c9b17..c2da473353c07 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -1615,12 +1615,13 @@ check_for_not_null_inheritance(ClusterInfo *cluster) PGconn *conn = connectToServer(&old_cluster, active_db->db_name); res = executeQueryOrDie(conn, - "SELECT cc.relnamespace::pg_catalog.regnamespace AS nspname, " - " cc.relname, ac.attname " + "SELECT nspname, cc.relname, ac.attname " "FROM pg_catalog.pg_inherits i, pg_catalog.pg_attribute ac, " - " pg_catalog.pg_attribute ap, pg_catalog.pg_class cc " + " pg_catalog.pg_attribute ap, pg_catalog.pg_class cc, " + " pg_catalog.pg_namespace nc " "WHERE cc.oid = ac.attrelid AND i.inhrelid = ac.attrelid " " AND i.inhparent = ap.attrelid AND ac.attname = ap.attname " + " AND cc.relnamespace = nc.oid " " AND ap.attnum > 0 and ap.attnotnull AND NOT ac.attnotnull"); ntup = PQntuples(res); From d855190be0b22cd77e2eb386885f1b5faa39714d Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 7 Jul 2025 08:54:37 +0900 Subject: [PATCH 709/796] Fix incompatibility with libxml2 >= 2.14 libxml2 has deprecated the members of xmlBuffer, and it is recommended to access them with dedicated routines. We have only one case in the tree where this shows an impact: xml2/xpath.c where "content" was getting directly accessed. The rest of the code looked fine, checking the PostgreSQL code with libxml2 close to the top of its "2.14" branch. xmlBufferContent() exists since year 2000 based on a check of the upstream libxml2 tree, so let's switch to it. Like 400928b83bd2, backpatch all the way down as this can have an impact on all the branches already released once newer versions of libxml2 get more popular. Reported-by: Walid Ibrahim Reviewed-by: Tom Lane Discussion: https://postgr.es/m/aGdSdcR4QTjEHX6s@paquier.xyz Backpatch-through: 13 --- contrib/xml2/xpath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c index 212cb74aa220b..94bb31434c89b 100644 --- a/contrib/xml2/xpath.c +++ b/contrib/xml2/xpath.c @@ -174,7 +174,7 @@ pgxmlNodeSetToText(xmlNodeSetPtr nodeset, xmlBufferWriteCHAR(buf, toptagname); xmlBufferWriteChar(buf, ">"); } - result = xmlStrdup(buf->content); + result = xmlStrdup(xmlBufferContent(buf)); xmlBufferFree(buf); return result; } From caac88dbc24e6abad39c0988786ad3f60fa266fa Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 7 Jul 2025 14:33:20 -0400 Subject: [PATCH 710/796] Restore the ability to run pl/pgsql expression queries in parallel. pl/pgsql's notion of an "expression" is very broad, encompassing any SQL SELECT query that returns a single column and no more than one row. So there are cases, for example evaluation of an aggregate function, where the query involves significant work and it'd be useful to run it with parallel workers. This used to be possible, but commits 3eea7a0c9 et al unintentionally disabled it. The simplest fix is to make exec_eval_expr() pass maxtuples = 0 rather than 2 to exec_run_select(). This avoids the new rule that we will never use parallelism when a nonzero "count" limit is passed to ExecutorRun(). (Note that the pre-3eea7a0c9 behavior was indeed unsafe, so reverting that rule is not in the cards.) The reason for passing 2 before was that exec_eval_expr() will throw an error if it gets more than one returned row, so we figured that as soon as we have two rows we know that will happen and we might as well stop running the query. That choice was cost-free when it was made; but disabling parallelism is far from cost-free, so now passing 2 amounts to optimizing a failure case at the expense of useful cases. An expression query that can return more than one row is certainly broken. People might now need to wait a bit longer to discover such breakage; but hopefully few will use enormously expensive cases as their first test of new pl/pgsql logic. Author: Dipesh Dhameliya Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CABgZEgdfbnq9t6xXJnmXbChNTcWFjeM_6nuig41tm327gYi2ig@mail.gmail.com Backpatch-through: 13 --- src/pl/plpgsql/src/pl_exec.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index ca3d1e753bc0e..e7b0f2544b46e 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -5711,7 +5711,7 @@ exec_eval_expr(PLpgSQL_execstate *estate, /* * Else do it the hard way via exec_run_select */ - rc = exec_run_select(estate, expr, 2, NULL); + rc = exec_run_select(estate, expr, 0, NULL); if (rc != SPI_OK_SELECT) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -5765,6 +5765,10 @@ exec_eval_expr(PLpgSQL_execstate *estate, /* ---------- * exec_run_select Execute a select query + * + * Note: passing maxtuples different from 0 ("return all tuples") is + * deprecated because it will prevent parallel execution of the query. + * However, we retain the parameter in case we need it someday. * ---------- */ static int From 5ed02a8b4699b050062f02cff8f9f00fd783f8ac Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 8 Jul 2025 12:50:19 -0400 Subject: [PATCH 711/796] Fix low-probability memory leak in XMLSERIALIZE(... INDENT). xmltotext_with_options() did not consider the possibility that pg_xml_init() could fail --- most likely due to OOM. If that happened, the already-parsed xmlDoc structure would be leaked. Oversight in commit 483bdb2af. Bug: #18981 Author: Dmitry Kovalenko Reviewed-by: Tom Lane Discussion: https://postgr.es/m/18981-9bc3c80f107ae925@postgresql.org Backpatch-through: 16 --- src/backend/utils/adt/xml.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index fba2c684b8ab9..5f722da9a0ee7 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -663,7 +663,7 @@ xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) volatile xmlBufferPtr buf = NULL; volatile xmlSaveCtxtPtr ctxt = NULL; ErrorSaveContext escontext = {T_ErrorSaveContext}; - PgXmlErrorContext *xmlerrcxt; + PgXmlErrorContext *volatile xmlerrcxt = NULL; #endif if (xmloption_arg != XMLOPTION_DOCUMENT && !indent) @@ -704,13 +704,18 @@ xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) return (text *) data; } - /* Otherwise, we gotta spin up some error handling. */ - xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); - + /* + * Otherwise, we gotta spin up some error handling. Unlike most other + * routines in this module, we already have a libxml "doc" structure to + * free, so we need to call pg_xml_init() inside the PG_TRY and be + * prepared for it to fail (typically due to palloc OOM). + */ PG_TRY(); { size_t decl_len = 0; + xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); + /* The serialized data will go into this buffer. */ buf = xmlBufferCreate(); @@ -838,10 +843,10 @@ xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent) xmlSaveClose(ctxt); if (buf) xmlBufferFree(buf); - if (doc) - xmlFreeDoc(doc); + xmlFreeDoc(doc); - pg_xml_done(xmlerrcxt, true); + if (xmlerrcxt) + pg_xml_done(xmlerrcxt, true); PG_RE_THROW(); } From 8af6a4bc59abb30440256a038266f9215471a606 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Wed, 9 Jul 2025 09:27:59 +0530 Subject: [PATCH 712/796] Doc: Improve logical replication failover documentation. Clarified that the failover steps apply to a specific PostgreSQL subscriber and added guidance for verifying replication slot synchronization during planned failover. Additionally, corrected the standby query to avoid false positives by checking invalidation_reason IS NULL instead of conflicting. Author: Ashutosh Bapat Author: Shveta Malik Backpatch-through: 17, where it was introduced Discussion: https://www.postgresql.org/message-id/CAExHW5uiZ-fF159=jwBwPMbjZeZDtmcTbN+hd4mrURLCg2uzJg@mail.gmail.com --- doc/src/sgml/logical-replication.sgml | 42 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 88b2491e09b3e..db68cc6ec1dd6 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -714,8 +714,8 @@ ALTER SUBSCRIPTION
- To confirm that the standby server is indeed ready for failover, follow these - steps to verify that all necessary logical replication slots have been + To confirm that the standby server is indeed ready for failover for a given subscriber, follow these + steps to verify that all the logical replication slots required by that subscriber have been synchronized to the standby server: @@ -769,7 +769,7 @@ test_sub=# SELECT Check that the logical replication slots identified above exist on the standby server and are ready for failover. -test_standby=# SELECT slot_name, (synced AND NOT temporary AND NOT conflicting) AS failover_ready +test_standby=# SELECT slot_name, (synced AND NOT temporary AND invalidation_reason IS NULL) AS failover_ready FROM pg_replication_slots WHERE slot_name IN ('sub1','sub2','sub3', 'pg_16394_sync_16385_7394666715149055164'); @@ -787,10 +787,42 @@ test_standby=# SELECT slot_name, (synced AND NOT temporary AND NOT conflicting) If all the slots are present on the standby server and the result (failover_ready) of the above SQL query is true, then - existing subscriptions can continue subscribing to publications now on the - new primary server. + existing subscriptions can continue subscribing to publications on the new + primary server. + + + + The first two steps in the above procedure are meant for a + PostgreSQL subscriber. It is recommended to run + these steps on each subscriber node, that will be served by the designated + standby after failover, to obtain the complete list of replication + slots. This list can then be verified in Step 3 to ensure failover readiness. + Non-PostgreSQL subscribers, on the other hand, may + use their own methods to identify the replication slots used by their + respective subscriptions. + + + + In some cases, such as during a planned failover, it is necessary to confirm + that all subscribers, whether PostgreSQL or + non-PostgreSQL, will be able to continue + replication after failover to a given standby server. In such cases, use the + following SQL, instead of performing the first two steps above, to identify + which replication slots on the primary need to be synced to the standby that + is intended for promotion. This query returns the relevant replication slots + associated with all the failover-enabled subscriptions. + + +/* primary # */ SELECT array_agg(quote_literal(r.slot_name)) AS slots + FROM pg_replication_slots r + WHERE r.failover AND NOT r.temporary; + slots +------- + {'sub1','sub2','sub3', 'pg_16394_sync_16385_7394666715149055164'} +(1 row) +
From 78274f638d3567b504dd001e3d62f6cfeac56445 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Wed, 9 Jul 2025 05:45:28 -0700 Subject: [PATCH 713/796] Fix tab-completion for COPY and \copy options. Commit c273d9d8ce4 reworked tab-completion of COPY and \copy in psql and added support for completing options within WITH clauses. However, the same COPY options were suggested for both COPY TO and COPY FROM commands, even though some options are only valid for one or the other. This commit separates the COPY options for COPY FROM and COPY TO commands to provide more accurate auto-completion suggestions. Back-patch to v14 where tab-completion for COPY and \copy options within WITH clauses was first supported. Author: Atsushi Torikoshi Reviewed-by: Yugo Nagata Discussion: https://postgr.es/m/079e7a2c801f252ae8d522b772790ed7@oss.nttdata.com Backpatch-through: 14 --- src/bin/psql/tab-complete.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 1bd01ff865ff5..6c62c07ce8219 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1169,6 +1169,19 @@ Alter_procedure_options, "COST", "IMMUTABLE", "LEAKPROOF", "NOT LEAKPROOF", \ Alter_routine_options, "CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", \ "STRICT", "SUPPORT" +/* COPY options shared between FROM and TO */ +#define Copy_common_options \ +"DELIMITER", "ENCODING", "ESCAPE", "FORMAT", "HEADER", "NULL", "QUOTE" + +/* COPY FROM options */ +#define Copy_from_options \ +Copy_common_options, "DEFAULT", "FORCE_NOT_NULL", "FORCE_NULL", "FREEZE", \ +"LOG_VERBOSITY", "ON_ERROR" + +/* COPY TO options */ +#define Copy_to_options \ +Copy_common_options, "FORCE_QUOTE" + /* * These object types were introduced later than our support cutoff of * server version 9.2. We use the VersionedQuery infrastructure so that @@ -2899,23 +2912,24 @@ psql_completion(const char *text, int start, int end) else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny)) COMPLETE_WITH("WITH (", "WHERE"); - /* Complete COPY FROM|TO filename WITH ( */ - else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "WITH", "(")) - COMPLETE_WITH("FORMAT", "FREEZE", "DELIMITER", "NULL", - "HEADER", "QUOTE", "ESCAPE", "FORCE_QUOTE", - "FORCE_NOT_NULL", "FORCE_NULL", "ENCODING", "DEFAULT", - "ON_ERROR", "LOG_VERBOSITY"); + /* Complete COPY FROM filename WITH ( */ + else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny, "WITH", "(")) + COMPLETE_WITH(Copy_from_options); + + /* Complete COPY TO filename WITH ( */ + else if (Matches("COPY|\\copy", MatchAny, "TO", MatchAny, "WITH", "(")) + COMPLETE_WITH(Copy_to_options); /* Complete COPY FROM|TO filename WITH (FORMAT */ else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "WITH", "(", "FORMAT")) COMPLETE_WITH("binary", "csv", "text"); /* Complete COPY FROM filename WITH (ON_ERROR */ - else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "WITH", "(", "ON_ERROR")) + else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny, "WITH", "(", "ON_ERROR")) COMPLETE_WITH("stop", "ignore"); /* Complete COPY FROM filename WITH (LOG_VERBOSITY */ - else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "WITH", "(", "LOG_VERBOSITY")) + else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAny, "WITH", "(", "LOG_VERBOSITY")) COMPLETE_WITH("default", "verbose"); /* Complete COPY FROM WITH () */ From 643760a77f58e38386f04c40d50d6fed52fc8ccf Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Thu, 10 Jul 2025 15:52:41 -0500 Subject: [PATCH 714/796] pg_dump: Fix object-type sort priority for large objects. Commit a45c78e328 moved large object metadata from SECTION_PRE_DATA to SECTION_DATA but neglected to move PRIO_LARGE_OBJECT in dbObjectTypePriorities accordingly. While this hasn't produced any known live bugs, it causes problems for a proposed patch that optimizes upgrades with many large objects. Fixing the priority might also make the topological sort step marginally faster by reducing the number of ordering violations that have to be fixed. Reviewed-by: Nitin Motiani Reviewed-by: Tom Lane Discussion: https://postgr.es/m/aBkQLSkx1zUJ-LwJ%40nathan Discussion: https://postgr.es/m/aG_5DBCjdDX6KAoD%40nathan Backpatch-through: 17 --- src/bin/pg_dump/pg_dump_sort.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 4cb754caa55fd..31bdb91a585ad 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -77,10 +77,10 @@ enum dbObjectTypePriorities PRIO_TABLE_ATTACH, PRIO_DUMMY_TYPE, PRIO_ATTRDEF, - PRIO_LARGE_OBJECT, PRIO_PRE_DATA_BOUNDARY, /* boundary! */ PRIO_TABLE_DATA, PRIO_SEQUENCE_SET, + PRIO_LARGE_OBJECT, PRIO_LARGE_OBJECT_DATA, PRIO_POST_DATA_BOUNDARY, /* boundary! */ PRIO_CONSTRAINT, From 0a071b4056e39a5388db2baefef4d5547e44d880 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Fri, 11 Jul 2025 09:53:34 +0530 Subject: [PATCH 715/796] Fix the handling of two GUCs during upgrade. Previously, the check_hook functions for max_slot_wal_keep_size and idle_replication_slot_timeout would incorrectly raise an ERROR for values set in postgresql.conf during upgrade, even though those values were not actively used in the upgrade process. To prevent logical slot invalidation during upgrade, we used to set special values for these GUCs. Now, instead of relying on those values, we directly prevent WAL removal and logical slot invalidation caused by max_slot_wal_keep_size and idle_replication_slot_timeout. Note: PostgreSQL 17 does not include the idle_replication_slot_timeout GUC, so related changes were not backported. BUG #18979 Reported-by: jorsol Author: Dilip Kumar Reviewed by: vignesh C Reviewed by: Alvaro Herrera Backpatch-through: 17, where it was introduced Discussion: https://postgr.es/m/219561.1751826409@sss.pgh.pa.us Discussion: https://postgr.es/m/18979-a1b7fdbb7cd181c6@postgresql.org --- src/backend/access/transam/xlog.c | 33 +++++++---------------------- src/backend/replication/slot.c | 12 ++++------- src/backend/utils/misc/guc_tables.c | 2 +- src/bin/pg_upgrade/server.c | 11 ---------- src/include/utils/guc_hooks.h | 2 -- 5 files changed, 13 insertions(+), 47 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 281d6a7f5f4af..f45a43acb54af 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -2218,25 +2218,6 @@ check_wal_segment_size(int *newval, void **extra, GucSource source) return true; } -/* - * GUC check_hook for max_slot_wal_keep_size - * - * We don't allow the value of max_slot_wal_keep_size other than -1 during the - * binary upgrade. See start_postmaster() in pg_upgrade for more details. - */ -bool -check_max_slot_wal_keep_size(int *newval, void **extra, GucSource source) -{ - if (IsBinaryUpgrade && *newval != -1) - { - GUC_check_errdetail("\"%s\" must be set to -1 during binary upgrade mode.", - "max_slot_wal_keep_size"); - return false; - } - - return true; -} - /* * At a checkpoint, how many WAL segments to recycle as preallocated future * XLOG segments? Returns the highest segment that should be preallocated. @@ -8001,17 +7982,19 @@ KeepLogSeg(XLogRecPtr recptr, XLogRecPtr slotsMinReqLSN, XLogSegNo *logSegNo) XLByteToSeg(recptr, currSegNo, wal_segment_size); segno = currSegNo; - /* - * Calculate how many segments are kept by slots first, adjusting for - * max_slot_wal_keep_size. - */ + /* Calculate how many segments are kept by slots. */ keep = slotsMinReqLSN; if (keep != InvalidXLogRecPtr && keep < recptr) { XLByteToSeg(keep, segno, wal_segment_size); - /* Cap by max_slot_wal_keep_size ... */ - if (max_slot_wal_keep_size_mb >= 0) + /* + * Account for max_slot_wal_keep_size to avoid keeping more than + * configured. However, don't do that during a binary upgrade: if + * slots were to be invalidated because of this, it would not be + * possible to preserve logical ones during the upgrade. + */ + if (max_slot_wal_keep_size_mb >= 0 && !IsBinaryUpgrade) { uint64 slot_keep_segs; diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index a1d4768623f55..a234e2ca9c1af 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1671,14 +1671,6 @@ InvalidatePossiblyObsoleteSlot(ReplicationSlotInvalidationCause cause, SpinLockRelease(&s->mutex); - /* - * The logical replication slots shouldn't be invalidated as GUC - * max_slot_wal_keep_size is set to -1 during the binary upgrade. See - * check_old_cluster_for_valid_slots() where we ensure that no - * invalidated before the upgrade. - */ - Assert(!(*invalidated && SlotIsLogical(s) && IsBinaryUpgrade)); - if (active_pid != 0) { /* @@ -1805,6 +1797,10 @@ InvalidateObsoleteReplicationSlots(ReplicationSlotInvalidationCause cause, if (!s->in_use) continue; + /* Prevent invalidation of logical slots during binary upgrade. */ + if (SlotIsLogical(s) && IsBinaryUpgrade) + continue; + if (InvalidatePossiblyObsoleteSlot(cause, s, oldestLSN, dboid, snapshotConflictHorizon, &invalidated)) diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index c42fccf3fe74d..a997dcb7dbcb7 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2953,7 +2953,7 @@ struct config_int ConfigureNamesInt[] = }, &max_slot_wal_keep_size_mb, -1, -1, MAX_KILOBYTES, - check_max_slot_wal_keep_size, NULL, NULL + NULL, NULL, NULL }, { diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c index 91bcb4dbc7e69..b223d5afddfff 100644 --- a/src/bin/pg_upgrade/server.c +++ b/src/bin/pg_upgrade/server.c @@ -241,17 +241,6 @@ start_postmaster(ClusterInfo *cluster, bool report_and_exit_on_error) if (cluster == &new_cluster) appendPQExpBufferStr(&pgoptions, " -c synchronous_commit=off -c fsync=off -c full_page_writes=off"); - /* - * Use max_slot_wal_keep_size as -1 to prevent the WAL removal by the - * checkpointer process. If WALs required by logical replication slots - * are removed, the slots are unusable. This setting prevents the - * invalidation of slots during the upgrade. We set this option when - * cluster is PG17 or later because logical replication slots can only be - * migrated since then. Besides, max_slot_wal_keep_size is added in PG13. - */ - if (GET_MAJOR_VERSION(cluster->major_version) >= 1700) - appendPQExpBufferStr(&pgoptions, " -c max_slot_wal_keep_size=-1"); - /* * Use -b to disable autovacuum and logical replication launcher * (effective in PG17 or later for the latter). diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 8fd91af3887f7..1babff78bf35c 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -86,8 +86,6 @@ extern bool check_maintenance_io_concurrency(int *newval, void **extra, extern void assign_maintenance_io_concurrency(int newval, void *extra); extern bool check_max_connections(int *newval, void **extra, GucSource source); extern bool check_max_wal_senders(int *newval, void **extra, GucSource source); -extern bool check_max_slot_wal_keep_size(int *newval, void **extra, - GucSource source); extern void assign_max_wal_size(int newval, void *extra); extern bool check_max_worker_processes(int *newval, void **extra, GucSource source); From baf7d8fe3e5238725563562bd2856ff04635f35f Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 11 Jul 2025 18:50:13 -0400 Subject: [PATCH 716/796] Fix inconsistent quoting of role names in ACLs. getid() and putid(), which parse and deparse role names within ACL input/output, applied isalnum() to see if a character within a role name requires quoting. They did this even for non-ASCII characters, which is problematic because the results would depend on encoding, locale, and perhaps even platform. So it's possible that putid() could elect not to quote some string that, later in some other environment, getid() will decide is not a valid identifier, causing dump/reload or similar failures. To fix this in a way that won't risk interoperability problems with unpatched versions, make getid() treat any non-ASCII as a legitimate identifier character (hence not requiring quotes), while making putid() treat any non-ASCII as requiring quoting. We could remove the resulting excess quoting once we feel that no unpatched servers remain in the wild, but that'll be years. A lesser problem is that getid() did the wrong thing with an input consisting of just two double quotes (""). That has to represent an empty string, but getid() read it as a single double quote instead. The case cannot arise in the normal course of events, since we don't allow empty-string role names. But let's fix it while we're here. Although we've not heard field reports of problems with non-ASCII role names, there's clearly a hazard there, so back-patch to all supported versions. Reported-by: Peter Eisentraut Author: Tom Lane Discussion: https://postgr.es/m/3792884.1751492172@sss.pgh.pa.us Backpatch-through: 13 --- src/backend/utils/adt/acl.c | 33 ++++++++++++++++++------ src/test/regress/expected/privileges.out | 20 ++++++++++++++ src/test/regress/sql/privileges.sql | 8 ++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index d7b39140b3d22..6c723e0dbcc59 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -130,6 +130,22 @@ static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode); static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue); +/* + * Test whether an identifier char can be left unquoted in ACLs. + * + * Formerly, we used isalnum() even on non-ASCII characters, resulting in + * unportable behavior. To ensure dump compatibility with old versions, + * we now treat high-bit-set characters as always requiring quoting during + * putid(), but getid() will always accept them without quotes. + */ +static inline bool +is_safe_acl_char(unsigned char c, bool is_getid) +{ + if (IS_HIGHBIT_SET(c)) + return is_getid; + return isalnum(c) || c == '_'; +} + /* * getid * Consumes the first alphanumeric string (identifier) found in string @@ -155,21 +171,22 @@ getid(const char *s, char *n, Node *escontext) while (isspace((unsigned char) *s)) s++; - /* This code had better match what putid() does, below */ for (; *s != '\0' && - (isalnum((unsigned char) *s) || - *s == '_' || - *s == '"' || - in_quotes); + (in_quotes || *s == '"' || is_safe_acl_char(*s, true)); s++) { if (*s == '"') { + if (!in_quotes) + { + in_quotes = true; + continue; + } /* safe to look at next char (could be '\0' though) */ if (*(s + 1) != '"') { - in_quotes = !in_quotes; + in_quotes = false; continue; } /* it's an escaped double quote; skip the escaping char */ @@ -203,10 +220,10 @@ putid(char *p, const char *s) const char *src; bool safe = true; + /* Detect whether we need to use double quotes */ for (src = s; *src; src++) { - /* This test had better match what getid() does, above */ - if (!isalnum((unsigned char) *src) && *src != '_') + if (!is_safe_acl_char(*src, false)) { safe = false; break; diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index e8c668e0a11f7..43845b6f7a8aa 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -2332,6 +2332,26 @@ SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, 'SELECT, fake_privilege', FALSE); -- error ERROR: unrecognized privilege type: "fake_privilege" +-- Test quoting and dequoting of user names in ACLs +CREATE ROLE "regress_""quoted"; +SELECT makeaclitem('regress_"quoted'::regrole, 'regress_"quoted'::regrole, + 'SELECT', TRUE); + makeaclitem +------------------------------------------ + "regress_""quoted"=r*/"regress_""quoted" +(1 row) + +SELECT '"regress_""quoted"=r*/"regress_""quoted"'::aclitem; + aclitem +------------------------------------------ + "regress_""quoted"=r*/"regress_""quoted" +(1 row) + +SELECT '""=r*/""'::aclitem; -- used to be misparsed as """" +ERROR: a name must follow the "/" sign +LINE 1: SELECT '""=r*/""'::aclitem; + ^ +DROP ROLE "regress_""quoted"; -- Test non-throwing aclitem I/O SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem'); pg_input_is_valid diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index b7e1cb6cdde9f..54b82b610a4fd 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -1481,6 +1481,14 @@ SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, 'SELECT, fake_privilege', FALSE); -- error +-- Test quoting and dequoting of user names in ACLs +CREATE ROLE "regress_""quoted"; +SELECT makeaclitem('regress_"quoted'::regrole, 'regress_"quoted'::regrole, + 'SELECT', TRUE); +SELECT '"regress_""quoted"=r*/"regress_""quoted"'::aclitem; +SELECT '""=r*/""'::aclitem; -- used to be misparsed as """" +DROP ROLE "regress_""quoted"; + -- Test non-throwing aclitem I/O SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem'); SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem'); From 27db585d18d32627693048c8bb32c043a8122941 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 15 Jul 2025 16:35:42 -0400 Subject: [PATCH 717/796] Doc: clarify description of current-date/time functions. Minor wordsmithing of the func.sgml paragraph describing statement_timestamp() and allied functions: don't switch between "statement" and "command" when those are being used to mean about the same thing. Also, add some text to protocol.sgml describing the perhaps-surprising behavior these functions have in a multi-statement Query message. Reported-by: P M Author: Tom Lane Reviewed-by: Laurenz Albe Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/175223006802.3157505.14764328206246105568@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/func.sgml | 6 +++--- doc/src/sgml/protocol.sgml | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9db19c5d4abcc..fc4b95378715e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11024,10 +11024,10 @@ now() statement (more specifically, the time of receipt of the latest command message from the client). statement_timestamp() and transaction_timestamp() - return the same value during the first command of a transaction, but might - differ during subsequent commands. + return the same value during the first statement of a transaction, but might + differ during subsequent statements. clock_timestamp() returns the actual current time, and - therefore its value changes even within a single SQL command. + therefore its value changes even within a single SQL statement. timeofday() is a historical PostgreSQL function. Like clock_timestamp(), it returns the actual current time, diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 1b1894e97511f..3a69acfa59c9e 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -800,6 +800,16 @@ SELCT 1/0; Errors detected at semantic analysis or later, such as a misspelled table or column name, do not have this effect.
+ + + Lastly, note that all the statements within the Query message will + observe the same value of statement_timestamp(), + since that timestamp is updated only upon receipt of the Query + message. This will result in them all observing the same + value of transaction_timestamp() as well, + except in cases where the query string ends a previously-started + transaction and begins a new one. + From 15f320b0b3debaaa668f2c22d74637dc01773978 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 15 Jul 2025 18:11:18 -0400 Subject: [PATCH 718/796] Silence uninitialized-value warnings in compareJsonbContainers(). Because not every path through JsonbIteratorNext() sets val->type, some compilers complain that compareJsonbContainers() is comparing possibly-uninitialized values. The paths that don't set it return WJB_DONE, WJB_END_ARRAY, or WJB_END_OBJECT, so it's clear by manual inspection that the "(ra == rb)" code path is safe, and indeed we aren't seeing warnings about that. But the (ra != rb) case is much less obviously safe. In Assert-enabled builds it seems that the asserts rejecting WJB_END_ARRAY and WJB_END_OBJECT persuade gcc 15.x not to warn, which makes little sense because it's impossible to believe that the compiler can prove of its own accord that ra/rb aren't WJB_DONE here. (In fact they never will be, so the code isn't wrong, but why is there no warning?) Without Asserts, the appearance of warnings is quite unsurprising. We discussed fixing this by converting those two Asserts into pg_assume, but that seems not very satisfactory when it's so unclear why the compiler is or isn't warning: the warning could easily reappear with some other compiler version. Let's fix it in a less magical, more future-proof way by changing JsonbIteratorNext() so that it always does set val->type. The cost of that should be pretty negligible, and it makes the function's API spec less squishy. Reported-by: Erik Rijkers Author: Tom Lane Reviewed-by: Andres Freund Discussion: https://postgr.es/m/988bf1bc-3f1f-99f3-bf98-222f1cd9dc5e@xs4all.nl Discussion: https://postgr.es/m/0c623e8a204187b87b4736792398eaf1@postgrespro.ru Backpatch-through: 13 --- src/backend/utils/adt/jsonb_util.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 9941daad2bc0b..0274deecbb19a 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -270,9 +270,6 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) else { /* - * It's safe to assume that the types differed, and that the va - * and vb values passed were set. - * * If the two values were of the same container type, then there'd * have been a chance to observe the variation in the number of * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They're @@ -845,15 +842,20 @@ JsonbIteratorInit(JsonbContainer *container) * It is our job to expand the jbvBinary representation without bothering them * with it. However, clients should not take it upon themselves to touch array * or Object element/pair buffers, since their element/pair pointers are - * garbage. Also, *val will not be set when returning WJB_END_ARRAY or - * WJB_END_OBJECT, on the assumption that it's only useful to access values - * when recursing in. + * garbage. + * + * *val is not meaningful when the result is WJB_DONE, WJB_END_ARRAY or + * WJB_END_OBJECT. However, we set val->type = jbvNull in those cases, + * so that callers may assume that val->type is always well-defined. */ JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) { if (*it == NULL) + { + val->type = jbvNull; return WJB_DONE; + } /* * When stepping into a nested container, we jump back here to start @@ -891,6 +893,7 @@ JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) * nesting). */ *it = freeAndGetParent(*it); + val->type = jbvNull; return WJB_END_ARRAY; } @@ -944,6 +947,7 @@ JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) * of nesting). */ *it = freeAndGetParent(*it); + val->type = jbvNull; return WJB_END_OBJECT; } else @@ -988,8 +992,10 @@ JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) return WJB_VALUE; } - elog(ERROR, "invalid iterator state"); - return -1; + elog(ERROR, "invalid jsonb iterator state"); + /* satisfy compilers that don't know that elog(ERROR) doesn't return */ + val->type = jbvNull; + return WJB_DONE; } /* From c9456b015cbac3d9dfcb6a33fe941924d97946fe Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 15 Jul 2025 18:53:00 -0400 Subject: [PATCH 719/796] Doc: clarify description of regexp fields in pg_ident.conf. The grammar was a little shaky and confusing here, so word-smith it a bit. Also, adjust the comments in pg_ident.conf.sample to use the same terminology as the SGML docs, in particular "DATABASE-USERNAME" not "PG-USERNAME". Back-patch appropriate subsets. I did not risk changing pg_ident.conf.sample in released branches, but it still seems OK to change it in v18. Reported-by: Alexey Shishkin Author: Tom Lane Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/175206279327.3157504.12519088928605422253@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/client-auth.sgml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index f1eb3b279ed42..3e5514fd436ad 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -984,8 +984,9 @@ local db1,db2,@demodbs all md5 the remainder of the field is treated as a regular expression. (See for details of PostgreSQL's regular expression syntax.) The regular - expression can include a single capture, or parenthesized subexpression, - which can then be referenced in the database-username + expression can include a single capture, or parenthesized subexpression. + The portion of the system user name that matched the capture can then + be referenced in the database-username field as \1 (backslash-one). This allows the mapping of multiple user names in a single line, which is particularly useful for simple syntax substitutions. For example, these entries @@ -1003,12 +1004,11 @@ mymap /^(.*)@otherdomain\.com$ guest If the database-username field starts with a slash (/), the remainder of the field is treated - as a regular expression (see - for details of PostgreSQL's regular - expression syntax). It is not possible to use \1 - to use a capture from regular expression on - system-username for a regular expression - on database-username. + as a regular expression. + When the database-username field is a regular + expression, it is not possible to use \1 within it to + refer to a capture from the system-username + field. From 7e03b9f4d28eeae7487a57faad2aa45e50375797 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Wed, 16 Jul 2025 08:32:52 +0900 Subject: [PATCH 720/796] doc: Fix confusing description of streaming option in START_REPLICATION. Previously, the documentation described the streaming option as a boolean, which is outdated since it's no longer a boolean as of protocol version 4. This could confuse users. This commit updates the description to remove the "boolean" reference and clearly list the valid values for the streaming option. Back-patch to v16, where the streaming option changed to a non-boolean. Author: Euler Taveira Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/8d21fb98-5c25-4dee-8387-e5a62b01ea7d@app.fastmail.com Backpatch-through: 16 --- doc/src/sgml/protocol.sgml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 3a69acfa59c9e..e19d1e4c6e92f 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -3294,11 +3294,13 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Boolean option to enable streaming of in-progress transactions. - It accepts an additional value "parallel" to enable sending extra - information with some messages to be used for parallelisation. - Minimum protocol version 2 is required to turn it on. Minimum protocol - version 4 is required for the "parallel" option. + Option to enable streaming of in-progress transactions. Valid values are + off (the default), on and + parallel. The setting parallel + enables sending extra information with some messages to be used for + parallelization. Minimum protocol version 2 is required to turn it + on. Minimum protocol version 4 is required for the + parallel value. From 5fb19cbbfa0125da7f95de10efab44a89e141560 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 16 Jul 2025 11:50:34 -0500 Subject: [PATCH 721/796] psql: Fix note on project naming in output of \copyright. This adjusts the wording to match the changes in commits 5987553fde, a233a603ba, and pgweb commit 2d764dbc08. Reviewed-by: Tom Lane Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/aHVo791guQR6uqwT%40nathan Backpatch-through: 13 --- src/bin/psql/help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 98af878724f8d..b880fa2c1dd1e 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -732,7 +732,7 @@ void print_copyright(void) { puts("PostgreSQL Database Management System\n" - "(formerly known as Postgres, then as Postgres95)\n\n" + "(also known as Postgres, formerly known as Postgres95)\n\n" "Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group\n\n" "Portions Copyright (c) 1994, The Regents of the University of California\n\n" "Permission to use, copy, modify, and distribute this software and its\n" From c8015bc23d5809b2c48c2ece214856a7f653bfb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Wed, 16 Jul 2025 19:22:53 +0200 Subject: [PATCH 722/796] Fix dumping of comments on invalid constraints on domains We skip dumping constraints together with domains if they are invalid ('separate') so that they appear after data -- but their comments were dumped together with the domain definition, which in effect leads to the comment being dumped when the constraint does not yet exist. Delay them in the same way. Oversight in 7eca575d1c28; backpatch all the way back. Author: jian he Discussion: https://postgr.es/m/CACJufxF_C2pe6J_+nPr6C5jf5rQnbYP8XOKr4HM8yHZtp2aQqQ@mail.gmail.com --- src/bin/pg_dump/pg_dump.c | 23 ++++++++++++++++++++++- src/test/regress/expected/constraints.out | 4 ++++ src/test/regress/sql/constraints.sql | 6 ++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 9ed1a856fa3d3..06f630a910dc8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -11623,8 +11623,13 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) for (i = 0; i < tyinfo->nDomChecks; i++) { ConstraintInfo *domcheck = &(tyinfo->domChecks[i]); - PQExpBuffer conprefix = createPQExpBuffer(); + PQExpBuffer conprefix; + /* but only if the constraint itself was dumped here */ + if (domcheck->separate) + continue; + + conprefix = createPQExpBuffer(); appendPQExpBuffer(conprefix, "CONSTRAINT %s ON DOMAIN", fmtId(domcheck->dobj.name)); @@ -17361,6 +17366,22 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) .section = SECTION_POST_DATA, .createStmt = q->data, .dropStmt = delq->data)); + + if (coninfo->dobj.dump & DUMP_COMPONENT_COMMENT) + { + PQExpBuffer conprefix = createPQExpBuffer(); + char *qtypname = pg_strdup(fmtId(tyinfo->dobj.name)); + + appendPQExpBuffer(conprefix, "CONSTRAINT %s ON DOMAIN", + fmtId(coninfo->dobj.name)); + + dumpComment(fout, conprefix->data, qtypname, + tyinfo->dobj.namespace->dobj.name, + tyinfo->rolname, + coninfo->dobj.catId, 0, tyinfo->dobj.dumpId); + destroyPQExpBuffer(conprefix); + free(qtypname); + } } } else diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out index cf0b80d616965..a5b5795e58736 100644 --- a/src/test/regress/expected/constraints.out +++ b/src/test/regress/expected/constraints.out @@ -830,3 +830,7 @@ DROP TABLE constraint_comments_tbl; DROP DOMAIN constraint_comments_dom; DROP ROLE regress_constraint_comments; DROP ROLE regress_constraint_comments_noaccess; +-- Leave some constraints for the pg_upgrade test to pick up +CREATE DOMAIN constraint_comments_dom AS int; +ALTER DOMAIN constraint_comments_dom ADD CONSTRAINT inv_ck CHECK (value > 0) NOT VALID; +COMMENT ON CONSTRAINT inv_ck ON DOMAIN constraint_comments_dom IS 'comment on invalid constraint'; diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql index e3e3bea70911b..c47db9469f5dc 100644 --- a/src/test/regress/sql/constraints.sql +++ b/src/test/regress/sql/constraints.sql @@ -632,3 +632,9 @@ DROP DOMAIN constraint_comments_dom; DROP ROLE regress_constraint_comments; DROP ROLE regress_constraint_comments_noaccess; + +-- Leave some constraints for the pg_upgrade test to pick up +CREATE DOMAIN constraint_comments_dom AS int; + +ALTER DOMAIN constraint_comments_dom ADD CONSTRAINT inv_ck CHECK (value > 0) NOT VALID; +COMMENT ON CONSTRAINT inv_ck ON DOMAIN constraint_comments_dom IS 'comment on invalid constraint'; From 40fa344e6ee2e357240a444794fc0d4262f68591 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Thu, 17 Jul 2025 00:21:18 +0200 Subject: [PATCH 723/796] doc: Add example file for COPY The paragraph for introducing INSERT and COPY discussed how a file could be used for bulk loading with COPY, without actually showing what the file would look like. This adds a programlisting for the file contents. Backpatch to all supported branches since this example has lacked the file contents since PostgreSQL 7.2. Author: Daniel Gustafsson Reviewed-by: Fujii Masao Reviewed-by: Tom Lane Discussion: https://postgr.es/m/158017814191.19852.15019251381150731439@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/query.sgml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/query.sgml b/doc/src/sgml/query.sgml index 59962d6e85633..bec8a52a58943 100644 --- a/doc/src/sgml/query.sgml +++ b/doc/src/sgml/query.sgml @@ -264,8 +264,18 @@ COPY weather FROM '/home/user/weather.txt'; where the file name for the source file must be available on the machine running the backend process, not the client, since the backend process - reads the file directly. You can read more about the - COPY command in . + reads the file directly. The data inserted above into the weather table + could also be inserted from a file containing (values are separated by a + tab character): + + +San Francisco 46 50 0.25 1994-11-27 +San Francisco 43 57 0.0 1994-11-29 +Hayward 37 54 \N 1994-11-29 + + + You can read more about the COPY command in + .
From 9f70e8aab69a8a7720275808cfcc4b772acae108 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 17 Jul 2025 09:32:55 +0900 Subject: [PATCH 724/796] Fix inconsistent LWLock tranche names for MultiXact* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The terms used in wait_event_names.txt and lwlock.c were inconsistent for MultiXactOffsetSLRU and MultiXactMemberSLRU, which could cause joins between pg_wait_events and pg_stat_activity to fail. lwlock.c is adjusted in this commit to what the historical name of the event has always been, and what is documented. Oversight in 53c2a97a9266. 08b9b9e043bb has fixed a similar inconsistency some time ago. Author: Bertrand Drouvot Reviewed-by: Álvaro Herrera Discussion: https://postgr.es/m/aHdxN0D0hKXzHFQG@ip-10-97-1-34.eu-west-3.compute.internal Backpatch-through: 17 --- src/backend/storage/lmgr/lwlock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 5df62fafbfbae..11df55c63e50c 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -162,8 +162,8 @@ static const char *const BuiltinTrancheNames[] = { [LWTRANCHE_DSM_REGISTRY_DSA] = "DSMRegistryDSA", [LWTRANCHE_DSM_REGISTRY_HASH] = "DSMRegistryHash", [LWTRANCHE_COMMITTS_SLRU] = "CommitTsSLRU", - [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultixactOffsetSLRU", - [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultixactMemberSLRU", + [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultiXactOffsetSLRU", + [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultiXactMemberSLRU", [LWTRANCHE_NOTIFY_SLRU] = "NotifySLRU", [LWTRANCHE_SERIAL_SLRU] = "SerialSLRU", [LWTRANCHE_SUBTRANS_SLRU] = "SubtransSLRU", From 14ace395f73da3c02cea8d4f61cea16204cf39ed Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 17 Jul 2025 14:30:11 +0900 Subject: [PATCH 725/796] Remove duplicate line In 231b7d670b21, while copy-pasting some code into ExecEvalJsonCoercionFinish(), I (amitlan) accidentally introduced a duplicate line. Remove it. Reported-by: Jian He Discussion: https://postgr.es/m/CACJufxHcf=BpmRAJcjgfjOUfV76MwKnyz1x3ErXsWL26EAFmng@mail.gmail.com --- src/backend/executor/execExprInterp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index a55ad77256629..366975dad68f0 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4672,7 +4672,6 @@ ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op) * JsonBehavior expression. */ jsestate->escontext.error_occurred = false; - jsestate->escontext.error_occurred = false; jsestate->escontext.details_wanted = true; } } From 487c04bdf863d041192bc8e01258c15cc007382c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 17 Jul 2025 17:40:22 +0200 Subject: [PATCH 726/796] Remove assertion from PortalRunMulti MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have an assertion to ensure that a command tag has been assigned by the time we're done executing, but if we happen to execute a command with no queries, the assertion would fail. Per discussion, rather than contort things to get a tag assigned, just remove the assertion. Oversight in 2f9661311b83. That commit also retained a comment that explained logic that had been adjacent to it but diffused into various places, leaving none apt to keep part of the comment. Remove that part, and rewrite what remains for extra clarity. Bug: #18984 Backpatch-through: 13 Reported-by: Aleksander Alekseev Reviewed-by: Tom Lane Reviewed-by: Michaël Paquier Discussion: https://postgr.es/m/18984-0f4778a6599ac3ae@postgresql.org --- src/backend/tcop/pquery.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 0a7200682399e..5f86fa980807d 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -1353,24 +1353,15 @@ PortalRunMulti(Portal portal, PopActiveSnapshot(); /* - * If a query completion data was supplied, use it. Otherwise use the - * portal's query completion data. - * - * Exception: Clients expect INSERT/UPDATE/DELETE tags to have counts, so - * fake them with zeros. This can happen with DO INSTEAD rules if there - * is no replacement query of the same type as the original. We print "0 - * 0" here because technically there is no query of the matching tag type, - * and printing a non-zero count for a different query type seems wrong, - * e.g. an INSERT that does an UPDATE instead should not print "0 1" if - * one row was updated. See QueryRewrite(), step 3, for details. + * If a command tag was requested and we did not fill in a run-time- + * determined tag above, copy the parse-time tag from the Portal. (There + * might not be any tag there either, in edge cases such as empty prepared + * statements. That's OK.) */ - if (qc && qc->commandTag == CMDTAG_UNKNOWN) - { - if (portal->qc.commandTag != CMDTAG_UNKNOWN) - CopyQueryCompletion(qc, &portal->qc); - /* If the caller supplied a qc, we should have set it by now. */ - Assert(qc->commandTag != CMDTAG_UNKNOWN); - } + if (qc && + qc->commandTag == CMDTAG_UNKNOWN && + portal->qc.commandTag != CMDTAG_UNKNOWN) + CopyQueryCompletion(qc, &portal->qc); } /* From 725f2faf89add333d2d49ce73486a10cf4b83409 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 17 Jul 2025 12:46:38 -0400 Subject: [PATCH 727/796] Fix PQport to never return NULL unless the connection is NULL. This is the documented behavior, and it worked that way before v10. However, addition of the connhost[] array created cases where conn->connhost[conn->whichhost].port is NULL. The rest of libpq is careful to substitute DEF_PGPORT[_STR] for a null or empty port string, but we failed to do so here, leading to possibly returning NULL. As of v18 that causes psql's \conninfo command to segfault. Older psql versions avoid that, but it's pretty likely that other clients have trouble with this, so we'd better back-patch the fix. In stable branches, just revert to our historical behavior of returning an empty string when there was no user-given port specification. However, it seems substantially more useful and indeed more correct to hand back DEF_PGPORT_STR in such cases, so let's make v18 and master do that. Author: Daniele Varrazzo Reviewed-by: Laurenz Albe Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CA+mi_8YTS8WPZPO0PAb2aaGLwHuQ0DEQRF0ZMnvWss4y9FwDYQ@mail.gmail.com Backpatch-through: 13 --- src/interfaces/libpq/fe-connect.c | 4 +++- src/interfaces/libpq/libpq-int.h | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 2f87961a71ede..454d2ea3fb7c1 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -7074,7 +7074,9 @@ PQport(const PGconn *conn) if (!conn) return NULL; - if (conn->connhost != NULL) + if (conn->connhost != NULL && + conn->connhost[conn->whichhost].port != NULL && + conn->connhost[conn->whichhost].port[0] != '\0') return conn->connhost[conn->whichhost].port; return ""; diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index f36d76bf3fedf..ba4a2850223fa 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -351,7 +351,8 @@ typedef struct pg_conn_host pg_conn_host_type type; /* type of host address */ char *host; /* host name or socket path */ char *hostaddr; /* host numeric IP address */ - char *port; /* port number (always provided) */ + char *port; /* port number (if NULL or empty, use + * DEF_PGPORT[_STR]) */ char *password; /* password for this host, read from the * password file; NULL if not sought or not * found in password file. */ From 41e1a7b7b14f400302c6ee59e203a40786514915 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Fri, 18 Jul 2025 10:01:31 +0100 Subject: [PATCH 728/796] Fix concurrent update trigger issues with MERGE in a CTE. If a MERGE inside a CTE attempts an UPDATE or DELETE on a table with BEFORE ROW triggers, and a concurrent UPDATE or DELETE happens, the merge code would fail (crashing in the case of an UPDATE action, and potentially executing the wrong action for a DELETE action). This is the same issue that 9321c79c86 attempted to fix, except now for a MERGE inside a CTE. As noted in 9321c79c86, what needs to happen is for the trigger code to exit early, returning the TM_Result and TM_FailureData information to the merge code, if a concurrent modification is detected, rather than attempting to do an EPQ recheck. The merge code will then do its own rechecking, and rescan the action list, potentially executing a different action in light of the concurrent update. In particular, the trigger code must never call ExecGetUpdateNewTuple() for MERGE, since that is bound to fail because MERGE has its own per-action projection information. Commit 9321c79c86 did this using estate->es_plannedstmt->commandType in the trigger code to detect that a MERGE was being executed, which is fine for a plain MERGE command, but does not work for a MERGE inside a CTE. Fix by passing that information to the trigger code as an additional parameter passed to ExecBRUpdateTriggers() and ExecBRDeleteTriggers(). Back-patch as far as v17 only, since MERGE cannot appear inside a CTE prior to that. Additionally, take care to preserve the trigger ABI in v17 (though not in v18, which is still in beta). Bug: #18986 Reported-by: Yaroslav Syrytsia Author: Dean Rasheed Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/18986-e7a8aac3d339fa47@postgresql.org Backpatch-through: 17 --- src/backend/commands/trigger.c | 134 +++++++++++++----- src/backend/executor/nodeModifyTable.c | 14 +- src/include/commands/trigger.h | 18 +++ .../expected/merge-match-recheck.out | 27 ++-- .../isolation/specs/merge-match-recheck.spec | 22 +-- 5 files changed, 153 insertions(+), 62 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ea5cc10919d9c..ad28abe085f42 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -79,6 +79,7 @@ static bool GetTupleForTrigger(EState *estate, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot *oldslot, + bool do_epq_recheck, TupleTableSlot **epqslot, TM_Result *tmresultp, TM_FailureData *tmfdp); @@ -2679,13 +2680,14 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, * back the concurrently updated tuple if any. */ bool -ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, - ResultRelInfo *relinfo, - ItemPointer tupleid, - HeapTuple fdw_trigtuple, - TupleTableSlot **epqslot, - TM_Result *tmresult, - TM_FailureData *tmfd) +ExecBRDeleteTriggersNew(EState *estate, EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot **epqslot, + TM_Result *tmresult, + TM_FailureData *tmfd, + bool is_merge_delete) { TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -2700,9 +2702,17 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, { TupleTableSlot *epqslot_candidate = NULL; + /* + * Get a copy of the on-disk tuple we are planning to delete. In + * general, if the tuple has been concurrently updated, we should + * recheck it using EPQ. However, if this is a MERGE DELETE action, + * we skip this EPQ recheck and leave it to the caller (it must do + * additional rechecking, and might end up executing a different + * action entirely). + */ if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid, - LockTupleExclusive, slot, &epqslot_candidate, - tmresult, tmfd)) + LockTupleExclusive, slot, !is_merge_delete, + &epqslot_candidate, tmresult, tmfd)) return false; /* @@ -2765,6 +2775,24 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, return result; } +/* + * ABI-compatible wrapper to emulate old version of the above function. + * Do not call this version in new code. + */ +bool +ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot **epqslot, + TM_Result *tmresult, + TM_FailureData *tmfd) +{ + return ExecBRDeleteTriggersNew(estate, epqstate, relinfo, tupleid, + fdw_trigtuple, epqslot, tmresult, tmfd, + false); +} + /* * Note: is_crosspart_update must be true if the DELETE is being performed * as part of a cross-partition update. @@ -2792,6 +2820,7 @@ ExecARDeleteTriggers(EState *estate, tupleid, LockTupleExclusive, slot, + false, NULL, NULL, NULL); @@ -2930,13 +2959,14 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, } bool -ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, - ResultRelInfo *relinfo, - ItemPointer tupleid, - HeapTuple fdw_trigtuple, - TupleTableSlot *newslot, - TM_Result *tmresult, - TM_FailureData *tmfd) +ExecBRUpdateTriggersNew(EState *estate, EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot *newslot, + TM_Result *tmresult, + TM_FailureData *tmfd, + bool is_merge_update) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo); @@ -2957,10 +2987,17 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, { TupleTableSlot *epqslot_candidate = NULL; - /* get a copy of the on-disk tuple we are planning to update */ + /* + * Get a copy of the on-disk tuple we are planning to update. In + * general, if the tuple has been concurrently updated, we should + * recheck it using EPQ. However, if this is a MERGE UPDATE action, + * we skip this EPQ recheck and leave it to the caller (it must do + * additional rechecking, and might end up executing a different + * action entirely). + */ if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid, - lockmode, oldslot, &epqslot_candidate, - tmresult, tmfd)) + lockmode, oldslot, !is_merge_update, + &epqslot_candidate, tmresult, tmfd)) return false; /* cancel the update action */ /* @@ -3082,6 +3119,24 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, return true; } +/* + * ABI-compatible wrapper to emulate old version of the above function. + * Do not call this version in new code. + */ +bool +ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot *newslot, + TM_Result *tmresult, + TM_FailureData *tmfd) +{ + return ExecBRUpdateTriggersNew(estate, epqstate, relinfo, tupleid, + fdw_trigtuple, newslot, tmresult, tmfd, + false); +} + /* * Note: 'src_partinfo' and 'dst_partinfo', when non-NULL, refer to the source * and destination partitions, respectively, of a cross-partition update of @@ -3132,6 +3187,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, tupleid, LockTupleExclusive, oldslot, + false, NULL, NULL, NULL); @@ -3288,6 +3344,7 @@ GetTupleForTrigger(EState *estate, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot *oldslot, + bool do_epq_recheck, TupleTableSlot **epqslot, TM_Result *tmresultp, TM_FailureData *tmfdp) @@ -3347,29 +3404,30 @@ GetTupleForTrigger(EState *estate, if (tmfd.traversed) { /* - * Recheck the tuple using EPQ. For MERGE, we leave this - * to the caller (it must do additional rechecking, and - * might end up executing a different action entirely). + * Recheck the tuple using EPQ, if requested. Otherwise, + * just return that it was concurrently updated. */ - if (estate->es_plannedstmt->commandType == CMD_MERGE) + if (do_epq_recheck) { - if (tmresultp) - *tmresultp = TM_Updated; - return false; + *epqslot = EvalPlanQual(epqstate, + relation, + relinfo->ri_RangeTableIndex, + oldslot); + + /* + * If PlanQual failed for updated tuple - we must not + * process this tuple! + */ + if (TupIsNull(*epqslot)) + { + *epqslot = NULL; + return false; + } } - - *epqslot = EvalPlanQual(epqstate, - relation, - relinfo->ri_RangeTableIndex, - oldslot); - - /* - * If PlanQual failed for updated tuple - we must not - * process this tuple! - */ - if (TupIsNull(*epqslot)) + else { - *epqslot = NULL; + if (tmresultp) + *tmresultp = TM_Updated; return false; } } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index a0d1091ec014a..c230b666706e3 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1349,9 +1349,10 @@ ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, if (context->estate->es_insert_pending_result_relations != NIL) ExecPendingInserts(context->estate); - return ExecBRDeleteTriggers(context->estate, context->epqstate, - resultRelInfo, tupleid, oldtuple, - epqreturnslot, result, &context->tmfd); + return ExecBRDeleteTriggersNew(context->estate, context->epqstate, + resultRelInfo, tupleid, oldtuple, + epqreturnslot, result, &context->tmfd, + context->mtstate->operation == CMD_MERGE); } return true; @@ -1947,9 +1948,10 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, if (context->estate->es_insert_pending_result_relations != NIL) ExecPendingInserts(context->estate); - return ExecBRUpdateTriggers(context->estate, context->epqstate, - resultRelInfo, tupleid, oldtuple, slot, - result, &context->tmfd); + return ExecBRUpdateTriggersNew(context->estate, context->epqstate, + resultRelInfo, tupleid, oldtuple, slot, + result, &context->tmfd, + context->mtstate->operation == CMD_MERGE); } return true; diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 8a5a9fe642274..f9e4dc4f3cdbc 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -206,6 +206,15 @@ extern void ExecBSDeleteTriggers(EState *estate, extern void ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); +extern bool ExecBRDeleteTriggersNew(EState *estate, + EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot **epqslot, + TM_Result *tmresult, + TM_FailureData *tmfd, + bool is_merge_delete); extern bool ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, @@ -228,6 +237,15 @@ extern void ExecBSUpdateTriggers(EState *estate, extern void ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); +extern bool ExecBRUpdateTriggersNew(EState *estate, + EPQState *epqstate, + ResultRelInfo *relinfo, + ItemPointer tupleid, + HeapTuple fdw_trigtuple, + TupleTableSlot *newslot, + TM_Result *tmresult, + TM_FailureData *tmfd, + bool is_merge_update); extern bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, diff --git a/src/test/isolation/expected/merge-match-recheck.out b/src/test/isolation/expected/merge-match-recheck.out index 9a44a5959270b..90300f1db5ab3 100644 --- a/src/test/isolation/expected/merge-match-recheck.out +++ b/src/test/isolation/expected/merge-match-recheck.out @@ -241,19 +241,28 @@ starting permutation: update_bal1_tg merge_bal_tg c2 select1_tg c1 s2: NOTICE: Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg") step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1; step merge_bal_tg: - MERGE INTO target_tg t - USING (SELECT 1 as key) s - ON s.key = t.key - WHEN MATCHED AND balance < 100 THEN - UPDATE SET balance = balance * 2, val = t.val || ' when1' - WHEN MATCHED AND balance < 200 THEN - UPDATE SET balance = balance * 4, val = t.val || ' when2' - WHEN MATCHED AND balance < 300 THEN - UPDATE SET balance = balance * 8, val = t.val || ' when3'; + WITH t AS ( + MERGE INTO target_tg t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3' + RETURNING t.* + ) + SELECT * FROM t; step c2: COMMIT; s1: NOTICE: Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1") step merge_bal_tg: <... completed> +key|balance|status|val +---+-------+------+------------------------------------- + 1| 100|s1 |setup updated by update_bal1_tg when1 +(1 row) + step select1_tg: SELECT * FROM target_tg; key|balance|status|val ---+-------+------+------------------------------------- diff --git a/src/test/isolation/specs/merge-match-recheck.spec b/src/test/isolation/specs/merge-match-recheck.spec index 298b2bfdcd609..22688bb635525 100644 --- a/src/test/isolation/specs/merge-match-recheck.spec +++ b/src/test/isolation/specs/merge-match-recheck.spec @@ -99,15 +99,19 @@ step "merge_bal_pa" } step "merge_bal_tg" { - MERGE INTO target_tg t - USING (SELECT 1 as key) s - ON s.key = t.key - WHEN MATCHED AND balance < 100 THEN - UPDATE SET balance = balance * 2, val = t.val || ' when1' - WHEN MATCHED AND balance < 200 THEN - UPDATE SET balance = balance * 4, val = t.val || ' when2' - WHEN MATCHED AND balance < 300 THEN - UPDATE SET balance = balance * 8, val = t.val || ' when3'; + WITH t AS ( + MERGE INTO target_tg t + USING (SELECT 1 as key) s + ON s.key = t.key + WHEN MATCHED AND balance < 100 THEN + UPDATE SET balance = balance * 2, val = t.val || ' when1' + WHEN MATCHED AND balance < 200 THEN + UPDATE SET balance = balance * 4, val = t.val || ' when2' + WHEN MATCHED AND balance < 300 THEN + UPDATE SET balance = balance * 8, val = t.val || ' when3' + RETURNING t.* + ) + SELECT * FROM t; } step "merge_delete" From ff5f16efb2e27a5e4f6eed80ab142b6e4455e518 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Fri, 18 Jul 2025 00:04:52 +0300 Subject: [PATCH 729/796] Improve recovery test 046_checkpoint_logical_slot This commit improves 046_checkpoint_logical_slot in two aspects: - Add one pg_logical_emit_message() call to force the creation of a record that spawns across two pages. - Make the logic wait for the checkpoint completion. Discussion: https://postgr.es/m/CALDaNm34m36PDHzsU_GdcNXU0gLTfFY5rzh9GSQv%3Dw6B%2BQVNRQ%40mail.gmail.com Author: Alexander Korotkov Co-authored-by: Hayato Kuroda Reviewed-by: Michael Paquier --- src/test/recovery/t/046_checkpoint_logical_slot.pl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/recovery/t/046_checkpoint_logical_slot.pl b/src/test/recovery/t/046_checkpoint_logical_slot.pl index 0468d4609e43b..d2cf1cb4464e6 100644 --- a/src/test/recovery/t/046_checkpoint_logical_slot.pl +++ b/src/test/recovery/t/046_checkpoint_logical_slot.pl @@ -119,12 +119,18 @@ q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} ); +# Generate a long WAL record, spawning at least two pages for the follow-up +# post-recovery check. +$node->safe_psql('postgres', + q{select pg_logical_emit_message(false, '', repeat('123456789', 1000))}); + # Continue the checkpoint. +my $log_offset = -s $node->logfile; $node->safe_psql('postgres', q{select injection_points_wakeup('checkpoint-before-old-wal-removal')}); +$node->wait_for_log(qr/checkpoint complete/, $log_offset); -# Abruptly stop the server (1 second should be enough for the checkpoint -# to finish; it would be better). +# Abruptly stop the server. $node->stop('immediate'); $node->start; From 310214f50eaef1b28798d3da8cfd039641efdd0d Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sat, 19 Jul 2025 13:51:07 +0300 Subject: [PATCH 730/796] Improve the stability of the recovery test 047_checkpoint_physical_slot Currently, the comments in 047_checkpoint_physical_slot. It shows an incomplete intention to wait for checkpoint completion before performing an immediate database stop. However, an immediate node stop can occur both before and after checkpoint completion. Both cases should work correctly. But we would like the test to be more stable and deterministic. This is why this commit makes this test explicitly wait for the checkpoint completion log message. Discussion: https://postgr.es/m/CAPpHfdurV-j_e0pb%3DUFENAy3tyzxfF%2ByHveNDNQk2gM82WBU5A%40mail.gmail.com Discussion: https://postgr.es/m/aHXLep3OaX_vRTNQ%40paquier.xyz Author: Alexander Korotkov Reviewed-by: Michael Paquier Backpatch-through: 17 --- src/test/recovery/t/047_checkpoint_physical_slot.pl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/recovery/t/047_checkpoint_physical_slot.pl b/src/test/recovery/t/047_checkpoint_physical_slot.pl index 643cb6a7bcb9b..a909bf2ba0171 100644 --- a/src/test/recovery/t/047_checkpoint_physical_slot.pl +++ b/src/test/recovery/t/047_checkpoint_physical_slot.pl @@ -97,9 +97,11 @@ q{select pg_replication_slot_advance('slot_physical', pg_current_wal_lsn())} ); -# Continue the checkpoint. +# Continue the checkpoint and wait for its completion. +my $log_offset = -s $node->logfile; $node->safe_psql('postgres', q{select injection_points_wakeup('checkpoint-before-old-wal-removal')}); +$node->wait_for_log(qr/checkpoint complete/, $log_offset); my $restart_lsn_old = $node->safe_psql('postgres', q{select restart_lsn from pg_replication_slots where slot_name = 'slot_physical'} @@ -107,8 +109,7 @@ chomp($restart_lsn_old); note("restart lsn before stop: $restart_lsn_old"); -# Abruptly stop the server (1 second should be enough for the checkpoint -# to finish; it would be better). +# Abruptly stop the server. $node->stop('immediate'); $node->start; From 566aa2d953908ec41dad34dbbcd0cdeafbf9e052 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sat, 19 Jul 2025 13:44:01 +0300 Subject: [PATCH 731/796] Fix infinite wait when reading a partially written WAL record If a crash occurs while writing a WAL record that spans multiple pages, the recovery process marks the page with the XLP_FIRST_IS_OVERWRITE_CONTRECORD flag. However, logical decoding currently attempts to read the full WAL record based on its expected size before checking this flag, which can lead to an infinite wait if the remaining data is never written (e.g., no activity after crash). This patch updates the logic first to read the page header and check for the XLP_FIRST_IS_OVERWRITE_CONTRECORD flag before attempting to reconstruct the full WAL record. If the flag is set, decoding correctly identifies the record as incomplete and avoids waiting for WAL data that will never arrive. Discussion: https://postgr.es/m/CAAKRu_ZCOzQpEumLFgG_%2Biw3FTa%2BhJ4SRpxzaQBYxxM_ZAzWcA%40mail.gmail.com Discussion: https://postgr.es/m/CALDaNm34m36PDHzsU_GdcNXU0gLTfFY5rzh9GSQv%3Dw6B%2BQVNRQ%40mail.gmail.com Author: Vignesh C Reviewed-by: Hayato Kuroda Reviewed-by: Dilip Kumar Reviewed-by: Michael Paquier Reviewed-by: Alexander Korotkov Backpatch-through: 13 --- src/backend/access/transam/xlogreader.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index d7892ae5c625d..efe082b98c7e2 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -738,11 +738,12 @@ XLogDecodeNextRecord(XLogReaderState *state, bool nonblocking) /* Calculate pointer to beginning of next page */ targetPagePtr += XLOG_BLCKSZ; - /* Wait for the next page to become available */ - readOff = ReadPageInternal(state, targetPagePtr, - Min(total_len - gotlen + SizeOfXLogShortPHD, - XLOG_BLCKSZ)); - + /* + * Read the page header before processing the record data, so we + * can handle the case where the previous record ended as being a + * partial one. + */ + readOff = ReadPageInternal(state, targetPagePtr, SizeOfXLogShortPHD); if (readOff == XLREAD_WOULDBLOCK) return XLREAD_WOULDBLOCK; else if (readOff < 0) @@ -791,6 +792,15 @@ XLogDecodeNextRecord(XLogReaderState *state, bool nonblocking) goto err; } + /* Wait for the next page to become available */ + readOff = ReadPageInternal(state, targetPagePtr, + Min(total_len - gotlen + SizeOfXLogShortPHD, + XLOG_BLCKSZ)); + if (readOff == XLREAD_WOULDBLOCK) + return XLREAD_WOULDBLOCK; + else if (readOff < 0) + goto err; + /* Append the continuation from this page to the buffer */ pageHeaderSize = XLogPageHeaderSize(pageHeader); From 5358fcab37ebdc473371c18197eeb5d99e5f1d00 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Sun, 20 Jul 2025 11:58:31 +0900 Subject: [PATCH 732/796] doc: Document reopen of output file via SIGHUP in pg_recvlogical. When pg_recvlogical receives a SIGHUP signal, it closes the current output file and reopens a new one. This is useful since it allows us to rotate the output file by renaming the current file and sending a SIGHUP. This behavior was previously undocumented. This commit adds the missing documentation. Back-patch to all supported versions. Author: Fujii Masao Reviewed-by: Shinya Kato Discussion: https://postgr.es/m/0977fc4f-1523-4ecd-8a0e-391af4976367@oss.nttdata.com Backpatch-through: 13 --- doc/src/sgml/ref/pg_recvlogical.sgml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/src/sgml/ref/pg_recvlogical.sgml b/doc/src/sgml/ref/pg_recvlogical.sgml index 95eb14b6352e2..d38dc0b286e1d 100644 --- a/doc/src/sgml/ref/pg_recvlogical.sgml +++ b/doc/src/sgml/ref/pg_recvlogical.sgml @@ -53,6 +53,16 @@ PostgreSQL documentation (ControlC) or SIGTERM signal.
+ + + When pg_recvlogical receives + a SIGHUP signal, it closes the current output file + and opens a new one using the filename specified by + the option. This allows us to rotate + the output file by first renaming the current file and then sending + a SIGHUP signal to + pg_recvlogical. + From 80146d963e88c3270c6316d51c8ccfb0859fc7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Mon, 21 Jul 2025 11:34:10 +0200 Subject: [PATCH 733/796] pg_dump: include comments on not-null constraints on domains, too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit e5da0fe3c22b introduced catalog entries for not-null constraints on domains; but because commit b0e96f311985 (the original work for catalogued not-null constraints on tables) forgot to teach pg_dump to process the comments for them, this one also forgot. Add that now. We also need to teach repairDependencyLoop() about the new type of constraints being possible for domains. Backpatch-through: 17 Co-authored-by: jian he Co-authored-by: Álvaro Herrera Reported-by: jian he Discussion: https://postgr.es/m/CACJufxF-0bqVR=j4jonS6N2Ka6hHUpFyu3_3TWKNhOW_4yFSSg@mail.gmail.com --- src/bin/pg_dump/pg_dump.c | 160 +++++++++++++++++++++++-------- src/bin/pg_dump/pg_dump.h | 4 +- src/bin/pg_dump/pg_dump_sort.c | 15 +-- src/bin/pg_dump/t/002_pg_dump.pl | 30 +++++- 4 files changed, 160 insertions(+), 49 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 06f630a910dc8..2626dd250c8eb 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -47,6 +47,7 @@ #include "catalog/pg_authid_d.h" #include "catalog/pg_cast_d.h" #include "catalog/pg_class_d.h" +#include "catalog/pg_constraint_d.h" #include "catalog/pg_default_acl_d.h" #include "catalog/pg_largeobject_d.h" #include "catalog/pg_largeobject_metadata_d.h" @@ -5929,6 +5930,7 @@ getTypes(Archive *fout, int *numTypes) */ tyinfo[i].nDomChecks = 0; tyinfo[i].domChecks = NULL; + tyinfo[i].notnull = NULL; if ((tyinfo[i].dobj.dump & DUMP_COMPONENT_DEFINITION) && tyinfo[i].typtype == TYPTYPE_DOMAIN) getDomainConstraints(fout, &(tyinfo[i])); @@ -7949,27 +7951,33 @@ addConstrChildIdxDeps(DumpableObject *dobj, const IndxInfo *refidx) static void getDomainConstraints(Archive *fout, TypeInfo *tyinfo) { - int i; ConstraintInfo *constrinfo; PQExpBuffer query = createPQExpBuffer(); PGresult *res; int i_tableoid, i_oid, i_conname, - i_consrc; + i_consrc, + i_convalidated, + i_contype; int ntups; if (!fout->is_prepared[PREPQUERY_GETDOMAINCONSTRAINTS]) { - /* Set up query for constraint-specific details */ - appendPQExpBufferStr(query, - "PREPARE getDomainConstraints(pg_catalog.oid) AS\n" - "SELECT tableoid, oid, conname, " - "pg_catalog.pg_get_constraintdef(oid) AS consrc, " - "convalidated " - "FROM pg_catalog.pg_constraint " - "WHERE contypid = $1 AND contype = 'c' " - "ORDER BY conname"); + /* + * Set up query for constraint-specific details. For servers 17 and + * up, domains have constraints of type 'n' as well as 'c', otherwise + * just the latter. + */ + appendPQExpBuffer(query, + "PREPARE getDomainConstraints(pg_catalog.oid) AS\n" + "SELECT tableoid, oid, conname, " + "pg_catalog.pg_get_constraintdef(oid) AS consrc, " + "convalidated, contype " + "FROM pg_catalog.pg_constraint " + "WHERE contypid = $1 AND contype IN (%s) " + "ORDER BY conname", + fout->remoteVersion < 170000 ? "'c'" : "'c', 'n'"); ExecuteSqlStatement(fout, query->data); @@ -7988,33 +7996,50 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) i_oid = PQfnumber(res, "oid"); i_conname = PQfnumber(res, "conname"); i_consrc = PQfnumber(res, "consrc"); + i_convalidated = PQfnumber(res, "convalidated"); + i_contype = PQfnumber(res, "contype"); constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo)); - - tyinfo->nDomChecks = ntups; tyinfo->domChecks = constrinfo; - for (i = 0; i < ntups; i++) + /* 'i' tracks result rows; 'j' counts CHECK constraints */ + for (int i = 0, j = 0; i < ntups; i++) { - bool validated = PQgetvalue(res, i, 4)[0] == 't'; - - constrinfo[i].dobj.objType = DO_CONSTRAINT; - constrinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); - constrinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); - AssignDumpId(&constrinfo[i].dobj); - constrinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_conname)); - constrinfo[i].dobj.namespace = tyinfo->dobj.namespace; - constrinfo[i].contable = NULL; - constrinfo[i].condomain = tyinfo; - constrinfo[i].contype = 'c'; - constrinfo[i].condef = pg_strdup(PQgetvalue(res, i, i_consrc)); - constrinfo[i].confrelid = InvalidOid; - constrinfo[i].conindex = 0; - constrinfo[i].condeferrable = false; - constrinfo[i].condeferred = false; - constrinfo[i].conislocal = true; - - constrinfo[i].separate = !validated; + bool validated = PQgetvalue(res, i, i_convalidated)[0] == 't'; + char contype = (PQgetvalue(res, i, i_contype))[0]; + ConstraintInfo *constraint; + + if (contype == CONSTRAINT_CHECK) + { + constraint = &constrinfo[j++]; + tyinfo->nDomChecks++; + } + else + { + Assert(contype == CONSTRAINT_NOTNULL); + Assert(tyinfo->notnull == NULL); + /* use last item in array for the not-null constraint */ + tyinfo->notnull = &(constrinfo[ntups - 1]); + constraint = tyinfo->notnull; + } + + constraint->dobj.objType = DO_CONSTRAINT; + constraint->dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + constraint->dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&(constraint->dobj)); + constraint->dobj.name = pg_strdup(PQgetvalue(res, i, i_conname)); + constraint->dobj.namespace = tyinfo->dobj.namespace; + constraint->contable = NULL; + constraint->condomain = tyinfo; + constraint->contype = contype; + constraint->condef = pg_strdup(PQgetvalue(res, i, i_consrc)); + constraint->confrelid = InvalidOid; + constraint->conindex = 0; + constraint->condeferrable = false; + constraint->condeferred = false; + constraint->conislocal = true; + + constraint->separate = !validated; /* * Make the domain depend on the constraint, ensuring it won't be @@ -8023,8 +8048,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) * anyway, so this doesn't matter. */ if (validated) - addObjectDependency(&tyinfo->dobj, - constrinfo[i].dobj.dumpId); + addObjectDependency(&tyinfo->dobj, constraint->dobj.dumpId); } PQclear(res); @@ -11557,8 +11581,36 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) appendPQExpBuffer(q, " COLLATE %s", fmtQualifiedDumpable(coll)); } + /* + * Print a not-null constraint if there's one. In servers older than 17 + * these don't have names, so just print it unadorned; in newer ones they + * do, but most of the time it's going to be the standard generated one, + * so omit the name in that case also. + */ if (typnotnull[0] == 't') - appendPQExpBufferStr(q, " NOT NULL"); + { + if (fout->remoteVersion < 170000 || tyinfo->notnull == NULL) + appendPQExpBufferStr(q, " NOT NULL"); + else + { + ConstraintInfo *notnull = tyinfo->notnull; + + if (!notnull->separate) + { + char *default_name; + + /* XXX should match ChooseConstraintName better */ + default_name = psprintf("%s_not_null", tyinfo->dobj.name); + + if (strcmp(default_name, notnull->dobj.name) == 0) + appendPQExpBufferStr(q, " NOT NULL"); + else + appendPQExpBuffer(q, " CONSTRAINT %s %s", + fmtId(notnull->dobj.name), notnull->condef); + free(default_name); + } + } + } if (typdefault != NULL) { @@ -11578,7 +11630,7 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) { ConstraintInfo *domcheck = &(tyinfo->domChecks[i]); - if (!domcheck->separate) + if (!domcheck->separate && domcheck->contype == 'c') appendPQExpBuffer(q, "\n\tCONSTRAINT %s %s", fmtId(domcheck->dobj.name), domcheck->condef); } @@ -11642,6 +11694,25 @@ dumpDomain(Archive *fout, const TypeInfo *tyinfo) destroyPQExpBuffer(conprefix); } + /* + * And a comment on the not-null constraint, if there's one -- but only if + * the constraint itself was dumped here + */ + if (tyinfo->notnull != NULL && !tyinfo->notnull->separate) + { + PQExpBuffer conprefix = createPQExpBuffer(); + + appendPQExpBuffer(conprefix, "CONSTRAINT %s ON DOMAIN", + fmtId(tyinfo->notnull->dobj.name)); + + if (tyinfo->notnull->dobj.dump & DUMP_COMPONENT_COMMENT) + dumpComment(fout, conprefix->data, qtypname, + tyinfo->dobj.namespace->dobj.name, + tyinfo->rolname, + tyinfo->notnull->dobj.catId, 0, tyinfo->dobj.dumpId); + destroyPQExpBuffer(conprefix); + } + destroyPQExpBuffer(q); destroyPQExpBuffer(delq); destroyPQExpBuffer(query); @@ -17336,14 +17407,23 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) .dropStmt = delq->data)); } } - else if (coninfo->contype == 'c' && tbinfo == NULL) + else if (tbinfo == NULL) { - /* CHECK constraint on a domain */ + /* CHECK, NOT NULL constraint on a domain */ TypeInfo *tyinfo = coninfo->condomain; + Assert(coninfo->contype == 'c' || coninfo->contype == 'n'); + /* Ignore if not to be dumped separately */ if (coninfo->separate) { + const char *keyword; + + if (coninfo->contype == 'c') + keyword = "CHECK CONSTRAINT"; + else + keyword = "CONSTRAINT"; + appendPQExpBuffer(q, "ALTER DOMAIN %s\n", fmtQualifiedDumpable(tyinfo)); appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n", @@ -17362,7 +17442,7 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) ARCHIVE_OPTS(.tag = tag, .namespace = tyinfo->dobj.namespace->dobj.name, .owner = tyinfo->rolname, - .description = "CHECK CONSTRAINT", + .description = keyword, .section = SECTION_POST_DATA, .createStmt = q->data, .dropStmt = delq->data)); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 1d352fe12d1ac..243942369ea25 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -215,7 +215,9 @@ typedef struct _typeInfo bool isDefined; /* true if typisdefined */ /* If needed, we'll create a "shell type" entry for it; link that here: */ struct _shellTypeInfo *shellType; /* shell-type entry, or NULL */ - /* If it's a domain, we store links to its constraints here: */ + /* If it's a domain, its not-null constraint is here: */ + struct _constraintInfo *notnull; + /* If it's a domain, we store links to its CHECK constraints here: */ int nDomChecks; struct _constraintInfo *domChecks; } TypeInfo; diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 31bdb91a585ad..d85f3d0e2d833 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -884,7 +884,7 @@ repairTableAttrDefMultiLoop(DumpableObject *tableobj, } /* - * CHECK constraints on domains work just like those on tables ... + * CHECK, NOT NULL constraints on domains work just like those on tables ... */ static void repairDomainConstraintLoop(DumpableObject *domainobj, @@ -1135,11 +1135,12 @@ repairDependencyLoop(DumpableObject **loop, } } - /* Domain and CHECK constraint */ + /* Domain and CHECK or NOT NULL constraint */ if (nLoop == 2 && loop[0]->objType == DO_TYPE && loop[1]->objType == DO_CONSTRAINT && - ((ConstraintInfo *) loop[1])->contype == 'c' && + (((ConstraintInfo *) loop[1])->contype == 'c' || + ((ConstraintInfo *) loop[1])->contype == 'n') && ((ConstraintInfo *) loop[1])->condomain == (TypeInfo *) loop[0]) { repairDomainConstraintLoop(loop[0], loop[1]); @@ -1148,14 +1149,15 @@ repairDependencyLoop(DumpableObject **loop, if (nLoop == 2 && loop[1]->objType == DO_TYPE && loop[0]->objType == DO_CONSTRAINT && - ((ConstraintInfo *) loop[0])->contype == 'c' && + (((ConstraintInfo *) loop[0])->contype == 'c' || + ((ConstraintInfo *) loop[0])->contype == 'n') && ((ConstraintInfo *) loop[0])->condomain == (TypeInfo *) loop[1]) { repairDomainConstraintLoop(loop[1], loop[0]); return; } - /* Indirect loop involving domain and CHECK constraint */ + /* Indirect loop involving domain and CHECK or NOT NULL constraint */ if (nLoop > 2) { for (i = 0; i < nLoop; i++) @@ -1165,7 +1167,8 @@ repairDependencyLoop(DumpableObject **loop, for (j = 0; j < nLoop; j++) { if (loop[j]->objType == DO_CONSTRAINT && - ((ConstraintInfo *) loop[j])->contype == 'c' && + (((ConstraintInfo *) loop[j])->contype == 'c' || + ((ConstraintInfo *) loop[j])->contype == 'n') && ((ConstraintInfo *) loop[j])->condomain == (TypeInfo *) loop[i]) { repairDomainConstraintMultiLoop(loop[i], loop[j]); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 5bcc2244d5831..58306d307ecee 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2041,17 +2041,19 @@ create_sql => 'CREATE DOMAIN dump_test.us_postal_code AS TEXT COLLATE "C" DEFAULT \'10014\' + CONSTRAINT nn NOT NULL CHECK(VALUE ~ \'^\d{5}$\' OR VALUE ~ \'^\d{5}-\d{4}$\'); + COMMENT ON CONSTRAINT nn + ON DOMAIN dump_test.us_postal_code IS \'not null\'; COMMENT ON CONSTRAINT us_postal_code_check ON DOMAIN dump_test.us_postal_code IS \'check it\';', regexp => qr/^ - \QCREATE DOMAIN dump_test.us_postal_code AS text COLLATE pg_catalog."C" DEFAULT '10014'::text\E\n\s+ + \QCREATE DOMAIN dump_test.us_postal_code AS text COLLATE pg_catalog."C" CONSTRAINT nn NOT NULL DEFAULT '10014'::text\E\n\s+ \QCONSTRAINT us_postal_code_check CHECK \E \Q(((VALUE ~ '^\d{5}\E \$\Q'::text) OR (VALUE ~ '^\d{5}-\d{4}\E\$ \Q'::text)));\E(.|\n)* - \QCOMMENT ON CONSTRAINT us_postal_code_check ON DOMAIN dump_test.us_postal_code IS 'check it';\E /xm, like => { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, @@ -2061,6 +2063,30 @@ }, }, + 'COMMENT ON CONSTRAINT ON DOMAIN (1)' => { + regexp => qr/^ + \QCOMMENT ON CONSTRAINT nn ON DOMAIN dump_test.us_postal_code IS 'not null';\E + /xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + + 'COMMENT ON CONSTRAINT ON DOMAIN (2)' => { + regexp => qr/^ + \QCOMMENT ON CONSTRAINT us_postal_code_check ON DOMAIN dump_test.us_postal_code IS 'check it';\E + /xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE FUNCTION dump_test.pltestlang_call_handler' => { create_order => 17, create_sql => 'CREATE FUNCTION dump_test.pltestlang_call_handler() From 28e525496de18591ab26be879023f8c8e096d845 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 22 Jul 2025 14:00:05 +0900 Subject: [PATCH 734/796] ecpg: Fix NULL pointer dereference during connection lookup ECPGconnect() caches established connections to the server, supporting the case of a NULL connection name when a database name is not specified by its caller. A follow-up call to ECPGget_PGconn() to get an established connection from the cached set with a non-NULL name could cause a NULL pointer dereference if a NULL connection was listed in the cache and checked for a match. At least two connections are necessary to reproduce the issue: one with a NULL name and one with a non-NULL name. Author: Aleksander Alekseev Discussion: https://postgr.es/m/CAJ7c6TNvFTPUTZQuNAoqgzaSGz-iM4XR61D7vEj5PsQXwg2RyA@mail.gmail.com Backpatch-through: 13 --- src/interfaces/ecpg/ecpglib/connect.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c index 8afb1f0a26fba..b912441f12e31 100644 --- a/src/interfaces/ecpg/ecpglib/connect.c +++ b/src/interfaces/ecpg/ecpglib/connect.c @@ -58,7 +58,12 @@ ecpg_get_connection_nr(const char *connection_name) for (con = all_connections; con != NULL; con = con->next) { - if (strcmp(connection_name, con->name) == 0) + /* + * Check for the case of a NULL connection name, stored as such in + * the connection information by ECPGconnect() when the database + * name is not specified by its caller. + */ + if (con->name != NULL && strcmp(connection_name, con->name) == 0) break; } ret = con; From b984588a483bf0d0d6b06729c1c2eed749d1c937 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Tue, 22 Jul 2025 14:34:22 +0900 Subject: [PATCH 735/796] doc: Inform about aminsertcleanup optional NULLness This index AM callback has been introduced in c1ec02be1d79 and it is optional, currently only being used by BRIN. Optional callbacks are documented with NULL as possible value in amapi.h and indexam.sgml, but this callback has missed this part of the description. Reported-by: Peter Smith Reviewed-by: Japin Li Discussion: https://postgr.es/m/CAHut+PvgYcPmPDi1YdHMJY5upnyGRpc0N8pk1xNB11xDSBwNog@mail.gmail.com Backpatch-through: 17 --- doc/src/sgml/indexam.sgml | 2 +- src/include/access/amapi.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index e3c1539a1e3bc..d0dbf017a9e1f 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -141,7 +141,7 @@ typedef struct IndexAmRoutine ambuild_function ambuild; ambuildempty_function ambuildempty; aminsert_function aminsert; - aminsertcleanup_function aminsertcleanup; + aminsertcleanup_function aminsertcleanup; /* can be NULL */ ambulkdelete_function ambulkdelete; amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index d3e5e5d591882..098014508160e 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -271,7 +271,7 @@ typedef struct IndexAmRoutine ambuild_function ambuild; ambuildempty_function ambuildempty; aminsert_function aminsert; - aminsertcleanup_function aminsertcleanup; + aminsertcleanup_function aminsertcleanup; /* can be NULL */ ambulkdelete_function ambulkdelete; amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ From 3922b2dd47db86d0043e1cf164311f6fd1e42d1c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 23 Jul 2025 15:44:29 -0400 Subject: [PATCH 736/796] Fix build breakage on Solaris-alikes with late-model GCC. Solaris has never bothered to add "const" to the second argument of PAM conversation procs, as all other Unixen did decades ago. This resulted in an "incompatible pointer" compiler warning when building --with-pam, but had no more serious effect than that, so we never did anything about it. However, as of GCC 14 the case is an error not warning by default. To complicate matters, recent OpenIndiana (and maybe illumos in general?) *does* supply the "const" by default, so we can't just assume that platforms using our solaris template need help. What we can do, short of building a configure-time probe, is to make solaris.h #define _PAM_LEGACY_NONCONST, which causes OpenIndiana's pam_appl.h to revert to the traditional definition, and hopefully will have no effect anywhere else. Then we can use that same symbol to control whether we include "const" in the declaration of pam_passwd_conv_proc(). Bug: #18995 Reported-by: Andrew Watkins Author: Tom Lane Discussion: https://postgr.es/m/18995-82058da9ab4337a7@postgresql.org Backpatch-through: 13 --- src/backend/libpq/auth.c | 12 ++++++++++-- src/include/port/solaris.h | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 2b607c52704ca..e0576f801d85d 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -93,8 +93,16 @@ static int auth_peer(hbaPort *port); #define PGSQL_PAM_SERVICE "postgresql" /* Service name passed to PAM */ +/* Work around original Solaris' lack of "const" in the conv_proc signature */ +#ifdef _PAM_LEGACY_NONCONST +#define PG_PAM_CONST +#else +#define PG_PAM_CONST const +#endif + static int CheckPAMAuth(Port *port, const char *user, const char *password); -static int pam_passwd_conv_proc(int num_msg, const struct pam_message **msg, +static int pam_passwd_conv_proc(int num_msg, + PG_PAM_CONST struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); static struct pam_conv pam_passw_conv = { @@ -1919,7 +1927,7 @@ auth_peer(hbaPort *port) */ static int -pam_passwd_conv_proc(int num_msg, const struct pam_message **msg, +pam_passwd_conv_proc(int num_msg, PG_PAM_CONST struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { const char *passwd; diff --git a/src/include/port/solaris.h b/src/include/port/solaris.h index e63a3bd824d6d..8ff40007c7f6a 100644 --- a/src/include/port/solaris.h +++ b/src/include/port/solaris.h @@ -24,3 +24,12 @@ #if defined(__i386__) #include #endif + +/* + * On original Solaris, PAM conversation procs lack a "const" in their + * declaration; but recent OpenIndiana versions put it there by default. + * The least messy way to deal with this is to define _PAM_LEGACY_NONCONST, + * which causes OpenIndiana to declare pam_conv per the Solaris tradition, + * and also use that symbol to control omitting the "const" in our own code. + */ +#define _PAM_LEGACY_NONCONST 1 From 67a5f3b8633ae84aeb49dad7b0b9e98cd7477629 Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Thu, 24 Jul 2025 08:36:31 +0000 Subject: [PATCH 737/796] Fix duplicate transaction replay during pg_createsubscriber. Previously, the tool could replay the same transaction twice, once during recovery, then again during replication after the subscriber was set up. This occurred because the same recovery_target_lsn was used both to finalize recovery and to start replication. If recovery_target_inclusive = true, the transaction at that LSN would be applied during recovery and then sent again by the publisher leading to duplication. To prevent this, we now set recovery_target_inclusive = false. This ensures the transaction at recovery_target_lsn is not reapplied during recovery, avoiding duplication when replication begins. Bug #18897 Reported-by: Zane Duffield Author: Shlok Kyal Reviewed-by: vignesh C Reviewed-by: Amit Kapila Backpatch-through: 17, where it was introduced Discussion: https://postgr.es/m/18897-d3db67535860dddb@postgresql.org --- src/bin/pg_basebackup/pg_createsubscriber.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c index 677c0cd0843bf..067a04ec6c329 100644 --- a/src/bin/pg_basebackup/pg_createsubscriber.c +++ b/src/bin/pg_basebackup/pg_createsubscriber.c @@ -1206,8 +1206,17 @@ setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir, const c appendPQExpBuffer(recoveryconfcontents, "recovery_target = ''\n"); appendPQExpBuffer(recoveryconfcontents, "recovery_target_timeline = 'latest'\n"); + + /* + * Set recovery_target_inclusive = false to avoid reapplying the + * transaction committed at 'lsn' after subscription is enabled. This is + * because the provided 'lsn' is also used as the replication start point + * for the subscription. So, the server can send the transaction committed + * at that 'lsn' after replication is started which can lead to applying + * the same transaction twice if we keep recovery_target_inclusive = true. + */ appendPQExpBuffer(recoveryconfcontents, - "recovery_target_inclusive = true\n"); + "recovery_target_inclusive = false\n"); appendPQExpBuffer(recoveryconfcontents, "recovery_target_action = promote\n"); appendPQExpBuffer(recoveryconfcontents, "recovery_target_name = ''\n"); From 8885959f9f2d8d259b5cc4c9de3d8a9df43b649f Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sun, 27 Jul 2025 15:10:01 +0300 Subject: [PATCH 738/796] Limit checkpointer requests queue size If the number of sync requests is big enough, the palloc() call in AbsorbSyncRequests() will attempt to allocate more than 1 GB of memory, resulting in failure. This can lead to an infinite loop in the checkpointer process, as it repeatedly fails to absorb the pending requests. This commit limits the checkpointer requests queue size to 10M items. In addition to preventing the palloc() failure, this change helps to avoid long queue processing time. Also, this commit is for backpathing only. The master branch receives a more invasive yet comprehensive fix for this problem. Discussion: https://postgr.es/m/db4534f83a22a29ab5ee2566ad86ca92%40postgrespro.ru Backpatch-through: 13 --- src/backend/postmaster/checkpointer.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 199f008bcda81..c5e86d3317ede 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -130,6 +130,9 @@ static CheckpointerShmemStruct *CheckpointerShmem; /* interval for calling AbsorbSyncRequests in CheckpointWriteDelay */ #define WRITES_PER_ABSORB 1000 +/* Max number of requests the checkpointer request queue can hold */ +#define MAX_CHECKPOINT_REQUESTS 10000000 + /* * GUC parameters */ @@ -914,7 +917,7 @@ CheckpointerShmemInit(void) */ MemSet(CheckpointerShmem, 0, size); SpinLockInit(&CheckpointerShmem->ckpt_lck); - CheckpointerShmem->max_requests = NBuffers; + CheckpointerShmem->max_requests = Min(NBuffers, MAX_CHECKPOINT_REQUESTS); ConditionVariableInit(&CheckpointerShmem->start_cv); ConditionVariableInit(&CheckpointerShmem->done_cv); } From c846d1e314434927057c98ee226fd4868f155184 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 28 Jul 2025 16:50:41 -0400 Subject: [PATCH 739/796] Avoid regression in the size of XML input that we will accept. This mostly reverts commit 6082b3d5d, "Use xmlParseInNodeContext not xmlParseBalancedChunkMemory". It turns out that xmlParseInNodeContext will reject text chunks exceeding 10MB, while (in most libxml2 versions) xmlParseBalancedChunkMemory will not. The bleeding-edge libxml2 bug that we needed to work around a year ago is presumably no longer a factor, and the argument that xmlParseBalancedChunkMemory is semi-deprecated is not enough to justify a functionality regression. Hence, go back to doing it the old way. Reported-by: Michael Paquier Author: Michael Paquier Co-authored-by: Erik Wienhold Reviewed-by: Tom Lane Discussion: https://postgr.es/m/aIGknLuc8b8ega2X@paquier.xyz Backpatch-through: 13 --- src/backend/utils/adt/xml.c | 68 ++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 5f722da9a0ee7..bf3dbe72cda1f 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -1730,7 +1730,7 @@ xml_doctype_in_content(const xmlChar *str) * xmloption_arg, but a DOCTYPE node in the input can force DOCUMENT mode). * * If parsed_nodes isn't NULL and we parse in CONTENT mode, the list - * of parsed nodes from the xmlParseInNodeContext call will be returned + * of parsed nodes from the xmlParseBalancedChunkMemory call will be returned * to *parsed_nodes. (It is caller's responsibility to free that.) * * Errors normally result in ereport(ERROR), but if escontext is an @@ -1756,6 +1756,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, PgXmlErrorContext *xmlerrcxt; volatile xmlParserCtxtPtr ctxt = NULL; volatile xmlDocPtr doc = NULL; + volatile int save_keep_blanks = -1; /* * This step looks annoyingly redundant, but we must do it to have a @@ -1783,7 +1784,6 @@ xml_parse(text *data, XmlOptionType xmloption_arg, PG_TRY(); { bool parse_as_document = false; - int options; int res_code; size_t count = 0; xmlChar *version = NULL; @@ -1814,18 +1814,6 @@ xml_parse(text *data, XmlOptionType xmloption_arg, parse_as_document = true; } - /* - * Select parse options. - * - * Note that here we try to apply DTD defaults (XML_PARSE_DTDATTR) - * according to SQL/XML:2008 GR 10.16.7.d: 'Default values defined by - * internal DTD are applied'. As for external DTDs, we try to support - * them too (see SQL/XML:2008 GR 10.16.7.e), but that doesn't really - * happen because xmlPgEntityLoader prevents it. - */ - options = XML_PARSE_NOENT | XML_PARSE_DTDATTR - | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS); - /* initialize output parameters */ if (parsed_xmloptiontype != NULL) *parsed_xmloptiontype = parse_as_document ? XMLOPTION_DOCUMENT : @@ -1835,11 +1823,26 @@ xml_parse(text *data, XmlOptionType xmloption_arg, if (parse_as_document) { + int options; + + /* set up parser context used by xmlCtxtReadDoc */ ctxt = xmlNewParserCtxt(); if (ctxt == NULL || xmlerrcxt->err_occurred) xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, "could not allocate parser context"); + /* + * Select parse options. + * + * Note that here we try to apply DTD defaults (XML_PARSE_DTDATTR) + * according to SQL/XML:2008 GR 10.16.7.d: 'Default values defined + * by internal DTD are applied'. As for external DTDs, we try to + * support them too (see SQL/XML:2008 GR 10.16.7.e), but that + * doesn't really happen because xmlPgEntityLoader prevents it. + */ + options = XML_PARSE_NOENT | XML_PARSE_DTDATTR + | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS); + doc = xmlCtxtReadDoc(ctxt, utf8string, NULL, /* no URL */ "UTF-8", @@ -1861,10 +1864,7 @@ xml_parse(text *data, XmlOptionType xmloption_arg, } else { - xmlNodePtr root; - xmlNodePtr oldroot PG_USED_FOR_ASSERTS_ONLY; - - /* set up document with empty root node to be the context node */ + /* set up document that xmlParseBalancedChunkMemory will add to */ doc = xmlNewDoc(version); if (doc == NULL || xmlerrcxt->err_occurred) xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, @@ -1877,36 +1877,23 @@ xml_parse(text *data, XmlOptionType xmloption_arg, "could not allocate XML document"); doc->standalone = standalone; - root = xmlNewNode(NULL, (const xmlChar *) "content-root"); - if (root == NULL || xmlerrcxt->err_occurred) - xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY, - "could not allocate xml node"); - - /* - * This attaches root to doc, so we need not free it separately; - * and there can't yet be any old root to free. - */ - oldroot = xmlDocSetRootElement(doc, root); - Assert(oldroot == NULL); + /* set parse options --- have to do this the ugly way */ + save_keep_blanks = xmlKeepBlanksDefault(preserve_whitespace ? 1 : 0); /* allow empty content */ if (*(utf8string + count)) { xmlNodePtr node_list = NULL; - xmlParserErrors res; - - res = xmlParseInNodeContext(root, - (char *) utf8string + count, - strlen((char *) utf8string + count), - options, - &node_list); - if (res != XML_ERR_OK || xmlerrcxt->err_occurred) + res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, + utf8string + count, + &node_list); + if (res_code != 0 || xmlerrcxt->err_occurred) { - xmlFreeNodeList(node_list); xml_errsave(escontext, xmlerrcxt, ERRCODE_INVALID_XML_CONTENT, "invalid XML content"); + xmlFreeNodeList(node_list); goto fail; } @@ -1922,6 +1909,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, } PG_CATCH(); { + if (save_keep_blanks != -1) + xmlKeepBlanksDefault(save_keep_blanks); if (doc != NULL) xmlFreeDoc(doc); if (ctxt != NULL) @@ -1933,6 +1922,9 @@ xml_parse(text *data, XmlOptionType xmloption_arg, } PG_END_TRY(); + if (save_keep_blanks != -1) + xmlKeepBlanksDefault(save_keep_blanks); + if (ctxt != NULL) xmlFreeParserCtxt(ctxt); From fee4cc9d9431b1a34bd9164b7226b7323dd22dea Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Tue, 29 Jul 2025 10:41:13 +0300 Subject: [PATCH 740/796] Clarify documentation for the initcap function This commit documents differences in the definition of word separators for the initcap function between libc and ICU locale providers. Backpatch to all supported branches. Discussion: https://postgr.es/m/804cc10ef95d4d3b298e76b181fd9437%40postgrespro.ru Author: Oleg Tselebrovskiy Backpatch-through: 13 --- doc/src/sgml/func.sgml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index fc4b95378715e..91557f4fb0fab 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -3110,8 +3110,11 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in Converts the first letter of each word to upper case and the - rest to lower case. Words are sequences of alphanumeric - characters separated by non-alphanumeric characters. + rest to lower case. When using the libc locale + provider, words are sequences of alphanumeric characters separated + by non-alphanumeric characters; when using the ICU locale provider, + words are separated according to + Unicode Standard Annex #29. initcap('hi THOMAS') From b1b1b668f727c21ef6ac00f2b95df3f08122eadf Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 29 Jul 2025 12:47:19 -0400 Subject: [PATCH 741/796] Remove unnecessary complication around xmlParseBalancedChunkMemory. When I prepared 71c0921b6 et al yesterday, I was thinking that the logic involving explicitly freeing the node_list output was still needed to dodge leakage bugs in libxml2. But I was misremembering: we introduced that only because with early 2.13.x releases we could not trust xmlParseBalancedChunkMemory's result code, so we had to look to see if a node list was returned or not. There's no reason to believe that xmlParseBalancedChunkMemory will fail to clean up the node list when required, so simplify. (This essentially completes reverting all the non-cosmetic changes in 6082b3d5d.) Reported-by: Jim Jones Author: Tom Lane Discussion: https://postgr.es/m/997668.1753802857@sss.pgh.pa.us Backpatch-through: 13 --- src/backend/utils/adt/xml.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index bf3dbe72cda1f..6e012b71fbe14 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -1883,24 +1883,16 @@ xml_parse(text *data, XmlOptionType xmloption_arg, /* allow empty content */ if (*(utf8string + count)) { - xmlNodePtr node_list = NULL; - res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, utf8string + count, - &node_list); + parsed_nodes); if (res_code != 0 || xmlerrcxt->err_occurred) { xml_errsave(escontext, xmlerrcxt, ERRCODE_INVALID_XML_CONTENT, "invalid XML content"); - xmlFreeNodeList(node_list); goto fail; } - - if (parsed_nodes != NULL) - *parsed_nodes = node_list; - else - xmlFreeNodeList(node_list); } } From 4d79ddf4104277079feb12d713b3c63b144a820a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 29 Jul 2025 15:17:41 -0400 Subject: [PATCH 742/796] Don't put library-supplied -L/-I switches before user-supplied ones. For many optional libraries, we extract the -L and -l switches needed to link the library from a helper program such as llvm-config. In some cases we put the resulting -L switches into LDFLAGS ahead of -L switches specified via --with-libraries. That risks breaking the user's intention for --with-libraries. It's not such a problem if the library's -L switch points to a directory containing only that library, but on some platforms a library helper may "helpfully" offer a switch such as -L/usr/lib that points to a directory holding all standard libraries. If the user specified --with-libraries in hopes of overriding the standard build of some library, the -L/usr/lib switch prevents that from happening since it will come before the user-specified directory. To fix, avoid inserting these switches directly into LDFLAGS during configure, instead adding them to LIBDIRS or SHLIB_LINK. They will still eventually get added to LDFLAGS, but only after the switches coming from --with-libraries. The same problem exists for -I switches: those coming from --with-includes should appear before any coming from helper programs such as llvm-config. We have not heard field complaints about this case, but it seems certain that a user attempting to override a standard library could have issues. The changes for this go well beyond configure itself, however, because many Makefiles have occasion to manipulate CPPFLAGS to insert locally-desirable -I switches, and some of them got it wrong. The correct ordering is any -I switches pointing at within-the- source-tree-or-build-tree directories, then those from the tree-wide CPPFLAGS, then those from helper programs. There were several places that risked pulling in a system-supplied copy of libpq headers, for example, instead of the in-tree files. (Commit cb36f8ec2 fixed one instance of that a few months ago, but this exercise found more.) The Meson build scripts may or may not have any comparable problems, but I'll leave it to someone else to investigate that. Reported-by: Charles Samborski Author: Tom Lane Discussion: https://postgr.es/m/70f2155f-27ca-4534-b33d-7750e20633d7@demurgos.net Backpatch-through: 13 --- config/llvm.m4 | 4 ++-- configure | 20 ++++++++++---------- configure.ac | 18 +++++++++--------- src/Makefile.global.in | 2 +- src/backend/jit/llvm/Makefile | 2 +- src/bin/initdb/Makefile | 2 +- src/interfaces/libpq/Makefile | 2 +- src/pl/plpython/Makefile | 2 +- src/pl/tcl/Makefile | 2 +- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/config/llvm.m4 b/config/llvm.m4 index f25efa25ed6a6..6ee9e55eefe38 100644 --- a/config/llvm.m4 +++ b/config/llvm.m4 @@ -4,7 +4,7 @@ # ----------------- # # Look for the LLVM installation, check that it's new enough, set the -# corresponding LLVM_{CFLAGS,CXXFLAGS,BINPATH} and LDFLAGS +# corresponding LLVM_{CFLAGS,CXXFLAGS,BINPATH,LIBS} # variables. Also verify that CLANG is available, to transform C # into bitcode. # @@ -55,7 +55,7 @@ AC_DEFUN([PGAC_LLVM_SUPPORT], for pgac_option in `$LLVM_CONFIG --link-static --ldflags`; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LLVM_LIBS="$LLVM_LIBS $pgac_option";; esac done diff --git a/configure b/configure index dbe565d73d6a2..588d1cf0b9ca7 100755 --- a/configure +++ b/configure @@ -5225,7 +5225,7 @@ fi for pgac_option in `$LLVM_CONFIG --link-static --ldflags`; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LLVM_LIBS="$LLVM_LIBS $pgac_option";; esac done @@ -9067,12 +9067,12 @@ fi # Note the user could also set XML2_CFLAGS/XML2_LIBS directly for pgac_option in $XML2_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $XML2_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -9297,12 +9297,12 @@ fi # note that -llz4 will be added by AC_CHECK_LIB below. for pgac_option in $LZ4_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $LZ4_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -9438,12 +9438,12 @@ fi # note that -lzstd will be added by AC_CHECK_LIB below. for pgac_option in $ZSTD_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $ZSTD_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -16403,7 +16403,7 @@ fi if test "$with_icu" = yes; then ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$ICU_CFLAGS $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $ICU_CFLAGS" # Verify we have ICU's header files ac_fn_c_check_header_mongrel "$LINENO" "unicode/ucol.h" "ac_cv_header_unicode_ucol_h" "$ac_includes_default" @@ -18840,7 +18840,7 @@ Use --without-tcl to disable building PL/Tcl." "$LINENO" 5 fi # now that we have TCL_INCLUDE_SPEC, we can check for ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$TCL_INCLUDE_SPEC $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $TCL_INCLUDE_SPEC" ac_fn_c_check_header_mongrel "$LINENO" "tcl.h" "ac_cv_header_tcl_h" "$ac_includes_default" if test "x$ac_cv_header_tcl_h" = xyes; then : @@ -18909,7 +18909,7 @@ fi # check for if test "$with_python" = yes; then ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$python_includespec $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $python_includespec" ac_fn_c_check_header_mongrel "$LINENO" "Python.h" "ac_cv_header_Python_h" "$ac_includes_default" if test "x$ac_cv_header_Python_h" = xyes; then : diff --git a/configure.ac b/configure.ac index b18dcd2acc68e..bcaa249eac0fa 100644 --- a/configure.ac +++ b/configure.ac @@ -1057,12 +1057,12 @@ if test "$with_libxml" = yes ; then # Note the user could also set XML2_CFLAGS/XML2_LIBS directly for pgac_option in $XML2_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $XML2_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -1106,12 +1106,12 @@ if test "$with_lz4" = yes; then # note that -llz4 will be added by AC_CHECK_LIB below. for pgac_option in $LZ4_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $LZ4_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -1131,12 +1131,12 @@ if test "$with_zstd" = yes; then # note that -lzstd will be added by AC_CHECK_LIB below. for pgac_option in $ZSTD_CFLAGS; do case $pgac_option in - -I*|-D*) CPPFLAGS="$CPPFLAGS $pgac_option";; + -I*|-D*) INCLUDES="$INCLUDES $pgac_option";; esac done for pgac_option in $ZSTD_LIBS; do case $pgac_option in - -L*) LDFLAGS="$LDFLAGS $pgac_option";; + -L*) LIBDIRS="$LIBDIRS $pgac_option";; esac done fi @@ -1937,7 +1937,7 @@ fi if test "$with_icu" = yes; then ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$ICU_CFLAGS $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $ICU_CFLAGS" # Verify we have ICU's header files AC_CHECK_HEADER(unicode/ucol.h, [], @@ -2367,7 +2367,7 @@ Use --without-tcl to disable building PL/Tcl.]) fi # now that we have TCL_INCLUDE_SPEC, we can check for ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$TCL_INCLUDE_SPEC $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $TCL_INCLUDE_SPEC" AC_CHECK_HEADER(tcl.h, [], [AC_MSG_ERROR([header file is required for Tcl])]) CPPFLAGS=$ac_save_CPPFLAGS fi @@ -2404,7 +2404,7 @@ fi # check for if test "$with_python" = yes; then ac_save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$python_includespec $CPPFLAGS" + CPPFLAGS="$CPPFLAGS $python_includespec" AC_CHECK_HEADER(Python.h, [], [AC_MSG_ERROR([header file is required for Python])]) CPPFLAGS=$ac_save_CPPFLAGS fi diff --git a/src/Makefile.global.in b/src/Makefile.global.in index ca305b5664d65..e14466ae57f46 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -241,7 +241,7 @@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ PG_SYSROOT = @PG_SYSROOT@ -override CPPFLAGS := $(ICU_CFLAGS) $(CPPFLAGS) +override CPPFLAGS += $(ICU_CFLAGS) ifdef PGXS override CPPFLAGS := -I$(includedir_server) -I$(includedir_internal) $(CPPFLAGS) diff --git a/src/backend/jit/llvm/Makefile b/src/backend/jit/llvm/Makefile index e9fd195cc227c..10c7d719902c9 100644 --- a/src/backend/jit/llvm/Makefile +++ b/src/backend/jit/llvm/Makefile @@ -31,7 +31,7 @@ endif # All files in this directory use LLVM. CFLAGS += $(LLVM_CFLAGS) CXXFLAGS += $(LLVM_CXXFLAGS) -override CPPFLAGS := $(LLVM_CPPFLAGS) $(CPPFLAGS) +override CPPFLAGS += $(LLVM_CPPFLAGS) SHLIB_LINK += $(LLVM_LIBS) # Because this module includes C++ files, we need to use a C++ diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile index 031cc77c9d61b..adc9940d64c45 100644 --- a/src/bin/initdb/Makefile +++ b/src/bin/initdb/Makefile @@ -20,7 +20,7 @@ include $(top_builddir)/src/Makefile.global # from libpq, else we have risks of version skew if we run with a libpq # shared library from a different PG version. Define # USE_PRIVATE_ENCODING_FUNCS to ensure that that happens. -override CPPFLAGS := -DUSE_PRIVATE_ENCODING_FUNCS -I$(libpq_srcdir) -I$(top_srcdir)/src/timezone $(ICU_CFLAGS) $(CPPFLAGS) +override CPPFLAGS := -DUSE_PRIVATE_ENCODING_FUNCS -I$(libpq_srcdir) -I$(top_srcdir)/src/timezone $(CPPFLAGS) $(ICU_CFLAGS) # We need libpq only because fe_utils does. LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) $(ICU_LIBS) diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index b36a7657648a7..e4206ac854013 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -22,7 +22,7 @@ NAME= pq SO_MAJOR_VERSION= 5 SO_MINOR_VERSION= $(MAJORVERSION) -override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -I$(top_builddir)/src/port -I$(top_srcdir)/src/port +override CPPFLAGS := -I$(srcdir) -I$(top_builddir)/src/port -I$(top_srcdir)/src/port $(CPPFLAGS) ifneq ($(PORTNAME), win32) override CFLAGS += $(PTHREAD_CFLAGS) endif diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index f959083a0bdec..25f295c3709e2 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -11,7 +11,7 @@ ifeq ($(PORTNAME), win32) override python_libspec = endif -override CPPFLAGS := -I. -I$(srcdir) $(python_includespec) $(CPPFLAGS) +override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) $(python_includespec) rpathdir = $(python_libdir) diff --git a/src/pl/tcl/Makefile b/src/pl/tcl/Makefile index ea52a2efc229d..dd57f7d694c82 100644 --- a/src/pl/tcl/Makefile +++ b/src/pl/tcl/Makefile @@ -11,7 +11,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -override CPPFLAGS := -I. -I$(srcdir) $(TCL_INCLUDE_SPEC) $(CPPFLAGS) +override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) $(TCL_INCLUDE_SPEC) # On Windows, we don't link directly with the Tcl library; see below ifneq ($(PORTNAME), win32) From 0d70ba3b0944402deedbe45db36720ee495ae7f4 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 30 Jul 2025 11:55:47 +0900 Subject: [PATCH 743/796] Fix ./configure checks with __cpuidex() and __cpuid() The configure checks used two incorrect functions when checking the presence of some routines in an environment: - __get_cpuidex() for the check of __cpuidex(). - __get_cpuid() for the check of __cpuid(). This means that Postgres has never been able to detect the presence of these functions, impacting environments where these exist, like Windows. Simply fixing the function name does not work. For example, using configure with MinGW on Windows causes the checks to detect all four of __get_cpuid(), __get_cpuid_count(), __cpuidex() and __cpuid() to be available, causing a compilation failure as this messes up with the MinGW headers as we would include both and . The Postgres code expects only one in { __get_cpuid() , __cpuid() } and one in { __get_cpuid_count() , __cpuidex() } to exist. This commit reshapes the configure checks to do exactly what meson is doing, which has been working well for us: check one, then the other, but never allow both to be detected in a given build. The logic is wrong since 3dc2d62d0486 and 792752af4eb5 where these checks have been introduced (the second case is most likely a copy-pasto coming from the first case), with meson documenting that the configure checks were broken. As far as I can see, they are not once applied consistently with what the code expects, but let's see if the buildfarm has different something to say. The comment in meson.build is adjusted as well, to reflect the new reality. Author: Lukas Fittl Co-authored-by: Michael Paquier Discussion: https://postgr.es/m/aIgwNYGVt5aRAqTJ@paquier.xyz Backpatch-through: 13 --- configure | 63 +++++++++++++++++++++++++++------------------------- configure.ac | 49 +++++++++++++++++++++------------------- meson.build | 5 +---- 3 files changed, 60 insertions(+), 57 deletions(-) diff --git a/configure b/configure index 588d1cf0b9ca7..677f69ea5fd04 100755 --- a/configure +++ b/configure @@ -17554,7 +17554,7 @@ $as_echo "#define HAVE_GCC__ATOMIC_INT64_CAS 1" >>confdefs.h fi -# Check for x86 cpuid instruction +# Check for __get_cpuid() and __cpuid() { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid" >&5 $as_echo_n "checking for __get_cpuid... " >&6; } if ${pgac_cv__get_cpuid+:} false; then : @@ -17587,77 +17587,79 @@ if test x"$pgac_cv__get_cpuid" = x"yes"; then $as_echo "#define HAVE__GET_CPUID 1" >>confdefs.h -fi - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid_count" >&5 -$as_echo_n "checking for __get_cpuid_count... " >&6; } -if ${pgac_cv__get_cpuid_count+:} false; then : +else + # __cpuid() + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuid" >&5 +$as_echo_n "checking for __cpuid... " >&6; } +if ${pgac_cv__cpuid+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include +#include int main () { unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuid_count(7, 0, &exx[0], &exx[1], &exx[2], &exx[3]); + __cpuid(exx, 1); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : - pgac_cv__get_cpuid_count="yes" + pgac_cv__cpuid="yes" else - pgac_cv__get_cpuid_count="no" + pgac_cv__cpuid="no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__get_cpuid_count" >&5 -$as_echo "$pgac_cv__get_cpuid_count" >&6; } -if test x"$pgac_cv__get_cpuid_count" = x"yes"; then +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuid" >&5 +$as_echo "$pgac_cv__cpuid" >&6; } + if test x"$pgac_cv__cpuid" = x"yes"; then -$as_echo "#define HAVE__GET_CPUID_COUNT 1" >>confdefs.h +$as_echo "#define HAVE__CPUID 1" >>confdefs.h + fi fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuid" >&5 -$as_echo_n "checking for __cpuid... " >&6; } -if ${pgac_cv__cpuid+:} false; then : +# Check for __get_cpuid_count() and __cpuidex() in a similar fashion. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __get_cpuid_count" >&5 +$as_echo_n "checking for __get_cpuid_count... " >&6; } +if ${pgac_cv__get_cpuid_count+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include +#include int main () { unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuid(exx[0], 1); + __get_cpuid_count(7, 0, &exx[0], &exx[1], &exx[2], &exx[3]); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : - pgac_cv__cpuid="yes" + pgac_cv__get_cpuid_count="yes" else - pgac_cv__cpuid="no" + pgac_cv__get_cpuid_count="no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuid" >&5 -$as_echo "$pgac_cv__cpuid" >&6; } -if test x"$pgac_cv__cpuid" = x"yes"; then - -$as_echo "#define HAVE__CPUID 1" >>confdefs.h +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__get_cpuid_count" >&5 +$as_echo "$pgac_cv__get_cpuid_count" >&6; } +if test x"$pgac_cv__get_cpuid_count" = x"yes"; then -fi +$as_echo "#define HAVE__GET_CPUID_COUNT 1" >>confdefs.h -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuidex" >&5 +else + # __cpuidex() + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __cpuidex" >&5 $as_echo_n "checking for __cpuidex... " >&6; } if ${pgac_cv__cpuidex+:} false; then : $as_echo_n "(cached) " >&6 @@ -17669,7 +17671,7 @@ int main () { unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuidex(exx[0], 7, 0); + __cpuidex(exx, 7, 0); ; return 0; @@ -17685,10 +17687,11 @@ rm -f core conftest.err conftest.$ac_objext \ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__cpuidex" >&5 $as_echo "$pgac_cv__cpuidex" >&6; } -if test x"$pgac_cv__cpuidex" = x"yes"; then + if test x"$pgac_cv__cpuidex" = x"yes"; then $as_echo "#define HAVE__CPUIDEX 1" >>confdefs.h + fi fi # Check for XSAVE intrinsics diff --git a/configure.ac b/configure.ac index bcaa249eac0fa..47441bb7238c3 100644 --- a/configure.ac +++ b/configure.ac @@ -2079,7 +2079,7 @@ PGAC_HAVE_GCC__ATOMIC_INT32_CAS PGAC_HAVE_GCC__ATOMIC_INT64_CAS -# Check for x86 cpuid instruction +# Check for __get_cpuid() and __cpuid() AC_CACHE_CHECK([for __get_cpuid], [pgac_cv__get_cpuid], [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [[unsigned int exx[4] = {0, 0, 0, 0}; @@ -2089,8 +2089,21 @@ AC_CACHE_CHECK([for __get_cpuid], [pgac_cv__get_cpuid], [pgac_cv__get_cpuid="no"])]) if test x"$pgac_cv__get_cpuid" = x"yes"; then AC_DEFINE(HAVE__GET_CPUID, 1, [Define to 1 if you have __get_cpuid.]) +else + # __cpuid() + AC_CACHE_CHECK([for __cpuid], [pgac_cv__cpuid], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [[unsigned int exx[4] = {0, 0, 0, 0}; + __cpuid(exx, 1); + ]])], + [pgac_cv__cpuid="yes"], + [pgac_cv__cpuid="no"])]) + if test x"$pgac_cv__cpuid" = x"yes"; then + AC_DEFINE(HAVE__CPUID, 1, [Define to 1 if you have __cpuid.]) + fi fi +# Check for __get_cpuid_count() and __cpuidex() in a similar fashion. AC_CACHE_CHECK([for __get_cpuid_count], [pgac_cv__get_cpuid_count], [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [[unsigned int exx[4] = {0, 0, 0, 0}; @@ -2100,28 +2113,18 @@ AC_CACHE_CHECK([for __get_cpuid_count], [pgac_cv__get_cpuid_count], [pgac_cv__get_cpuid_count="no"])]) if test x"$pgac_cv__get_cpuid_count" = x"yes"; then AC_DEFINE(HAVE__GET_CPUID_COUNT, 1, [Define to 1 if you have __get_cpuid_count.]) -fi - -AC_CACHE_CHECK([for __cpuid], [pgac_cv__cpuid], -[AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], - [[unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuid(exx[0], 1); - ]])], - [pgac_cv__cpuid="yes"], - [pgac_cv__cpuid="no"])]) -if test x"$pgac_cv__cpuid" = x"yes"; then - AC_DEFINE(HAVE__CPUID, 1, [Define to 1 if you have __cpuid.]) -fi - -AC_CACHE_CHECK([for __cpuidex], [pgac_cv__cpuidex], -[AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], - [[unsigned int exx[4] = {0, 0, 0, 0}; - __get_cpuidex(exx[0], 7, 0); - ]])], - [pgac_cv__cpuidex="yes"], - [pgac_cv__cpuidex="no"])]) -if test x"$pgac_cv__cpuidex" = x"yes"; then - AC_DEFINE(HAVE__CPUIDEX, 1, [Define to 1 if you have __cpuidex.]) +else + # __cpuidex() + AC_CACHE_CHECK([for __cpuidex], [pgac_cv__cpuidex], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [[unsigned int exx[4] = {0, 0, 0, 0}; + __cpuidex(exx, 7, 0); + ]])], + [pgac_cv__cpuidex="yes"], + [pgac_cv__cpuidex="no"])]) + if test x"$pgac_cv__cpuidex" = x"yes"; then + AC_DEFINE(HAVE__CPUIDEX, 1, [Define to 1 if you have __cpuidex.]) + fi fi # Check for XSAVE intrinsics diff --git a/meson.build b/meson.build index 731bb38a1eb90..fe4d137db0c2e 100644 --- a/meson.build +++ b/meson.build @@ -1897,10 +1897,7 @@ if cc.links(''' cdata.set('HAVE__BUILTIN_OP_OVERFLOW', 1) endif - -# XXX: The configure.ac check for __cpuid() is broken, we don't copy that -# here. To prevent problems due to two detection methods working, stop -# checking after one. +# Check for __get_cpuid() and __cpuid(). if cc.links(''' #include int main(int arg, char **argv) From 93b69a4bce5b8ec18625cc0ae1a3f7c4a72121a8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 4 Nov 2024 13:30:30 -0500 Subject: [PATCH 744/796] pg_dump: provide a stable sort order for rules. Previously, we sorted rules by schema name and then rule name; if that wasn't unique, we sorted by rule OID. This can be problematic for comparing dumps from databases with different histories, especially since certain rule names like "_RETURN" are very common. Let's make the sort key schema name, rule name, table name, which should be unique. (This is the same behavior we've long used for triggers and RLS policies.) Andreas Karlsson This back-patches v18 commit 350e6b8ea86c22c0b95c2e32a4e8d109255b5596 to all supported branches. The next commit will assert that pg_dump provides a stable sort order for all object types. That assertion would fail without stabilizing DO_RULE order as this commit did. Discussion: https://postgr.es/m/b4e468d8-0cd6-42e6-ac8a-1d6afa6e0cf1@proxel.se Discussion: https://postgr.es/m/20250707192654.9e.nmisch@google.com Backpatch-through: 13-17 --- src/bin/pg_dump/pg_dump_sort.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index d85f3d0e2d833..bb31c1503d8f8 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -294,6 +294,17 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_RULE) + { + RuleInfo *robj1 = *(RuleInfo *const *) p1; + RuleInfo *robj2 = *(RuleInfo *const *) p2; + + /* Sort by table name (table namespace was considered already) */ + cmpval = strcmp(robj1->ruletable->dobj.name, + robj2->ruletable->dobj.name); + if (cmpval != 0) + return cmpval; + } else if (obj1->objType == DO_TRIGGER) { TriggerInfo *tobj1 = *(TriggerInfo *const *) p1; From 2dc14211861c39c3506f6fb0a3036428b369a1c1 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Thu, 31 Jul 2025 06:37:56 -0700 Subject: [PATCH 745/796] Sort dump objects independent of OIDs, for the 7 holdout object types. pg_dump sorts objects by their logical names, e.g. (nspname, relname, tgname), before dependency-driven reordering. That removes one source of logically-identical databases differing in their schema-only dumps. In other words, it helps with schema diffing. The logical name sort ignored essential sort keys for constraints, operators, PUBLICATION ... FOR TABLE, PUBLICATION ... FOR TABLES IN SCHEMA, operator classes, and operator families. pg_dump's sort then depended on object OID, yielding spurious schema diffs. After this change, OIDs affect dump order only in the event of catalog corruption. While pg_dump also wrongly ignored pg_collation.collencoding, CREATE COLLATION restrictions have been keeping that imperceptible in practical use. Use techniques like we use for object types already having full sort key coverage. Where the pertinent queries weren't fetching the ignored sort keys, this adds columns to those queries and stores those keys in memory for the long term. The ignorance of sort keys became more problematic when commit 172259afb563d35001410dc6daad78b250924038 added a schema diff test sensitive to it. Buildfarm member hippopotamus witnessed that. However, dump order stability isn't a new goal, and this might avoid other dump comparison failures. Hence, back-patch to v13 (all supported versions). Reviewed-by: Robert Haas Discussion: https://postgr.es/m/20250707192654.9e.nmisch@google.com Backpatch-through: 13 --- src/bin/pg_dump/common.c | 19 ++ src/bin/pg_dump/pg_dump.c | 62 ++++-- src/bin/pg_dump/pg_dump.h | 6 + src/bin/pg_dump/pg_dump_sort.c | 238 ++++++++++++++++++++-- src/test/regress/expected/publication.out | 21 ++ src/test/regress/sql/publication.sql | 22 ++ 6 files changed, 335 insertions(+), 33 deletions(-) diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 64e7dc89f1338..74bbea79663db 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -17,6 +17,7 @@ #include +#include "catalog/pg_am_d.h" #include "catalog/pg_class_d.h" #include "catalog/pg_collation_d.h" #include "catalog/pg_extension_d.h" @@ -933,6 +934,24 @@ findOprByOid(Oid oid) return (OprInfo *) dobj; } +/* + * findAccessMethodByOid + * finds the DumpableObject for the access method with the given oid + * returns NULL if not found + */ +AccessMethodInfo * +findAccessMethodByOid(Oid oid) +{ + CatalogId catId; + DumpableObject *dobj; + + catId.tableoid = AccessMethodRelationId; + catId.oid = oid; + dobj = findObjectByCatalogId(catId); + Assert(dobj == NULL || dobj->objType == DO_ACCESS_METHOD); + return (AccessMethodInfo *) dobj; +} + /* * findCollationByOid * finds the DumpableObject for the collation with the given oid diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 2626dd250c8eb..0f26b018d4afc 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2012,6 +2012,13 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout) static void selectDumpableAccessMethod(AccessMethodInfo *method, Archive *fout) { + /* see getAccessMethods() comment about v9.6. */ + if (fout->remoteVersion < 90600) + { + method->dobj.dump = DUMP_COMPONENT_NONE; + return; + } + if (checkExtensionMembership(&method->dobj, fout)) return; /* extension membership overrides all else */ @@ -5997,6 +6004,8 @@ getOperators(Archive *fout, int *numOprs) int i_oprnamespace; int i_oprowner; int i_oprkind; + int i_oprleft; + int i_oprright; int i_oprcode; /* @@ -6008,6 +6017,8 @@ getOperators(Archive *fout, int *numOprs) "oprnamespace, " "oprowner, " "oprkind, " + "oprleft, " + "oprright, " "oprcode::oid AS oprcode " "FROM pg_operator"); @@ -6024,6 +6035,8 @@ getOperators(Archive *fout, int *numOprs) i_oprnamespace = PQfnumber(res, "oprnamespace"); i_oprowner = PQfnumber(res, "oprowner"); i_oprkind = PQfnumber(res, "oprkind"); + i_oprleft = PQfnumber(res, "oprleft"); + i_oprright = PQfnumber(res, "oprright"); i_oprcode = PQfnumber(res, "oprcode"); for (i = 0; i < ntups; i++) @@ -6037,6 +6050,8 @@ getOperators(Archive *fout, int *numOprs) findNamespace(atooid(PQgetvalue(res, i, i_oprnamespace))); oprinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_oprowner)); oprinfo[i].oprkind = (PQgetvalue(res, i, i_oprkind))[0]; + oprinfo[i].oprleft = atooid(PQgetvalue(res, i, i_oprleft)); + oprinfo[i].oprright = atooid(PQgetvalue(res, i, i_oprright)); oprinfo[i].oprcode = atooid(PQgetvalue(res, i, i_oprcode)); /* Decide whether we want to dump it */ @@ -6070,6 +6085,7 @@ getCollations(Archive *fout, int *numCollations) int i_collname; int i_collnamespace; int i_collowner; + int i_collencoding; query = createPQExpBuffer(); @@ -6080,7 +6096,8 @@ getCollations(Archive *fout, int *numCollations) appendPQExpBufferStr(query, "SELECT tableoid, oid, collname, " "collnamespace, " - "collowner " + "collowner, " + "collencoding " "FROM pg_collation"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -6095,6 +6112,7 @@ getCollations(Archive *fout, int *numCollations) i_collname = PQfnumber(res, "collname"); i_collnamespace = PQfnumber(res, "collnamespace"); i_collowner = PQfnumber(res, "collowner"); + i_collencoding = PQfnumber(res, "collencoding"); for (i = 0; i < ntups; i++) { @@ -6106,6 +6124,7 @@ getCollations(Archive *fout, int *numCollations) collinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_collnamespace))); collinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_collowner)); + collinfo[i].collencoding = atoi(PQgetvalue(res, i, i_collencoding)); /* Decide whether we want to dump it */ selectDumpableObject(&(collinfo[i].dobj), fout); @@ -6207,19 +6226,28 @@ getAccessMethods(Archive *fout, int *numAccessMethods) int i_amhandler; int i_amtype; - /* Before 9.6, there are no user-defined access methods */ - if (fout->remoteVersion < 90600) - { - *numAccessMethods = 0; - return NULL; - } - query = createPQExpBuffer(); - /* Select all access methods from pg_am table */ - appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, amtype, " - "amhandler::pg_catalog.regproc AS amhandler " - "FROM pg_am"); + /* + * Select all access methods from pg_am table. v9.6 introduced CREATE + * ACCESS METHOD, so earlier versions usually have only built-in access + * methods. v9.6 also changed the access method API, replacing dozens of + * pg_am columns with amhandler. Even if a user created an access method + * by "INSERT INTO pg_am", we have no way to translate pre-v9.6 pg_am + * columns to a v9.6+ CREATE ACCESS METHOD. Hence, before v9.6, read + * pg_am just to facilitate findAccessMethodByOid() providing the + * OID-to-name mapping. + */ + appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, "); + if (fout->remoteVersion >= 90600) + appendPQExpBufferStr(query, + "amtype, " + "amhandler::pg_catalog.regproc AS amhandler "); + else + appendPQExpBufferStr(query, + "'i'::pg_catalog.\"char\" AS amtype, " + "'-'::pg_catalog.regproc AS amhandler "); + appendPQExpBufferStr(query, "FROM pg_am"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -6274,6 +6302,7 @@ getOpclasses(Archive *fout, int *numOpclasses) OpclassInfo *opcinfo; int i_tableoid; int i_oid; + int i_opcmethod; int i_opcname; int i_opcnamespace; int i_opcowner; @@ -6283,7 +6312,7 @@ getOpclasses(Archive *fout, int *numOpclasses) * system-defined opclasses at dump-out time. */ - appendPQExpBufferStr(query, "SELECT tableoid, oid, opcname, " + appendPQExpBufferStr(query, "SELECT tableoid, oid, opcmethod, opcname, " "opcnamespace, " "opcowner " "FROM pg_opclass"); @@ -6297,6 +6326,7 @@ getOpclasses(Archive *fout, int *numOpclasses) i_tableoid = PQfnumber(res, "tableoid"); i_oid = PQfnumber(res, "oid"); + i_opcmethod = PQfnumber(res, "opcmethod"); i_opcname = PQfnumber(res, "opcname"); i_opcnamespace = PQfnumber(res, "opcnamespace"); i_opcowner = PQfnumber(res, "opcowner"); @@ -6310,6 +6340,7 @@ getOpclasses(Archive *fout, int *numOpclasses) opcinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_opcname)); opcinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_opcnamespace))); + opcinfo[i].opcmethod = atooid(PQgetvalue(res, i, i_opcmethod)); opcinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_opcowner)); /* Decide whether we want to dump it */ @@ -6340,6 +6371,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) OpfamilyInfo *opfinfo; int i_tableoid; int i_oid; + int i_opfmethod; int i_opfname; int i_opfnamespace; int i_opfowner; @@ -6351,7 +6383,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) * system-defined opfamilies at dump-out time. */ - appendPQExpBufferStr(query, "SELECT tableoid, oid, opfname, " + appendPQExpBufferStr(query, "SELECT tableoid, oid, opfmethod, opfname, " "opfnamespace, " "opfowner " "FROM pg_opfamily"); @@ -6366,6 +6398,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) i_tableoid = PQfnumber(res, "tableoid"); i_oid = PQfnumber(res, "oid"); i_opfname = PQfnumber(res, "opfname"); + i_opfmethod = PQfnumber(res, "opfmethod"); i_opfnamespace = PQfnumber(res, "opfnamespace"); i_opfowner = PQfnumber(res, "opfowner"); @@ -6378,6 +6411,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) opfinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_opfname)); opfinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_opfnamespace))); + opfinfo[i].opfmethod = atooid(PQgetvalue(res, i, i_opfmethod)); opfinfo[i].rolname = getRoleName(PQgetvalue(res, i, i_opfowner)); /* Decide whether we want to dump it */ diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 243942369ea25..2de5afdacdb8a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -253,6 +253,8 @@ typedef struct _oprInfo DumpableObject dobj; const char *rolname; char oprkind; + Oid oprleft; + Oid oprright; Oid oprcode; } OprInfo; @@ -266,12 +268,14 @@ typedef struct _accessMethodInfo typedef struct _opclassInfo { DumpableObject dobj; + Oid opcmethod; const char *rolname; } OpclassInfo; typedef struct _opfamilyInfo { DumpableObject dobj; + Oid opfmethod; const char *rolname; } OpfamilyInfo; @@ -279,6 +283,7 @@ typedef struct _collInfo { DumpableObject dobj; const char *rolname; + int collencoding; } CollInfo; typedef struct _convInfo @@ -723,6 +728,7 @@ extern TableInfo *findTableByOid(Oid oid); extern TypeInfo *findTypeByOid(Oid oid); extern FuncInfo *findFuncByOid(Oid oid); extern OprInfo *findOprByOid(Oid oid); +extern AccessMethodInfo *findAccessMethodByOid(Oid oid); extern CollInfo *findCollationByOid(Oid oid); extern NamespaceInfo *findNamespaceByOid(Oid oid); extern ExtensionInfo *findExtensionByOid(Oid oid); diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index bb31c1503d8f8..62e27b8d176ce 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -161,6 +161,8 @@ static DumpId postDataBoundId; static int DOTypeNameCompare(const void *p1, const void *p2); +static int pgTypeNameCompare(Oid typid1, Oid typid2); +static int accessMethodNameCompare(Oid am1, Oid am2); static bool TopoSort(DumpableObject **objs, int numObjs, DumpableObject **ordering, @@ -227,12 +229,39 @@ DOTypeNameCompare(const void *p1, const void *p2) else if (obj2->namespace) return 1; - /* Sort by name */ + /* + * Sort by name. With a few exceptions, names here are single catalog + * columns. To get a fuller picture, grep pg_dump.c for "dobj.name = ". + * Names here don't match "Name:" in plain format output, which is a + * _tocEntry.tag. For example, DumpableObject.name of a constraint is + * pg_constraint.conname, but _tocEntry.tag of a constraint is relname and + * conname joined with a space. + */ cmpval = strcmp(obj1->name, obj2->name); if (cmpval != 0) return cmpval; - /* To have a stable sort order, break ties for some object types */ + /* + * Sort by type. This helps types that share a type priority without + * sharing a unique name constraint, e.g. opclass and opfamily. + */ + cmpval = obj1->objType - obj2->objType; + if (cmpval != 0) + return cmpval; + + /* + * To have a stable sort order, break ties for some object types. Most + * catalogs have a natural key, e.g. pg_proc_proname_args_nsp_index. Where + * the above "namespace" and "name" comparisons don't cover all natural + * key columns, compare the rest here. + * + * The natural key usually refers to other catalogs by surrogate keys. + * Hence, this translates each of those references to the natural key of + * the referenced catalog. That may descend through multiple levels of + * catalog references. For example, to sort by pg_proc.proargtypes, + * descend to each pg_type and then further to its pg_namespace, for an + * overall sort by (nspname, typname). + */ if (obj1->objType == DO_FUNC || obj1->objType == DO_AGG) { FuncInfo *fobj1 = *(FuncInfo *const *) p1; @@ -245,22 +274,10 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; for (i = 0; i < fobj1->nargs; i++) { - TypeInfo *argtype1 = findTypeByOid(fobj1->argtypes[i]); - TypeInfo *argtype2 = findTypeByOid(fobj2->argtypes[i]); - - if (argtype1 && argtype2) - { - if (argtype1->dobj.namespace && argtype2->dobj.namespace) - { - cmpval = strcmp(argtype1->dobj.namespace->dobj.name, - argtype2->dobj.namespace->dobj.name); - if (cmpval != 0) - return cmpval; - } - cmpval = strcmp(argtype1->dobj.name, argtype2->dobj.name); - if (cmpval != 0) - return cmpval; - } + cmpval = pgTypeNameCompare(fobj1->argtypes[i], + fobj2->argtypes[i]); + if (cmpval != 0) + return cmpval; } } else if (obj1->objType == DO_OPERATOR) @@ -272,6 +289,57 @@ DOTypeNameCompare(const void *p1, const void *p2) cmpval = (oobj2->oprkind - oobj1->oprkind); if (cmpval != 0) return cmpval; + /* Within an oprkind, sort by argument type names */ + cmpval = pgTypeNameCompare(oobj1->oprleft, oobj2->oprleft); + if (cmpval != 0) + return cmpval; + cmpval = pgTypeNameCompare(oobj1->oprright, oobj2->oprright); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_OPCLASS) + { + OpclassInfo *opcobj1 = *(OpclassInfo *const *) p1; + OpclassInfo *opcobj2 = *(OpclassInfo *const *) p2; + + /* Sort by access method name, per pg_opclass_am_name_nsp_index */ + cmpval = accessMethodNameCompare(opcobj1->opcmethod, + opcobj2->opcmethod); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_OPFAMILY) + { + OpfamilyInfo *opfobj1 = *(OpfamilyInfo *const *) p1; + OpfamilyInfo *opfobj2 = *(OpfamilyInfo *const *) p2; + + /* Sort by access method name, per pg_opfamily_am_name_nsp_index */ + cmpval = accessMethodNameCompare(opfobj1->opfmethod, + opfobj2->opfmethod); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_COLLATION) + { + CollInfo *cobj1 = *(CollInfo *const *) p1; + CollInfo *cobj2 = *(CollInfo *const *) p2; + + /* + * Sort by encoding, per pg_collation_name_enc_nsp_index. Technically, + * this is not necessary, because wherever this changes dump order, + * restoring the dump fails anyway. CREATE COLLATION can't create a + * tie for this to break, because it imposes restrictions to make + * (nspname, collname) uniquely identify a collation within a given + * DatabaseEncoding. While pg_import_system_collations() can create a + * tie, pg_dump+restore fails after + * pg_import_system_collations('my_schema') does so. However, there's + * little to gain by ignoring one natural key column on the basis of + * those limitations elsewhere, so respect the full natural key like + * we do for other object types. + */ + cmpval = cobj1->collencoding - cobj2->collencoding; + if (cmpval != 0) + return cmpval; } else if (obj1->objType == DO_ATTRDEF) { @@ -316,11 +384,143 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_CONSTRAINT) + { + ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; + ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; - /* Usually shouldn't get here, but if we do, sort by OID */ + /* + * Sort domain constraints before table constraints, for consistency + * with our decision to sort CREATE DOMAIN before CREATE TABLE. + */ + if (robj1->condomain) + { + if (robj2->condomain) + { + /* Sort by domain name (domain namespace was considered) */ + cmpval = strcmp(robj1->condomain->dobj.name, + robj2->condomain->dobj.name); + if (cmpval != 0) + return cmpval; + } + else + return PRIO_TYPE - PRIO_TABLE; + } + else if (robj2->condomain) + return PRIO_TABLE - PRIO_TYPE; + else + { + /* Sort by table name (table namespace was considered already) */ + cmpval = strcmp(robj1->contable->dobj.name, + robj2->contable->dobj.name); + if (cmpval != 0) + return cmpval; + } + } + else if (obj1->objType == DO_PUBLICATION_REL) + { + PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; + PublicationRelInfo *probj2 = *(PublicationRelInfo *const *) p2; + + /* Sort by publication name, since (namespace, name) match the rel */ + cmpval = strcmp(probj1->publication->dobj.name, + probj2->publication->dobj.name); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_PUBLICATION_TABLE_IN_SCHEMA) + { + PublicationSchemaInfo *psobj1 = *(PublicationSchemaInfo *const *) p1; + PublicationSchemaInfo *psobj2 = *(PublicationSchemaInfo *const *) p2; + + /* Sort by publication name, since ->name is just nspname */ + cmpval = strcmp(psobj1->publication->dobj.name, + psobj2->publication->dobj.name); + if (cmpval != 0) + return cmpval; + } + + /* + * Shouldn't get here except after catalog corruption, but if we do, sort + * by OID. This may make logically-identical databases differ in the + * order of objects in dump output. Users will get spurious schema diffs. + * Expect flaky failures of 002_pg_upgrade.pl test 'dump outputs from + * original and restored regression databases match' if the regression + * database contains objects allowing that test to reach here. That's a + * consequence of the test using "pg_restore -j", which doesn't fully + * constrain OID assignment order. + */ + Assert(false); return oidcmp(obj1->catId.oid, obj2->catId.oid); } +/* Compare two OID-identified pg_type values by nspname, then by typname. */ +static int +pgTypeNameCompare(Oid typid1, Oid typid2) +{ + TypeInfo *typobj1; + TypeInfo *typobj2; + int cmpval; + + if (typid1 == typid2) + return 0; + + typobj1 = findTypeByOid(typid1); + typobj2 = findTypeByOid(typid2); + + if (!typobj1 || !typobj2) + { + /* + * getTypes() didn't find some OID. Assume catalog corruption, e.g. + * an oprright value without the corresponding OID in a pg_type row. + * Report as "equal", so the caller uses the next available basis for + * comparison, e.g. the next function argument. + * + * Unary operators have InvalidOid in oprleft (if oprkind='r') or in + * oprright (if oprkind='l'). Caller already sorted by oprkind, + * calling us only for like-kind operators. Hence, "typid1 == typid2" + * took care of InvalidOid. (v14 removed postfix operator support. + * Hence, when dumping from v14+, only oprleft can be InvalidOid.) + */ + Assert(false); + return 0; + } + + if (!typobj1->dobj.namespace || !typobj2->dobj.namespace) + Assert(false); /* catalog corruption */ + else + { + cmpval = strcmp(typobj1->dobj.namespace->dobj.name, + typobj2->dobj.namespace->dobj.name); + if (cmpval != 0) + return cmpval; + } + return strcmp(typobj1->dobj.name, typobj2->dobj.name); +} + +/* Compare two OID-identified pg_am values by amname. */ +static int +accessMethodNameCompare(Oid am1, Oid am2) +{ + AccessMethodInfo *amobj1; + AccessMethodInfo *amobj2; + + if (am1 == am2) + return 0; + + amobj1 = findAccessMethodByOid(am1); + amobj2 = findAccessMethodByOid(am2); + + if (!amobj1 || !amobj2) + { + /* catalog corruption: handle like pgTypeNameCompare() does */ + Assert(false); + return 0; + } + + return strcmp(amobj1->dobj.name, amobj2->dobj.name); +} + /* * Sort the given objects into a safe dump order using dependency diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 30b637113404f..3edf0bed510d7 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -1745,3 +1745,24 @@ DROP SCHEMA sch2 cascade; RESET SESSION AUTHORIZATION; DROP ROLE regress_publication_user, regress_publication_user2; DROP ROLE regress_publication_user_dummy; +-- stage objects for pg_dump tests +CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int); +CREATE SCHEMA pubme2 CREATE TABLE t0 (c int, d int); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION dump_pub_qual_1ct FOR + TABLE ONLY pubme.t0 (c, d) WHERE (c > 0); +CREATE PUBLICATION dump_pub_qual_2ct FOR + TABLE ONLY pubme.t0 (c) WHERE (c > 0), + TABLE ONLY pubme.t1 (c); +CREATE PUBLICATION dump_pub_nsp_1ct FOR + TABLES IN SCHEMA pubme; +CREATE PUBLICATION dump_pub_nsp_2ct FOR + TABLES IN SCHEMA pubme, + TABLES IN SCHEMA pubme2; +CREATE PUBLICATION dump_pub_all FOR + TABLE ONLY pubme.t0, + TABLE ONLY pubme.t1 WHERE (c < 0), + TABLES IN SCHEMA pubme, + TABLES IN SCHEMA pubme2 + WITH (publish_via_partition_root = true); +RESET client_min_messages; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 479d4f3264472..c4f12d4e0a39c 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -1109,3 +1109,25 @@ DROP SCHEMA sch2 cascade; RESET SESSION AUTHORIZATION; DROP ROLE regress_publication_user, regress_publication_user2; DROP ROLE regress_publication_user_dummy; + +-- stage objects for pg_dump tests +CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int); +CREATE SCHEMA pubme2 CREATE TABLE t0 (c int, d int); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION dump_pub_qual_1ct FOR + TABLE ONLY pubme.t0 (c, d) WHERE (c > 0); +CREATE PUBLICATION dump_pub_qual_2ct FOR + TABLE ONLY pubme.t0 (c) WHERE (c > 0), + TABLE ONLY pubme.t1 (c); +CREATE PUBLICATION dump_pub_nsp_1ct FOR + TABLES IN SCHEMA pubme; +CREATE PUBLICATION dump_pub_nsp_2ct FOR + TABLES IN SCHEMA pubme, + TABLES IN SCHEMA pubme2; +CREATE PUBLICATION dump_pub_all FOR + TABLE ONLY pubme.t0, + TABLE ONLY pubme.t1 WHERE (c < 0), + TABLES IN SCHEMA pubme, + TABLES IN SCHEMA pubme2 + WITH (publish_via_partition_root = true); +RESET client_min_messages; From fc733f3840320df4ed113b76933cf3befd56abea Mon Sep 17 00:00:00 2001 From: Amit Kapila Date: Fri, 1 Aug 2025 06:53:16 +0000 Subject: [PATCH 746/796] Fix a deadlock during ALTER SUBSCRIPTION ... DROP PUBLICATION. A deadlock can occur when the DDL command and the apply worker acquire catalog locks in different orders while dropping replication origins. The issue is rare in PG16 and higher branches because, in most cases, the tablesync worker performs the origin drop in those branches, and its locking sequence does not conflict with DDL operations. This patch ensures consistent lock acquisition to prevent such deadlocks. As per buildfarm. Reported-by: Alexander Lakhin Author: Ajin Cherian Reviewed-by: Hayato Kuroda Reviewed-by: vignesh C Reviewed-by: Amit Kapila Backpatch-through: 14, where it was introduced Discussion: https://postgr.es/m/bab95e12-6cc5-4ebb-80a8-3e41956aa297@gmail.com --- src/backend/catalog/pg_subscription.c | 33 ++++++++++++++++++--- src/backend/replication/logical/tablesync.c | 27 +++++++++++++++-- src/include/catalog/pg_subscription_rel.h | 2 ++ 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c index 9efc9159f2c22..68e32c42cb092 100644 --- a/src/backend/catalog/pg_subscription.c +++ b/src/backend/catalog/pg_subscription.c @@ -287,8 +287,8 @@ AddSubscriptionRelState(Oid subid, Oid relid, char state, * Update the state of a subscription table. */ void -UpdateSubscriptionRelState(Oid subid, Oid relid, char state, - XLogRecPtr sublsn) +UpdateSubscriptionRelStateEx(Oid subid, Oid relid, char state, + XLogRecPtr sublsn, bool already_locked) { Relation rel; HeapTuple tup; @@ -296,9 +296,24 @@ UpdateSubscriptionRelState(Oid subid, Oid relid, char state, Datum values[Natts_pg_subscription_rel]; bool replaces[Natts_pg_subscription_rel]; - LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock); + if (already_locked) + { +#ifdef USE_ASSERT_CHECKING + LOCKTAG tag; - rel = table_open(SubscriptionRelRelationId, RowExclusiveLock); + Assert(CheckRelationOidLockedByMe(SubscriptionRelRelationId, + RowExclusiveLock, true)); + SET_LOCKTAG_OBJECT(tag, InvalidOid, SubscriptionRelationId, subid, 0); + Assert(LockHeldByMe(&tag, AccessShareLock, true)); +#endif + + rel = table_open(SubscriptionRelRelationId, NoLock); + } + else + { + LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock); + rel = table_open(SubscriptionRelRelationId, RowExclusiveLock); + } /* Try finding existing mapping. */ tup = SearchSysCacheCopy2(SUBSCRIPTIONRELMAP, @@ -332,6 +347,16 @@ UpdateSubscriptionRelState(Oid subid, Oid relid, char state, table_close(rel, NoLock); } +/* + * Update the state of a subscription table. + */ +void +UpdateSubscriptionRelState(Oid subid, Oid relid, char state, + XLogRecPtr sublsn) +{ + UpdateSubscriptionRelStateEx(subid, relid, state, sublsn, false); +} + /* * Get state of subscription table. * diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index c8893ffad965a..1da5a7e7ef86e 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -426,6 +426,7 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) ListCell *lc; bool started_tx = false; bool should_exit = false; + Relation rel = NULL; Assert(!IsTransactionState()); @@ -493,7 +494,16 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) * worker to remove the origin tracking as if there is any * error while dropping we won't restart it to drop the * origin. So passing missing_ok = true. + * + * Lock the subscription and origin in the same order as we + * are doing during DDL commands to avoid deadlocks. See + * AlterSubscription_refresh. */ + LockSharedObject(SubscriptionRelationId, MyLogicalRepWorker->subid, + 0, AccessShareLock); + if (!rel) + rel = table_open(SubscriptionRelRelationId, RowExclusiveLock); + ReplicationOriginNameForLogicalRep(MyLogicalRepWorker->subid, rstate->relid, originname, @@ -503,9 +513,9 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) /* * Update the state to READY only after the origin cleanup. */ - UpdateSubscriptionRelState(MyLogicalRepWorker->subid, - rstate->relid, rstate->state, - rstate->lsn); + UpdateSubscriptionRelStateEx(MyLogicalRepWorker->subid, + rstate->relid, rstate->state, + rstate->lsn, true); } } else @@ -556,7 +566,14 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) * This is required to avoid any undetected deadlocks * due to any existing lock as deadlock detector won't * be able to detect the waits on the latch. + * + * Also close any tables prior to the commit. */ + if (rel) + { + table_close(rel, NoLock); + rel = NULL; + } CommitTransactionCommand(); pgstat_report_stat(false); } @@ -623,6 +640,10 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn) } } + /* Close table if opened */ + if (rel) + table_close(rel, NoLock); + if (started_tx) { /* diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h index 8244ad537aea9..0afda8832197e 100644 --- a/src/include/catalog/pg_subscription_rel.h +++ b/src/include/catalog/pg_subscription_rel.h @@ -86,6 +86,8 @@ extern void AddSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn, bool retain_lock); extern void UpdateSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn); +extern void UpdateSubscriptionRelStateEx(Oid subid, Oid relid, char state, + XLogRecPtr sublsn, bool already_locked); extern char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn); extern void RemoveSubscriptionRel(Oid subid, Oid relid); From a007f07339cbb24cbb4112ac18e6432e2b0277d9 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 1 Aug 2025 16:52:11 -0500 Subject: [PATCH 747/796] Allow resetting unknown custom GUCs with reserved prefixes. Currently, ALTER DATABASE/ROLE/SYSTEM RESET [ALL] with an unknown custom GUC with a prefix reserved by MarkGUCPrefixReserved() errors (unless a superuser runs a RESET ALL variant). This is problematic for cases such as an extension library upgrade that removes a GUC. To fix, simply make sure the relevant code paths explicitly allow it. Note that we require superuser or privileges on the parameter to reset it. This is perhaps a bit more restrictive than is necessary, but it's not clear whether further relaxing the requirements is safe. Oversight in commit 88103567cb. The ALTER SYSTEM fix is dependent on commit 2d870b4aef, which first appeared in v17. Unfortunately, back-patching that commit would introduce ABI breakage, and while that breakage seems unlikely to bother anyone, it doesn't seem worth the risk. Hence, the ALTER SYSTEM part of this commit is omitted on v15 and v16. Reported-by: Mert Alev Reviewed-by: Laurenz Albe Discussion: https://postgr.es/m/18964-ba09dea8c98fccd6%40postgresql.org Backpatch-through: 15 --- contrib/auto_explain/Makefile | 2 ++ contrib/auto_explain/expected/alter_reset.out | 19 ++++++++++++++++ contrib/auto_explain/meson.build | 5 +++++ contrib/auto_explain/sql/alter_reset.sql | 22 +++++++++++++++++++ src/backend/utils/misc/guc.c | 21 +++++++++++++----- 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 contrib/auto_explain/expected/alter_reset.out create mode 100644 contrib/auto_explain/sql/alter_reset.sql diff --git a/contrib/auto_explain/Makefile b/contrib/auto_explain/Makefile index efd127d3cae64..94ab28e7c06b9 100644 --- a/contrib/auto_explain/Makefile +++ b/contrib/auto_explain/Makefile @@ -6,6 +6,8 @@ OBJS = \ auto_explain.o PGFILEDESC = "auto_explain - logging facility for execution plans" +REGRESS = alter_reset + TAP_TESTS = 1 ifdef USE_PGXS diff --git a/contrib/auto_explain/expected/alter_reset.out b/contrib/auto_explain/expected/alter_reset.out new file mode 100644 index 0000000000000..ec355189806ae --- /dev/null +++ b/contrib/auto_explain/expected/alter_reset.out @@ -0,0 +1,19 @@ +-- +-- This tests resetting unknown custom GUCs with reserved prefixes. There's +-- nothing specific to auto_explain; this is just a convenient place to put +-- this test. +-- +SELECT current_database() AS datname \gset +CREATE ROLE regress_ae_role; +ALTER DATABASE :"datname" SET auto_explain.bogus = 1; +ALTER ROLE regress_ae_role SET auto_explain.bogus = 1; +ALTER ROLE regress_ae_role IN DATABASE :"datname" SET auto_explain.bogus = 1; +ALTER SYSTEM SET auto_explain.bogus = 1; +LOAD 'auto_explain'; +WARNING: invalid configuration parameter name "auto_explain.bogus", removing it +DETAIL: "auto_explain" is now a reserved prefix. +ALTER DATABASE :"datname" RESET auto_explain.bogus; +ALTER ROLE regress_ae_role RESET auto_explain.bogus; +ALTER ROLE regress_ae_role IN DATABASE :"datname" RESET auto_explain.bogus; +ALTER SYSTEM RESET auto_explain.bogus; +DROP ROLE regress_ae_role; diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build index af1a3b8e3250e..4bf2fedf1dd54 100644 --- a/contrib/auto_explain/meson.build +++ b/contrib/auto_explain/meson.build @@ -20,6 +20,11 @@ tests += { 'name': 'auto_explain', 'sd': meson.current_source_dir(), 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'alter_reset', + ], + }, 'tap': { 'tests': [ 't/001_auto_explain.pl', diff --git a/contrib/auto_explain/sql/alter_reset.sql b/contrib/auto_explain/sql/alter_reset.sql new file mode 100644 index 0000000000000..bf621454ec24a --- /dev/null +++ b/contrib/auto_explain/sql/alter_reset.sql @@ -0,0 +1,22 @@ +-- +-- This tests resetting unknown custom GUCs with reserved prefixes. There's +-- nothing specific to auto_explain; this is just a convenient place to put +-- this test. +-- + +SELECT current_database() AS datname \gset +CREATE ROLE regress_ae_role; + +ALTER DATABASE :"datname" SET auto_explain.bogus = 1; +ALTER ROLE regress_ae_role SET auto_explain.bogus = 1; +ALTER ROLE regress_ae_role IN DATABASE :"datname" SET auto_explain.bogus = 1; +ALTER SYSTEM SET auto_explain.bogus = 1; + +LOAD 'auto_explain'; + +ALTER DATABASE :"datname" RESET auto_explain.bogus; +ALTER ROLE regress_ae_role RESET auto_explain.bogus; +ALTER ROLE regress_ae_role IN DATABASE :"datname" RESET auto_explain.bogus; +ALTER SYSTEM RESET auto_explain.bogus; + +DROP ROLE regress_ae_role; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 0831d45aba3ac..29a8972214f2e 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -4725,8 +4725,13 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt) * the config file cannot cause postmaster start to fail, so we * don't have to be too tense about possibly installing a bad * value.) + * + * As an exception, we skip this check if this is a RESET command + * for an unknown custom GUC, else there'd be no way for users to + * remove such settings with reserved prefixes. */ - (void) assignable_custom_variable_name(name, false, ERROR); + if (value || !valid_custom_variable_name(name)) + (void) assignable_custom_variable_name(name, false, ERROR); } /* @@ -6713,6 +6718,7 @@ validate_option_array_item(const char *name, const char *value, { struct config_generic *gconf; + bool reset_custom; /* * There are three cases to consider: @@ -6731,16 +6737,21 @@ validate_option_array_item(const char *name, const char *value, * it's assumed to be fully validated.) * * name is not known and can't be created as a placeholder. Throw error, - * unless skipIfNoPermissions is true, in which case return false. + * unless skipIfNoPermissions or reset_custom is true. If reset_custom is + * true, this is a RESET or RESET ALL operation for an unknown custom GUC + * with a reserved prefix, in which case we want to fall through to the + * placeholder case described in the preceding paragraph (else there'd be + * no way for users to remove them). Otherwise, return false. */ - gconf = find_option(name, true, skipIfNoPermissions, ERROR); - if (!gconf) + reset_custom = (!value && valid_custom_variable_name(name)); + gconf = find_option(name, true, skipIfNoPermissions || reset_custom, ERROR); + if (!gconf && !reset_custom) { /* not known, failed to make a placeholder */ return false; } - if (gconf->flags & GUC_CUSTOM_PLACEHOLDER) + if (!gconf || gconf->flags & GUC_CUSTOM_PLACEHOLDER) { /* * We cannot do any meaningful check on the value, so only permissions From fbba02177b1c75388d84b11bf7e0cdb6b892d87c Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Sat, 2 Aug 2025 17:08:50 +0900 Subject: [PATCH 748/796] Fix use-after-free with INSERT ON CONFLICT changes in reorderbuffer.c In ReorderBufferProcessTXN(), used to send the data of a transaction to an output plugin, INSERT ON CONFLICT changes (INTERNAL_SPEC_INSERT) are delayed until a confirmation record arrives (INTERNAL_SPEC_CONFIRM), updating the change being processed. 8c58624df462 has added an extra step after processing a change to update the progress of the transaction, by calling the callback update_progress_txn() based on the LSN stored in a change after a threshold of CHANGES_THRESHOLD (100) is reached. This logic has missed the fact that for an INSERT ON CONFLICT change the data is freed once processed, hence update_progress_txn() could be called pointing to a LSN value that's already been freed. This could result in random crashes, depending on the workload. Per discussion, this issue is fixed by reusing in update_progress_txn() the LSN from the change processed found at the beginning of the loop, meaning that for a INTERNAL_SPEC_CONFIRM change the progress is updated using the LSN of the INTERNAL_SPEC_CONFIRM change, and not the LSN from its INTERNAL_SPEC_INSERT change. This is actually more correct, as we want to update the progress to point to the INTERNAL_SPEC_CONFIRM change. Masahiko Sawada has found a nice trick to reproduce the issue: hardcode CHANGES_THRESHOLD at 1 and run test_decoding (test "ddl" being enough) on an instance running valgrind. The bug has been analyzed by Ethan Mertz, who also originally suggested the solution used in this patch. Issue introduced by 8c58624df462, so backpatch down to v16. Author: Ethan Mertz Co-authored-by: Michael Paquier Reviewed-by: Amit Kapila Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/aIsQqDZ7x4LAQ6u1@paquier.xyz Backpatch-through: 16 --- src/backend/replication/logical/reorderbuffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index d85a9f2bb2026..4bd1f7af061a0 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -2516,7 +2516,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, if (++changes_count >= CHANGES_THRESHOLD) { - rb->update_progress_txn(rb, txn, change->lsn); + rb->update_progress_txn(rb, txn, prev_lsn); changes_count = 0; } } From 350b0c61658903f22ab43aa6cbdcc0bc7d25034d Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Sat, 2 Aug 2025 18:30:01 +0900 Subject: [PATCH 749/796] Doc: clarify the restrictions of AFTER triggers with transition tables. It was not very clear that the triggers are only allowed on plain tables (not foreign tables). Also, rephrase the documentation for better readability. Follow up to commit 9e6104c66. Reported-by: Etsuro Fujita Author: Ashutosh Bapat Reviewed-by: Etsuro Fujita Discussion: https://postgr.es/m/CAPmGK16XBs9ptNr8Lk4f-tJZogf6y-Prz%3D8yhvJbb_4dpsc3mQ%40mail.gmail.com Backpatch-through: 13 --- doc/src/sgml/ref/create_trigger.sgml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 982ab6f3ee450..71869c996e3dc 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -197,9 +197,11 @@ CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name of the rows inserted, deleted, or modified by the current SQL statement. This feature lets the trigger see a global view of what the statement did, not just one row at a time. This option is only allowed for - an AFTER trigger that is not a constraint trigger; also, if - the trigger is an UPDATE trigger, it must not specify - a column_name list. + an AFTER trigger on a plain table (not a foreign table). + The trigger should not be a constraint trigger. Also, if the trigger is + an UPDATE trigger, it must not specify + a column_name list when using + this option. OLD TABLE may only be specified once, and only for a trigger that can fire on UPDATE or DELETE; it creates a transition relation containing the before-images of all rows From 0d93673affce8de80811e534b855cde0f0051fd7 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Sun, 3 Aug 2025 10:49:03 +0900 Subject: [PATCH 750/796] Fix assertion failure in pgbench when handling multiple pipeline sync messages. Previously, when running pgbench in pipeline mode with a custom script that triggered retriable errors (e.g., serialization errors), an assertion failure could occur: Assertion failed: (res == ((void*)0)), function discardUntilSync, file pgbench.c, line 3515. The root cause was that pgbench incorrectly assumed only a single pipeline sync message would be received at the end. In reality, multiple pipeline sync messages can be sent and must be handled properly. This commit fixes the issue by updating pgbench to correctly process multiple pipeline sync messages, preventing the assertion failure. Back-patch to v15, where the bug was introduced. Author: Fujii Masao Reviewed-by: Tatsuo Ishii Discussion: https://postgr.es/m/CAHGQGwFAX56Tfx+1ppo431OSWiLLuW72HaGzZ39NkLkop6bMzQ@mail.gmail.com Backpatch-through: 15 --- src/bin/pgbench/pgbench.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 0b45b7d453da1..7d2811ebe4277 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -3473,6 +3473,8 @@ doRetry(CState *st, pg_time_usec_t *now) static int discardUntilSync(CState *st) { + bool received_sync = false; + /* send a sync */ if (!PQpipelineSync(st->con)) { @@ -3487,10 +3489,21 @@ discardUntilSync(CState *st) PGresult *res = PQgetResult(st->con); if (PQresultStatus(res) == PGRES_PIPELINE_SYNC) + received_sync = true; + else if (received_sync) { - PQclear(res); - res = PQgetResult(st->con); + /* + * PGRES_PIPELINE_SYNC must be followed by another + * PGRES_PIPELINE_SYNC or NULL; otherwise, assert failure. + */ Assert(res == NULL); + + /* + * Reset ongoing sync count to 0 since all PGRES_PIPELINE_SYNC + * results have been discarded. + */ + st->num_syncs = 0; + PQclear(res); break; } PQclear(res); From 268266039d2886b5a7307bc6798dfd2c8b7e9eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Mon, 4 Aug 2025 13:26:45 +0200 Subject: [PATCH 751/796] doc: mention unusability of dropped CHECK to verify NOT NULL It's possible to use a CHECK (col IS NOT NULL) constraint to skip scanning a table for nulls when adding a NOT NULL constraint on the same column. However, if the CHECK constraint is dropped on the same command that the NOT NULL is added, this fails, i.e., makes the NOT NULL addition slow. The best we can do about it at this stage is to document this so that users aren't taken by surprise. (In Postgres 18 you can directly add the NOT NULL constraint as NOT VALID instead, so there's no longer much use for the CHECK constraint, therefore no point in building mechanism to support the case better.) Reported-by: Andrew Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/175385113607.786.16774570234342968908@wrigleys.postgresql.org --- doc/src/sgml/ref/alter_table.sgml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index c6ab432df1483..896a7e7baafe2 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -238,9 +238,10 @@ WITH ( MODULUS numeric_literal, REM provided none of the records in the table contain a NULL value for the column. Ordinarily this is checked during the ALTER TABLE by scanning the - entire table; however, if a valid CHECK constraint is - found which proves no NULL can exist, then the - table scan is skipped. + entire table; + however, if a valid CHECK constraint exists + (and is not dropped in the same command) which proves no + NULL can exist, then the table scan is skipped. From 3f8e01f34598c1dbf6185e4185b81820529b17d1 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Mon, 4 Aug 2025 20:51:42 +0900 Subject: [PATCH 752/796] Avoid unexpected shutdown when sync_replication_slots is enabled. Previously, enabling sync_replication_slots while wal_level was not set to logical could cause the server to shut down. This was because the postmaster performed a configuration check before launching the slot synchronization worker and raised an ERROR if the settings were incompatible. Since ERROR is treated as FATAL in the postmaster, this resulted in the entire server shutting down unexpectedly. This commit changes the postmaster to log that message with a LOG-level instead of raising an ERROR, allowing the server to continue running even with the misconfiguration. Back-patch to v17, where slot synchronization was introduced. Reported-by: Hugo DUBOIS Author: Fujii Masao Reviewed-by: Hugo DUBOIS Reviewed-by: Shveta Malik Discussion: https://postgr.es/m/CAH0PTU_pc3oHi__XESF9ZigCyzai1Mo3LsOdFyQA4aUDkm01RA@mail.gmail.com Backpatch-through: 17 --- src/backend/replication/logical/slotsync.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index e6dcb79231974..7be1459172b94 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -1040,14 +1040,14 @@ ValidateSlotSyncParams(int elevel) { /* * Logical slot sync/creation requires wal_level >= logical. - * - * Since altering the wal_level requires a server restart, so error out in - * this case regardless of elevel provided by caller. */ if (wal_level < WAL_LEVEL_LOGICAL) - ereport(ERROR, + { + ereport(elevel, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("replication slot synchronization requires \"wal_level\" >= \"logical\"")); + return false; + } /* * A physical replication slot(primary_slot_name) is required on the From 2af0751206bd2a0319104f8985cfcf38f1f0d83e Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Mon, 4 Aug 2025 16:21:31 +0100 Subject: [PATCH 753/796] Fix typo in create_index.sql. Introduced by 578b229718e. Author: Dean Rasheed Reviewed-by: Tender Wang Discussion: https://postgr.es/m/CAEZATCV_CzRSOPMf1gbHQ7xTmyrV6kE7ViCBD6B81WF7GfTAEA@mail.gmail.com Backpatch-through: 13 --- src/test/regress/expected/create_index.out | 4 ++-- src/test/regress/sql/create_index.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index cf6eac57349a0..b1df05593c4e8 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -1606,8 +1606,8 @@ DROP TABLE cwi_test; -- CREATE TABLE syscol_table (a INT); -- System columns cannot be indexed -CREATE INDEX ON syscolcol_table (ctid); -ERROR: relation "syscolcol_table" does not exist +CREATE INDEX ON syscol_table (ctid); +ERROR: index creation on system columns is not supported -- nor used in expressions CREATE INDEX ON syscol_table ((ctid >= '(1000,0)')); ERROR: index creation on system columns is not supported diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index e296891cab806..0c292cd660b06 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -629,7 +629,7 @@ DROP TABLE cwi_test; CREATE TABLE syscol_table (a INT); -- System columns cannot be indexed -CREATE INDEX ON syscolcol_table (ctid); +CREATE INDEX ON syscol_table (ctid); -- nor used in expressions CREATE INDEX ON syscol_table ((ctid >= '(1000,0)')); From a38462203a3309220025640ea9c2f83b46cab226 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Mon, 4 Aug 2025 15:07:08 -0400 Subject: [PATCH 754/796] Minor test fixes in 035_standby_logical_decoding.pl Import usleep, which, due to an oversight in oversight in commit 48796a98d5ae was used but not imported. Correct the comparison string used in two logfile checks. Previously, it was incorrect and thus the test could never have failed. Also wordsmith a comment to make it clear when hot_standby_feedback is meant to be on during the test scenarios. Reported-by: Melanie Plageman Author: Bertrand Drouvot Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/flat/CAAKRu_YO2mEm%3DZWZKPjTMU%3DgW5Y83_KMi_1cr51JwavH0ctd7w%40mail.gmail.com Backpatch-through: 16 --- src/test/recovery/t/035_standby_logical_decoding.pl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index 58c4402e80e6c..9c5b1ba64a811 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -8,6 +8,7 @@ use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; +use Time::HiRes qw(usleep); use Test::More; my ($stdin, $stdout, $stderr, @@ -582,7 +583,7 @@ sub wait_until_vacuum_can_remove /ERROR: cannot copy invalidated replication slot "vacuum_full_inactiveslot"/, "invalidated slot cannot be copied"); -# Turn hot_standby_feedback back on +# Set hot_standby_feedback to on change_hot_standby_feedback_and_wait_for_xmins(1, 1); ################################################## @@ -724,12 +725,12 @@ sub wait_until_vacuum_can_remove # message should not be issued ok( !$node_standby->log_contains( - "invalidating obsolete slot \"no_conflict_inactiveslot\"", $logstart), + "invalidating obsolete replication slot \"no_conflict_inactiveslot\"", $logstart), 'inactiveslot slot invalidation is not logged with vacuum on conflict_test' ); ok( !$node_standby->log_contains( - "invalidating obsolete slot \"no_conflict_activeslot\"", $logstart), + "invalidating obsolete replication slot \"no_conflict_activeslot\"", $logstart), 'activeslot slot invalidation is not logged with vacuum on conflict_test' ); From 080ff1ff82e9c33352fa7b701226ff6b0ebc4fbb Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 5 Aug 2025 16:51:10 -0400 Subject: [PATCH 755/796] Fix incorrect return value in brin_minmax_multi_distance_numeric(). The result of "DirectFunctionCall1(numeric_float8, d)" is already in Datum form, but the code was incorrectly applying PG_RETURN_FLOAT8() to it. On machines where float8 is pass-by-reference, this would result in complete garbage, since an unpredictable pointer value would be treated as an integer and then converted to float. It's not entirely clear how much of a problem would ensue on 64-bit hardware, but certainly interpreting a float8 bitpattern as uint64 and then converting that to float isn't the intended behavior. As luck would have it, even the complete-garbage case doesn't break BRIN indexes, since the results are only used to make choices about how to merge values into ranges: at worst, we'd make poor choices resulting in an inefficient index. Doubtless that explains the lack of field complaints. However, users with BRIN indexes that use the numeric_minmax_multi_ops opclass may wish to reindex in hopes of making their indexes more efficient. Author: Peter Eisentraut Co-authored-by: Tom Lane Discussion: https://postgr.es/m/2093712.1753983215@sss.pgh.pa.us Backpatch-through: 14 --- src/backend/access/brin/brin_minmax_multi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c index 8a13cd59c0b03..b88e9b2d7cc93 100644 --- a/src/backend/access/brin/brin_minmax_multi.c +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -2032,7 +2032,7 @@ brin_minmax_multi_distance_numeric(PG_FUNCTION_ARGS) d = DirectFunctionCall2(numeric_sub, a2, a1); /* a2 - a1 */ - PG_RETURN_FLOAT8(DirectFunctionCall1(numeric_float8, d)); + PG_RETURN_DATUM(DirectFunctionCall1(numeric_float8, d)); } /* From 661c2078e12e317eb1ead5b155958353f4e817e6 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Wed, 6 Aug 2025 16:48:47 +0900 Subject: [PATCH 756/796] doc: Recommend ANALYZE after ALTER TABLE ... SET EXPRESSION AS. ALTER TABLE ... SET EXPRESSION AS removes statistics for the target column, so running ANALYZE afterward is recommended. But this was previously not documented, even though a similar recommendation exists for ALTER TABLE ... SET DATA TYPE, which also clears the column's statistics. This commit updates the documentation to include the ANALYZE recommendation for SET EXPRESSION AS. Since v18, virtual generated columns are supported, and these columns never have statistics. Therefore, ANALYZE is not needed after SET DATA TYPE or SET EXPRESSION AS when used on virtual generated columns. This commit also updates the documentation to clarify that ANALYZE is unnecessary in such cases. Back-patch the ANALYZE recommendation for SET EXPRESSION AS to v17 where the feature was introduced, and the note about virtual generated columns to v18 where those columns were added. Author: Yugo Nagata Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/20250804151418.0cf365bd2855d606763443fe@sraoss.co.jp Backpatch-through: 17 --- doc/src/sgml/ref/alter_table.sgml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 896a7e7baafe2..029ddb391ec8d 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -265,6 +265,12 @@ WITH ( MODULUS numeric_literal, REM in the column is rewritten and all the future changes will apply the new generation expression. + + + When this form is used, the column's statistics are removed, + so running ANALYZE + on the table afterwards is recommended. + From 79e35bb8d55ea650c117997d448dd48d6d3bced9 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 7 Aug 2025 11:48:43 +0200 Subject: [PATCH 757/796] pg_upgrade: Improve message indentation Fix commit f295494d338 to use consistent four-space indentation for verbose messages. --- src/bin/pg_upgrade/check.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index c2da473353c07..b4b596872a6fc 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -1656,7 +1656,7 @@ check_for_not_null_inheritance(ClusterInfo *cluster) "If the parent column(s) are NOT NULL, then the child column must\n" "also be marked NOT NULL, or the upgrade will fail.\n" "You can fix this by running\n" - " ALTER TABLE tablename ALTER column SET NOT NULL;\n" + " ALTER TABLE tablename ALTER column SET NOT NULL;\n" "on each column listed in the file:\n" " %s", output_path); } From 38ba6e27c7bf1596bd34db773ffc4afbb1b33dc0 Mon Sep 17 00:00:00 2001 From: John Naylor Date: Thu, 7 Aug 2025 17:13:55 +0700 Subject: [PATCH 758/796] Update ICU C++ API symbols Recent ICU versions have added U_SHOW_CPLUSPLUS_HEADER_API, and we need to set this to zero as well to hide the ICU C++ APIs from pg_locale.h Per discussion, we want cpluspluscheck to work cleanly in backbranches, so backpatch both this and its predecessor commit ed26c4e25a4 to all supported versions. Reported-by: Tom Lane Discussion: https://postgr.es/m/1115793.1754414782%40sss.pgh.pa.us Backpatch-through: 13 --- .cirrus.tasks.yml | 3 --- src/include/utils/pg_locale.h | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index aa7c7dcd34dbd..e9e1d3a10b2b4 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -777,14 +777,11 @@ task: # - Don't use ccache, the files are uncacheable, polluting ccache's # cache # - Use -fmax-errors, as particularly cpluspluscheck can be very verbose - # - XXX have to disable ICU to avoid errors: - # https://postgr.es/m/20220323002024.f2g6tivduzrktgfa%40alap3.anarazel.de ### always: headers_headerscheck_script: | time ./configure \ ${LINUX_CONFIGURE_FEATURES} \ - --without-icu \ --quiet \ CC="gcc" CXX"=g++" CLANG="clang" make -s -j${BUILD_JOBS} clean diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index 040968d6ff295..0bc93142e9121 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -16,6 +16,11 @@ #include #endif #ifdef USE_ICU +/* only include the C APIs, to avoid errors in cpluspluscheck */ +#undef U_SHOW_CPLUSPLUS_API +#define U_SHOW_CPLUSPLUS_API 0 +#undef U_SHOW_CPLUSPLUS_HEADER_API +#define U_SHOW_CPLUSPLUS_HEADER_API 0 #include #endif From 3dbff7718c4b64de343adb8acf6c706501baf44d Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 7 Aug 2025 14:11:49 +0300 Subject: [PATCH 759/796] Revert "Clarify documentation for the initcap function" This reverts commit 1fe9e3822c4e574aa526b99af723e61e03f36d4f. That commit was a documentation improvement, not a bug fix. We don't normally backpatch such changes. Discussion: https://postgr.es/m/d8eacbeb8194c578a98317b86d7eb2ef0b6eb0e0.camel%40j-davis.com --- doc/src/sgml/func.sgml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 91557f4fb0fab..fc4b95378715e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -3110,11 +3110,8 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in Converts the first letter of each word to upper case and the - rest to lower case. When using the libc locale - provider, words are sequences of alphanumeric characters separated - by non-alphanumeric characters; when using the ICU locale provider, - words are separated according to - Unicode Standard Annex #29. + rest to lower case. Words are sequences of alphanumeric + characters separated by non-alphanumeric characters. initcap('hi THOMAS') From f0eaceaeae878635cd5385d44210a80a79a553aa Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 7 Aug 2025 13:27:32 +0200 Subject: [PATCH 760/796] Message improvements Backpatch of the relevant parts of commit 50fd428b2b9 for consistency. --- src/backend/replication/logical/slotsync.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index 7be1459172b94..1a8cff9cf1f83 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -214,7 +214,7 @@ update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid, ereport(slot->data.persistency == RS_TEMPORARY ? LOG : DEBUG1, errmsg("could not synchronize replication slot \"%s\"", remote_slot->name), - errdetail("Synchronization could lead to data loss as the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u.", + errdetail("Synchronization could lead to data loss, because the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u.", LSN_FORMAT_ARGS(remote_slot->restart_lsn), remote_slot->catalog_xmin, LSN_FORMAT_ARGS(slot->data.restart_lsn), @@ -577,7 +577,7 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid) { ereport(LOG, errmsg("could not synchronize replication slot \"%s\"", remote_slot->name), - errdetail("Synchronization could lead to data loss as standby could not build a consistent snapshot to decode WALs at LSN %X/%X.", + errdetail("Synchronization could lead to data loss, because the standby could not build a consistent snapshot to decode WALs at LSN %X/%X.", LSN_FORMAT_ARGS(slot->data.restart_lsn))); return false; From ad61f3a90694494793b73d22d55b14b376037df2 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 7 Aug 2025 14:29:02 +0300 Subject: [PATCH 761/796] Fix checkpointer shared memory allocation Use Min(NBuffers, MAX_CHECKPOINT_REQUESTS) instead of NBuffers in CheckpointerShmemSize() to match the actual array size limit set in CheckpointerShmemInit(). This prevents wasting shared memory when NBuffers > MAX_CHECKPOINT_REQUESTS. Also, fix the comment. Reported-by: Tom Lane Discussion: https://postgr.es/m/1439188.1754506714%40sss.pgh.pa.us Author: Xuneng Zhou Co-authored-by: Alexander Korotkov --- src/backend/postmaster/checkpointer.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index c5e86d3317ede..342376f933ecf 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -884,11 +884,14 @@ CheckpointerShmemSize(void) Size size; /* - * Currently, the size of the requests[] array is arbitrarily set equal to - * NBuffers. This may prove too large or small ... + * The size of the requests[] array is arbitrarily set equal to NBuffers. + * But there is a cap of MAX_CHECKPOINT_REQUESTS to prevent accumulating + * too many checkpoint requests in the ring buffer. */ size = offsetof(CheckpointerShmemStruct, requests); - size = add_size(size, mul_size(NBuffers, sizeof(CheckpointerRequest))); + size = add_size(size, mul_size(Min(NBuffers, + MAX_CHECKPOINT_REQUESTS), + sizeof(CheckpointerRequest))); return size; } From 57b9d3acb0013bcf4ad72ba2405d3b2c353dfda1 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 7 Aug 2025 13:29:08 +0200 Subject: [PATCH 762/796] doc: Formatting improvements Small touch-up on commits 25505082f0e and 50fd428b2b9. Fix the formatting of the example messages in the documentation and adjust the wording to match the code. --- doc/src/sgml/logicaldecoding.sgml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml index fc3ed623294b6..c5ce77658b1ee 100644 --- a/doc/src/sgml/logicaldecoding.sgml +++ b/doc/src/sgml/logicaldecoding.sgml @@ -290,7 +290,7 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU A logical slot will emit each change just once in normal operation. The current position of each slot is persisted only at checkpoint, so in - the case of a crash the slot may return to an earlier LSN, which will + the case of a crash the slot might return to an earlier LSN, which will then cause recent changes to be sent again when the server restarts. Logical decoding clients are responsible for avoiding ill effects from handling the same message more than once. Clients may wish to record @@ -409,7 +409,7 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU should be used with caution. Unlike automatic synchronization, it does not include cyclic retries, making it more prone to synchronization failures, particularly during initial sync scenarios where the required WAL files - or catalog rows for the slot may have already been removed or are at risk + or catalog rows for the slot might have already been removed or are at risk of being removed on the standby. In contrast, automatic synchronization via sync_replication_slots provides continuous slot updates, enabling seamless failover and supporting high availability. @@ -430,8 +430,8 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU standby, the slot will not be persisted to avoid data loss. In such cases, the following log message may appear: - LOG: could not synchronize replication slot "failover_slot" - DETAIL: Synchronization could lead to data loss as the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756 +LOG: could not synchronize replication slot "failover_slot" +DETAIL: Synchronization could lead to data loss, because the remote slot needs WAL at LSN 0/3003F28 and catalog xmin 754, but the standby has LSN 0/3003F28 and catalog xmin 756. If the logical replication slot is actively used by a consumer, no manual intervention is needed; the slot will advance automatically, From 8e90812f9f56ce0f6614ebae02abe7ccf7a1908c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 7 Aug 2025 18:04:45 -0400 Subject: [PATCH 763/796] doc: add float as an alias for double precision. Although the "Floating-Point Types" section says that "float" data type is taken to mean "double precision", this information was not reflected in the data type table that lists all data type aliases. Reported-by: alexander.kjall@hafslund.no Author: Euler Taveira Reviewed-by: Tom Lane Discussion: https://postgr.es/m/175456294638.800.12038559679827947313@wrigleys.postgresql.org Backpatch-through: 13 --- doc/src/sgml/datatype.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index cd82c1c17e0a4..621a1d903197b 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -117,7 +117,7 @@ double precision - float8 + float, float8 double precision floating-point number (8 bytes) From be60167ecddf93b4911b05943ed376dbec37c192 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 8 Aug 2025 09:07:51 +0900 Subject: [PATCH 764/796] Add information about "generation" when dropping twice pgstats entry Dropping twice a pgstats entry should not happen, and the error report generated was missing the "generation" counter (tracking when an entry is reused) that has been added in 818119afccd3. Like d92573adcb02, backpatch down to v15 where this information is useful to have, to gather more information from instances where the problem shows up. A report has shown that this error path has been reached on a standby based on 17.3, for a relation stats entry and an OID close to wraparound. Author: Bertrand Drouvot Discussion: https://postgr.es/m/CAN4RuQvYth942J2+FcLmJKgdpq6fE5eqyFvb_PuskxF2eL=Wzg@mail.gmail.com Backpatch-through: 15 --- src/backend/utils/activity/pgstat_shmem.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c index 6ede0f4a6e100..36dc7922d14b4 100644 --- a/src/backend/utils/activity/pgstat_shmem.c +++ b/src/backend/utils/activity/pgstat_shmem.c @@ -838,10 +838,11 @@ pgstat_drop_entry_internal(PgStatShared_HashEntry *shent, */ if (shent->dropped) elog(ERROR, - "trying to drop stats entry already dropped: kind=%s dboid=%u objoid=%u refcount=%u", + "trying to drop stats entry already dropped: kind=%s dboid=%u objoid=%u refcount=%u generation=%u", pgstat_get_kind_info(shent->key.kind)->name, shent->key.dboid, shent->key.objoid, - pg_atomic_read_u32(&shent->refcount)); + pg_atomic_read_u32(&shent->refcount), + pg_atomic_read_u32(&shent->generation)); shent->dropped = true; /* release refcount marking entry as not dropped */ From 436016fb99f3757d5fa5e3da6668c4c944131b3a Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Fri, 8 Aug 2025 10:50:02 +0900 Subject: [PATCH 765/796] Disallow collecting transition tuples from child foreign tables. Commit 9e6104c66 disallowed transition tables on foreign tables, but failed to account for cases where a foreign table is a child table of a partitioned/inherited table on which transition tables exist, leading to incorrect transition tuples collected from such foreign tables for queries on the parent table triggering transition capture. This occurred not only for inherited UPDATE/DELETE but for partitioned INSERT later supported by commit 3d956d956, which should have handled it at least for the INSERT case, but didn't. To fix, modify ExecAR*Triggers to throw an error if the given relation is a foreign table requesting transition capture. Also, this commit fixes make_modifytable so that in case of an inherited UPDATE/DELETE triggering transition capture, FDWs choose normal operations to modify child foreign tables, not DirectModify; which is needed because they would otherwise skip the calls to ExecAR*Triggers at execution, causing unexpected behavior. Author: Etsuro Fujita Reviewed-by: Amit Langote Discussion: https://postgr.es/m/CAPmGK14QJYikKzBDCe3jMbpGENnQ7popFmbEgm-XTNuk55oyHg%40mail.gmail.com Backpatch-through: 13 --- .../postgres_fdw/expected/postgres_fdw.out | 113 ++++++++++++++++++ contrib/postgres_fdw/sql/postgres_fdw.sql | 78 ++++++++++++ src/backend/commands/trigger.c | 28 +++++ src/backend/optimizer/plan/createplan.c | 18 ++- src/backend/optimizer/util/plancat.c | 54 +++++++++ src/include/optimizer/plancat.h | 2 + 6 files changed, 291 insertions(+), 2 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 602067c4e474f..81e752877cebd 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -8012,6 +8012,119 @@ DELETE FROM rem1; -- can't be pushed down (5 rows) DROP TRIGGER trig_row_after_delete ON rem1; +-- We are allowed to create transition-table triggers on both kinds of +-- inheritance even if they contain foreign tables as children, but currently +-- collecting transition tuples from such foreign tables is not supported. +CREATE TABLE local_tbl (a text, b int); +CREATE FOREIGN TABLE foreign_tbl (a text, b int) + SERVER loopback OPTIONS (table_name 'local_tbl'); +INSERT INTO foreign_tbl VALUES ('AAA', 42); +-- Test case for partition hierarchy +CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA'); +CREATE TRIGGER parent_tbl_insert_trig + AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_update_trig + AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_delete_trig + AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +INSERT INTO parent_tbl VALUES ('AAA', 42); +ERROR: cannot collect transition tuples from child foreign tables +COPY parent_tbl (a, b) FROM stdin; +ERROR: cannot collect transition tuples from child foreign tables +CONTEXT: COPY parent_tbl, line 1: "AAA 42" +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +INSERT INTO parent_tbl VALUES ('AAA', 42); +ERROR: cannot collect transition tuples from child foreign tables +COPY parent_tbl (a, b) FROM stdin; +ERROR: cannot collect transition tuples from child foreign tables +CONTEXT: COPY parent_tbl, line 1: "AAA 42" +ALTER SERVER loopback OPTIONS (DROP batch_size); +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE parent_tbl SET b = b + 1; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.parent_tbl + Foreign Update on public.foreign_tbl parent_tbl_1 + Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1 + -> Foreign Scan on public.foreign_tbl parent_tbl_1 + Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.* + Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE +(6 rows) + +UPDATE parent_tbl SET b = b + 1; +ERROR: cannot collect transition tuples from child foreign tables +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM parent_tbl; + QUERY PLAN +------------------------------------------------------------------ + Delete on public.parent_tbl + Foreign Delete on public.foreign_tbl parent_tbl_1 + Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1 + -> Foreign Scan on public.foreign_tbl parent_tbl_1 + Output: parent_tbl_1.tableoid, parent_tbl_1.ctid + Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE +(6 rows) + +DELETE FROM parent_tbl; +ERROR: cannot collect transition tuples from child foreign tables +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl; +DROP TABLE parent_tbl; +-- Test case for non-partition hierarchy +CREATE TABLE parent_tbl (a text, b int); +ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl; +CREATE TRIGGER parent_tbl_update_trig + AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_delete_trig + AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE parent_tbl SET b = b + 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Update on public.parent_tbl + Update on public.parent_tbl parent_tbl_1 + Foreign Update on public.foreign_tbl parent_tbl_2 + Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1 + -> Result + Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record) + -> Append + -> Seq Scan on public.parent_tbl parent_tbl_1 + Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record + -> Foreign Scan on public.foreign_tbl parent_tbl_2 + Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.* + Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE +(12 rows) + +UPDATE parent_tbl SET b = b + 1; +ERROR: cannot collect transition tuples from child foreign tables +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM parent_tbl; + QUERY PLAN +------------------------------------------------------------------------ + Delete on public.parent_tbl + Delete on public.parent_tbl parent_tbl_1 + Foreign Delete on public.foreign_tbl parent_tbl_2 + Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1 + -> Append + -> Seq Scan on public.parent_tbl parent_tbl_1 + Output: parent_tbl_1.tableoid, parent_tbl_1.ctid + -> Foreign Scan on public.foreign_tbl parent_tbl_2 + Output: parent_tbl_2.tableoid, parent_tbl_2.ctid + Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE +(10 rows) + +DELETE FROM parent_tbl; +ERROR: cannot collect transition tuples from child foreign tables +ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl; +DROP TABLE parent_tbl; +-- Cleanup +DROP FOREIGN TABLE foreign_tbl; +DROP TABLE local_tbl; -- =================================================================== -- test inheritance features -- =================================================================== diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 8acfb78f471cb..12c2940412555 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -2233,6 +2233,84 @@ EXPLAIN (verbose, costs off) DELETE FROM rem1; -- can't be pushed down DROP TRIGGER trig_row_after_delete ON rem1; + +-- We are allowed to create transition-table triggers on both kinds of +-- inheritance even if they contain foreign tables as children, but currently +-- collecting transition tuples from such foreign tables is not supported. + +CREATE TABLE local_tbl (a text, b int); +CREATE FOREIGN TABLE foreign_tbl (a text, b int) + SERVER loopback OPTIONS (table_name 'local_tbl'); + +INSERT INTO foreign_tbl VALUES ('AAA', 42); + +-- Test case for partition hierarchy +CREATE TABLE parent_tbl (a text, b int) PARTITION BY LIST (a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES IN ('AAA'); + +CREATE TRIGGER parent_tbl_insert_trig + AFTER INSERT ON parent_tbl REFERENCING NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_update_trig + AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_delete_trig + AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +INSERT INTO parent_tbl VALUES ('AAA', 42); + +COPY parent_tbl (a, b) FROM stdin; +AAA 42 +\. + +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); + +INSERT INTO parent_tbl VALUES ('AAA', 42); + +COPY parent_tbl (a, b) FROM stdin; +AAA 42 +\. + +ALTER SERVER loopback OPTIONS (DROP batch_size); + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE parent_tbl SET b = b + 1; +UPDATE parent_tbl SET b = b + 1; + +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM parent_tbl; +DELETE FROM parent_tbl; + +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl; +DROP TABLE parent_tbl; + +-- Test case for non-partition hierarchy +CREATE TABLE parent_tbl (a text, b int); +ALTER FOREIGN TABLE foreign_tbl INHERIT parent_tbl; + +CREATE TRIGGER parent_tbl_update_trig + AFTER UPDATE ON parent_tbl REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER parent_tbl_delete_trig + AFTER DELETE ON parent_tbl REFERENCING OLD TABLE AS old_table + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE parent_tbl SET b = b + 1; +UPDATE parent_tbl SET b = b + 1; + +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM parent_tbl; +DELETE FROM parent_tbl; + +ALTER FOREIGN TABLE foreign_tbl NO INHERIT parent_tbl; +DROP TABLE parent_tbl; + +-- Cleanup +DROP FOREIGN TABLE foreign_tbl; +DROP TABLE local_tbl; + -- =================================================================== -- test inheritance features -- =================================================================== diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ad28abe085f42..c27d007ed9dfe 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2537,6 +2537,15 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + transition_capture->tcs_insert_new_table) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_insert_after_row) || (transition_capture && transition_capture->tcs_insert_new_table)) AfterTriggerSaveEvent(estate, relinfo, NULL, NULL, @@ -2807,6 +2816,15 @@ ExecARDeleteTriggers(EState *estate, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + transition_capture->tcs_delete_old_table) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_delete_after_row) || (transition_capture && transition_capture->tcs_delete_old_table)) { @@ -3160,6 +3178,16 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + if (relinfo->ri_FdwRoutine && transition_capture && + (transition_capture->tcs_update_old_table || + transition_capture->tcs_update_new_table)) + { + Assert(relinfo->ri_RootResultRelInfo); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot collect transition tuples from child foreign tables"))); + } + if ((trigdesc && trigdesc->trig_update_after_row) || (transition_capture && (transition_capture->tcs_update_old_table || diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c0af10ebd34c3..25e126d1c3963 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -7038,6 +7038,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan, int epqParam) { ModifyTable *node = makeNode(ModifyTable); + bool transition_tables = false; + bool transition_tables_valid = false; List *fdw_private_list; Bitmapset *direct_modify_plans; ListCell *lc; @@ -7182,7 +7184,7 @@ make_modifytable(PlannerInfo *root, Plan *subplan, * callback functions needed for that and (2) there are no local * structures that need to be run for each modified row: row-level * triggers on the foreign table, stored generated columns, WITH CHECK - * OPTIONs from parent views. + * OPTIONs from parent views, transition tables on the named relation. */ direct_modify = false; if (fdwroutine != NULL && @@ -7193,7 +7195,19 @@ make_modifytable(PlannerInfo *root, Plan *subplan, withCheckOptionLists == NIL && !has_row_triggers(root, rti, operation) && !has_stored_generated_columns(root, rti)) - direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i); + { + /* transition_tables is the same for all result relations */ + if (!transition_tables_valid) + { + transition_tables = has_transition_tables(root, + nominalRelation, + operation); + transition_tables_valid = true; + } + if (!transition_tables) + direct_modify = fdwroutine->PlanDirectModify(root, node, + rti, i); + } if (direct_modify) direct_modify_plans = bms_add_member(direct_modify_plans, i); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 86655f05dc8d6..ac82a021e977c 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2281,6 +2281,60 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event) return result; } +/* + * has_transition_tables + * + * Detect whether the specified relation has any transition tables for event. + */ +bool +has_transition_tables(PlannerInfo *root, Index rti, CmdType event) +{ + RangeTblEntry *rte = planner_rt_fetch(rti, root); + Relation relation; + TriggerDesc *trigDesc; + bool result = false; + + Assert(rte->rtekind == RTE_RELATION); + + /* Currently foreign tables cannot have transition tables */ + if (rte->relkind == RELKIND_FOREIGN_TABLE) + return result; + + /* Assume we already have adequate lock */ + relation = table_open(rte->relid, NoLock); + + trigDesc = relation->trigdesc; + switch (event) + { + case CMD_INSERT: + if (trigDesc && + trigDesc->trig_insert_new_table) + result = true; + break; + case CMD_UPDATE: + if (trigDesc && + (trigDesc->trig_update_old_table || + trigDesc->trig_update_new_table)) + result = true; + break; + case CMD_DELETE: + if (trigDesc && + trigDesc->trig_delete_old_table) + result = true; + break; + /* There is no separate event for MERGE, only INSERT/UPDATE/DELETE */ + case CMD_MERGE: + result = false; + break; + default: + elog(ERROR, "unrecognized CmdType: %d", (int) event); + break; + } + + table_close(relation, NoLock); + return result; +} + /* * has_stored_generated_columns * diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index f59b77b10125c..1206f60a715ba 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -72,6 +72,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node); extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event); +extern bool has_transition_tables(PlannerInfo *root, Index rti, CmdType event); + extern bool has_stored_generated_columns(PlannerInfo *root, Index rti); extern Bitmapset *get_dependent_generated_columns(PlannerInfo *root, Index rti, From 83f4ac57be3c74d4e04157eb1942511a5c7a5273 Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Fri, 8 Aug 2025 14:36:39 +0900 Subject: [PATCH 766/796] pg_dump: Fix incorrect parsing of object types in pg_dump --filter. Previously, pg_dump --filter could misinterpret invalid object types in the filter file as valid ones. For example, the invalid object type "table-data" (likely a typo for the valid "table_data") could be mistakenly recognized as "table", causing pg_dump to succeed when it should have failed. This happened because pg_dump identified keywords as sequences of ASCII alphabetic characters, treating non-alphabetic characters (like hyphens) as keyword boundaries. As a result, "table-data" was parsed as "table". To fix this, pg_dump --filter now treats keywords as strings of non-whitespace characters, ensuring invalid types like "table-data" are correctly rejected. Back-patch to v17, where the --filter option was introduced. Author: Fujii Masao Reviewed-by: Xuneng Zhou Reviewed-by: Srinath Reddy Reviewed-by: Daniel Gustafsson Discussion: https://postgr.es/m/CAHGQGwFzPKUwiV5C-NLBqz1oK1+z9K8cgrF+LcxFem-p3_Ftug@mail.gmail.com Backpatch-through: 17 --- src/bin/pg_dump/filter.c | 13 ++++++++----- src/bin/pg_dump/t/005_pg_dump_filterfile.pl | 14 ++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/bin/pg_dump/filter.c b/src/bin/pg_dump/filter.c index 5815cd237483f..30d3ddff6af93 100644 --- a/src/bin/pg_dump/filter.c +++ b/src/bin/pg_dump/filter.c @@ -172,9 +172,8 @@ pg_log_filter_error(FilterStateData *fstate, const char *fmt,...) /* * filter_get_keyword - read the next filter keyword from buffer * - * Search for keywords (limited to ascii alphabetic characters) in - * the passed in line buffer. Returns NULL when the buffer is empty or the first - * char is not alpha. The char '_' is allowed, except as the first character. + * Search for keywords (strings of non-whitespace characters) in the passed + * in line buffer. Returns NULL when the buffer is empty or no keyword exists. * The length of the found keyword is returned in the size parameter. */ static const char * @@ -183,6 +182,9 @@ filter_get_keyword(const char **line, int *size) const char *ptr = *line; const char *result = NULL; + /* The passed buffer must not be NULL */ + Assert(*line != NULL); + /* Set returned length preemptively in case no keyword is found */ *size = 0; @@ -190,11 +192,12 @@ filter_get_keyword(const char **line, int *size) while (isspace((unsigned char) *ptr)) ptr++; - if (isalpha((unsigned char) *ptr)) + /* Grab one keyword that's the string of non-whitespace characters */ + if (*ptr != '\0' && !isspace((unsigned char) *ptr)) { result = ptr++; - while (isalpha((unsigned char) *ptr) || *ptr == '_') + while (*ptr != '\0' && !isspace((unsigned char) *ptr)) ptr++; *size = ptr - result; diff --git a/src/bin/pg_dump/t/005_pg_dump_filterfile.pl b/src/bin/pg_dump/t/005_pg_dump_filterfile.pl index a80e13a0d36f5..12fd0eaeedbb2 100644 --- a/src/bin/pg_dump/t/005_pg_dump_filterfile.pl +++ b/src/bin/pg_dump/t/005_pg_dump_filterfile.pl @@ -374,10 +374,16 @@ qr/invalid filter command/, "invalid syntax: incorrect filter command"); -# Test invalid object type +# Test invalid object type. +# +# This test also verifies that keywords are correctly recognized as strings of +# non-whitespace characters. If the parser incorrectly treats non-whitespace +# delimiters (like hyphens) as keyword boundaries, "table-data" might be +# misread as the valid object type "table". To catch such issues, +# "table-data" is used here as an intentionally invalid object type. open $inputfile, '>', "$tempdir/inputfile.txt" or die "unable to open filterfile for writing"; -print $inputfile "include xxx"; +print $inputfile "exclude table-data one"; close $inputfile; command_fails_like( @@ -385,8 +391,8 @@ 'pg_dump', '-p', $port, '-f', $plainfile, "--filter=$tempdir/inputfile.txt", 'postgres' ], - qr/unsupported filter object type: "xxx"/, - "invalid syntax: invalid object type specified, should be table, schema, foreign_data or data" + qr/unsupported filter object type: "table-data"/, + "invalid syntax: invalid object type specified" ); # Test missing object identifier pattern From 220f1c0b80ccb9de7da99d6d24775c74bdbc6a1d Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Fri, 8 Aug 2025 17:35:01 +0900 Subject: [PATCH 767/796] Fix oversight in FindTriggerIncompatibleWithInheritance. This function is called from ATExecAttachPartition/ATExecAddInherit, which prevent tables with row-level triggers with transition tables from becoming partitions or inheritance children, to check if there is such a trigger on the given table, but failed to check if a found trigger is row-level, causing the caller functions to needlessly prevent a table with only a statement-level trigger with transition tables from becoming a partition or inheritance child. Repair. Oversight in commit 501ed02cf. Author: Etsuro Fujita Discussion: https://postgr.es/m/CAPmGK167mXzwzzmJ_0YZ3EZrbwiCxtM1vogH_8drqsE6PtxRYw%40mail.gmail.com Backpatch-through: 13 --- src/backend/commands/trigger.c | 2 ++ src/test/regress/expected/triggers.out | 8 ++++++++ src/test/regress/sql/triggers.sql | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index c27d007ed9dfe..c89763440c982 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2279,6 +2279,8 @@ FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc) { Trigger *trigger = &trigdesc->triggers[i]; + if (!TRIGGER_FOR_ROW(trigger->tgtype)) + continue; if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL) return trigger->tgname; } diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index b7f0c3027e016..a6786e204a3da 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -3016,6 +3016,10 @@ NOTICE: trigger = child3_delete_trig, old table = (42,CCC) -- copy into parent sees parent-format tuples copy parent (a, b) from stdin; NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42) +-- check detach/reattach behavior; statement triggers with transition tables +-- should not prevent a table from becoming a partition again +alter table parent detach partition child1; +alter table parent attach partition child1 for values in ('AAA'); -- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children drop trigger child1_insert_trig on child1; @@ -3213,6 +3217,10 @@ NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42) create index on parent(b); copy parent (a, b) from stdin; NOTICE: trigger = parent_insert_trig, new table = (DDD,42) +-- check disinherit/reinherit behavior; statement triggers with transition +-- tables should not prevent a table from becoming an inheritance child again +alter table child1 no inherit parent; +alter table child1 inherit parent; -- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children drop trigger child1_insert_trig on child1; diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index a3c3115a6e721..5a0fa012f1fbc 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -2110,6 +2110,11 @@ BBB 42 CCC 42 \. +-- check detach/reattach behavior; statement triggers with transition tables +-- should not prevent a table from becoming a partition again +alter table parent detach partition child1; +alter table parent attach partition child1 for values in ('AAA'); + -- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children drop trigger child1_insert_trig on child1; @@ -2329,6 +2334,11 @@ copy parent (a, b) from stdin; DDD 42 \. +-- check disinherit/reinherit behavior; statement triggers with transition +-- tables should not prevent a table from becoming an inheritance child again +alter table child1 no inherit parent; +alter table child1 inherit parent; + -- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children drop trigger child1_insert_trig on child1; From 502f6559b6164023238e7e869e4650ec6c85f16f Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 8 Aug 2025 12:06:06 +0200 Subject: [PATCH 768/796] Fix incorrect lack of Datum conversion in _int_matchsel() The code used return (Selectivity) 0.0; where PG_RETURN_FLOAT8(0.0); would be correct. On 64-bit systems, these are pretty much equivalent, but on 32-bit systems, PG_RETURN_FLOAT8() correctly produces a pointer, but the old wrong code would return a null pointer, possibly leading to a crash elsewhere. We think this code is actually not reachable because bqarr_in won't accept an empty query, and there is no other function that will create query_int values. But better be safe and not let such incorrect code lie around. Reviewed-by: Tom Lane Discussion: https://www.postgresql.org/message-id/flat/8246d7ff-f4b7-4363-913e-827dadfeb145%40eisentraut.org --- contrib/intarray/_int_selfuncs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/intarray/_int_selfuncs.c b/contrib/intarray/_int_selfuncs.c index bc1ad3a80487f..f9f1c7bb13800 100644 --- a/contrib/intarray/_int_selfuncs.c +++ b/contrib/intarray/_int_selfuncs.c @@ -178,7 +178,7 @@ _int_matchsel(PG_FUNCTION_ARGS) if (query->size == 0) { ReleaseVariableStats(vardata); - return (Selectivity) 0.0; + PG_RETURN_FLOAT8(0.0); } /* From decea1aa0deee2689a677ccbabffb7124784da0b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 8 Aug 2025 13:47:04 -0400 Subject: [PATCH 769/796] First-draft release notes for 17.6. As usual, the release notes for other branches will be made by cutting these down, but put them up for community review first. --- doc/src/sgml/release-17.sgml | 1652 ++++++++++++++++++++++++++++++++++ 1 file changed, 1652 insertions(+) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index 57e3b8fcc01d5..6961fe95b7b6b 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -1,6 +1,1658 @@ + + Release 17.6 + + + Release date: + 2025-08-14 + + + + This release contains a variety of fixes from 17.5. + For information about new features in major release 17, see + . + + + + Migration to Version 17.6 + + + A dump/restore is not required for those running 17.X. + + + + However, if you have any + BRIN numeric_minmax_multi_ops indexes, it is + advisable to reindex them after updating. See the first changelog + entry below. + + + + Also, if you are upgrading from a version earlier than 17.5, + see . + + + + + Changes + + + + + + + Fix incorrect distance calculation in + BRIN numeric_minmax_multi_ops support function + (Peter Eisentraut, Tom Lane) + § + + + + The results were sometimes wrong on 64-bit platforms, and wildly + wrong on 32-bit platforms. This did not produce obvious failures + because the logic is only used to choose how to merge values into + ranges; at worst the index would become inefficient and bloated. + Nonetheless it's recommended to reindex any BRIN indexes that use + the numeric_minmax_multi_ops operator class. + + + + + + + Avoid regression in the size of XML input that we will accept + (Michael Paquier, Erik Wienhold) + § + § + + + + Our workaround for a bug in early 2.13.x releases + of libxml2 made use of a code path that + rejects text chunks exceeding 10MB, whereas the previous coding did + not. Those early releases are presumably extinct in the wild by + now, so revert to the previous coding. + + + + + + + Fix MERGE problems with concurrent updates + (Dean Rasheed) + § + + + + If a MERGE + inside a CTE attempts an update or delete on a table with + BEFORE ROW triggers, and a + concurrent UPDATE or DELETE + changes the target row, the MERGE command would + fail (crashing in the case of an update action, and potentially + executing the wrong action in the case of a delete action). + + + + + + + Fix MERGE into a plain-inheritance parent table + (Dean Rasheed) + § + + + + Insertions into such a target table could crash or produce incorrect + query results due to failing to handle WITH CHECK + OPTION and RETURNING actions. + + + + + + + Allow tables with statement-level triggers to become partitions or + inheritance children (Etsuro Fujita) + § + + + + We do not allow partitions or inheritance child tables to have + row-level triggers with transition tables, because an operation on + the whole inheritance tree would need to maintain a separate + transition table for each such child table. But that problem does + not apply for statement-level triggers, because only the parent's + statement-level triggers will be fired. The code that checks + whether an existing table can become a partition or inheritance + child nonetheless rejected both kinds of trigger. + + + + + + + Disallow collecting transition tuples from child foreign tables + (Etsuro Fujita) + § + + + + We do not support triggers with transition tables on foreign tables. + However, the case of a partition or inheritance child that is a + foreign table was overlooked. If the parent has such a trigger, + incorrect transition tuples were collected from the foreign child. + Instead throw an error, reporting that the case is not supported. + + + + + + + Allow resetting unknown custom parameters with reserved prefixes + (Nathan Bossart) + § + + + + Previously, if a parameter setting had been stored + using ALTER DATABASE/ROLE/SYSTEM, the stored + setting could not be removed if the parameter was unknown but had a + reserved prefix. This case could arise if an extension used to have + a parameter, but that parameter had been removed in an upgrade. + + + + + + + Fix a potential deadlock during ALTER SUBSCRIPTION ... DROP + PUBLICATION (Ajin Cherian) + § + + + + Ensure that server processes acquire catalog locks in a consistent + order during replication origin drops. + + + + + + + Shorten the race condition window for creating indexes with + conflicting names (Tom Lane) + § + + + + When choosing an auto-generated name for an index, avoid conflicting + with not-yet-committed pg_class rows as + well as fully-valid ones. This avoids possibly choosing the same + name as some concurrent CREATE INDEX did, + when that command is still in process of filling its index, or is + done but is part of a not-yet-committed transaction. There's still + a window for trouble, but it's only as long as the time needed to + validate a new index's parameters and insert + its pg_class row. + + + + + + + Prevent usage of incorrect VACUUM options in some + cases where multiple tables are vacuumed in a single command (Nathan + Bossart, Michael Paquier) + § + + + + The TRUNCATE and INDEX_CLEANUP + options of one table could be applied to others. + + + + + + + Ensure that the table's free-space map is updated in a timely way + when vacuuming a table that has no indexes (Masahiko Sawada) + § + + + + A previous optimization caused FSM vacuuming to sometimes be skipped + for such tables. + + + + + + + Fix processing of character classes within SIMILAR + TO regular expressions (Laurenz Albe) + § + § + + + + The code that translates SIMILAR TO pattern + matching expressions to POSIX-style regular expressions did not + consider that square brackets can be nested. For example, in a + pattern like [[:alpha:]%_], the code treated + the % and _ characters as + metacharacters when they should be literals. + + + + + + + When deparsing queries, always add parentheses around the expression + in FETCH FIRST expression ROWS + WITH TIES clauses (Heikki Linnakangas) + § + § + + + + This avoids some cases where the deparsed result wasn't + syntactically valid. + + + + + + + Limit the checkpointer process's fsync request queue size (Alexander + Korotkov, Xuneng Zhou) + § + § + + + + With very large shared_buffers settings, it was + possible for the checkpointer to attempt to allocate more than 1GB + for fsync requests, leading to failure and an infinite loop. Clamp + the queue size to prevent this scenario. + + + + + + + Avoid infinite wait in logical decoding when reading a + partially-written WAL record (Vignesh C) + § + + + + If the server crashes after writing the first part of a WAL record + that would span multiple pages, subsequent logical decoding of the + WAL stream would wait for data to arrive on the next WAL page. + That might never happen if the server is now idle. + + + + + + + Fix inconsistent spelling of LWLock names + for MultiXactOffsetSLRU + and MultiXactMemberSLRU (Bertrand Drouvot) + § + + + + This resulted in different wait-event names being displayed + in pg_wait_events + and pg_stat_activity, potentially breaking + monitoring queries that join those views. + + + + + + + Fix inconsistent quoting of role names in ACL strings (Tom Lane) + § + + + + The previous quoting rule was locale-sensitive, which could lead to + portability problems when transferring aclitem values + across installations. (pg_dump does not + do that, but other tools might.) To ensure consistency, always quote + non-ASCII characters in aclitem output; but to preserve + backward compatibility, never require that they be quoted + during aclitem input. + + + + + + + Reject equal signs (=) in the names of relation + options and foreign-data options (Tom Lane) + § + + + + There's no evident use-case for option names like this, and allowing + them creates ambiguity in the stored representation. + + + + + + + Fix potentially-incorrect decompression of LZ4-compressed archive + data (Mikhail Gribkov) + § + + + + This error seems to manifest only with not-very-compressible input + data, which may explain why it escaped detection. + + + + + + + Avoid a rare scenario where a btree index scan could mark the wrong + index entries as dead (Peter Geoghegan) + § + + + + + + + Avoid re-distributing cache invalidation messages from other + transactions during logical replication (vignesh C) + § + + + + Our previous round of minor releases included a bug fix to ensure + that replication receiver processes would respond to cross-process + cache invalidation messages, preventing them from using stale + catalog data while performing replication updates. However, the fix + unintentionally made them also redistribute those messages again, + leading to an exponential increase in the number of invalidation + messages, which would often end in a memory allocation failure. + Fix by not redistributing received messages. + + + + + + + Avoid unexpected server shutdown when replication slot + synchronization is misconfigured (Fujii Masao) + § + + + + The postmaster process would report an error (and then stop) + if sync_replication_slots was set + to true while wal_level was + less than logical. The desired behavior is just + that slot synchronization should be disabled, so reduce this error + message's level to avoid postmaster shutdown. + + + + + + + Avoid premature removal of old WAL during checkpoints (Vitaly Davydov) + § + + + + If a replication slot's restart point is advanced while a checkpoint + is in progress, no-longer-needed WAL segments could get removed too + soon, leading to recovery failure if the database crashes + immediately afterwards. Fix by keeping them for one additional + checkpoint cycle. + + + + + + + Never move a replication slot's confirmed-flush position backwards + (Shveta Malik) + § + + + + In some cases a replication client could acknowledge an LSN that's + past what it has stored persistently, and then perhaps send an older + LSN after a restart. We consider this not-a-bug so long as the + client did not have anything it needed to do for the WAL between the + two points. However, we should not re-send that WAL for fear of + data duplication, so make sure we always believe the latest + confirmed LSN for a given slot. + + + + + + + Prevent excessive delays before launching new logical replication + workers (Tom Lane) + § + + + + In some cases the logical replication launcher could sleep + considerably longer than the + configured wal_retrieve_retry_interval before + launching a new worker. + + + + + + + Fix use-after-free during logical replication of INSERT + ... ON CONFLICT (Ethan Mertz, Michael Paquier) + § + + + + This could result in incorrect progress reporting, or with very bad + luck it could result in a crash of the WAL sender process. + + + + + + + Allow waiting for a transaction on a standby server to be + interrupted (Kevin K Biju) + § + + + + Creation of a replication slot on a standby server may require waiting + for some active transaction(s) to finish on the primary and then be + replayed on the standby. Since that could be an indefinite wait, + it's desirable to allow the operation to be cancelled, but there was + no check for query cancel in the loop. + + + + + + + Do not let cascading logical WAL senders try to send data that's + beyond what has been replayed on their standby server (Alexey + Makhmutov) + § + + + + This avoids a situation where such WAL senders could get stuck at + standby server shutdown, waiting for replay work that will not + happen because the server's startup process is already shut down. + + + + + + + Fix per-relation memory leakage in autovacuum (Tom Lane) + § + + + + + + + Fix session-lifespan memory leaks + in XMLSERIALIZE(... INDENT) + (Dmitry Kovalenko, Tom Lane) + § + § + + + + + + + Fix possible crash after out-of-memory when allocating large chunks + with the bump allocator (Tom Lane) + § + + + + + + + Fix some places that might try to fetch toasted fields of system + catalogs without any snapshot (Nathan Bossart) + § + + + + This could result in an assertion failure or cannot fetch + toast data without an active snapshot error. + + + + + + + Avoid assertion failure during cross-table constraint updates + (Tom Lane, Jian He) + § + § + + + + + + + Remove faulty assertion that a command tag must have been determined + by the end of PortalRunMulti() (Álvaro Herrera) + § + + + + This failed in edge cases such as an empty prepared statement. + + + + + + + Fix assertion failure in XMLTABLE parsing + (Richard Guo) + § + + + + + + + Restore the ability to run PL/pgSQL expressions in parallel + (Dipesh Dhameliya) + § + + + + PL/pgSQL's notion of an expression is very broad, + encompassing any SQL SELECT query that returns a + single column and no more than one row. So there are cases, for + example evaluation of an aggregate function, where the query + involves significant work and it'd be useful to run it with parallel + workers. This used to be possible, but a previous bug fix + unintentionally disabled it. + + + + + + + Fix edge-case resource leaks in PL/Python error reporting (Tom Lane) + § + § + + + + An out-of-memory failure while reporting an error from Python could + result in failure to drop reference counts on Python objects, + leading to session-lifespan memory leakage. + + + + + + + Fix libpq's + PQcancelCreate() function for the case where + the server's address was specified using hostaddr + (Sergei Kornilov) + § + + + + libpq would crash if the resulting cancel + object was actually used. + + + + + + + Fix libpq's PQport() + function to never return NULL unless the passed connection is NULL + (Daniele Varrazzo) + § + + + + This is the documented behavior, but + recent libpq versions would return NULL + in some cases where the user had not provided a port specification. + Revert to our historical behavior of returning an empty string in + such cases. (v18 and later will return the compiled-in default port + number, typically "5432", instead.) + + + + + + + Avoid failure when GSSAPI authentication requires packets larger + than 16kB (Jacob Champion, Tom Lane) + § + + + + Larger authentication packets are needed for Active Directory users + who belong to many AD groups. This limitation manifested in + connection failures with unintelligible error messages, + typically GSSAPI context establishment error: The routine + must be called again to complete its function: Unknown + error. + + + + + + + Fix timing-dependent failures in SSL and GSSAPI data transmission + (Tom Lane) + § + + + + When using SSL or GSSAPI encryption in non-blocking + mode, libpq sometimes failed + with SSL error: bad length or GSSAPI caller + failed to retransmit all data needing to be retried. + + + + + + + Avoid null-pointer dereference during connection lookup + in ecpg applications (Aleksander + Alekseev) + § + + + + The case could occur only if the application has some connections + that are named and some that are not. + + + + + + + Improve psql's tab completion + for COPY and \copy options + (Atsushi Torikoshi) + § + + + + The same completions were offered for both COPY + FROM and COPY TO, although some options + are only valid for one case or the other. Distinguish these cases + to provide more accurate suggestions. + + + + + + + Avoid assertion failure in pgbench when + multiple pipeline sync messages are received (Fujii Masao) + § + + + + + + + Fix duplicate transaction replay when initializing a subscription + with pg_createsubscriber (Shlok Kyal) + § + + + + It was possible for the last transaction processed during subscriber + recovery to be sent again once normal replication begins. + + + + + + + Ensure that pg_dump dumps comments on + not-null constraints on domain types (Jian He, Álvaro Herrera) + § + + + + + + + Ensure that pg_dump dumps comments on + domain constraints in a valid order (Jian He) + § + + + + In some cases the comment command could appear before creation of + the constraint. + + + + + + + Ensure stable sort ordering in pg_dump + for all types of database objects (Noah Misch, Andreas Karlsson) + § + § + + + + pg_dump sorts objects by their logical + names before performing dependency-driven reordering. This sort did + not account for the full unique key identifying certain object types + such as rules and constraints, and thus it could produce dissimilar + sort orders for logically-identical databases. That made it + difficult to compare databases by + diff'ing pg_dump output, so improve the + logic to ensure stable sort ordering in all cases. + + + + + + + Fix incorrect parsing of object types + in pg_dump filter files (Fujii Masao) + § + + + + Treat keywords as extending to the next whitespace, rather than + stopping at the first non-alphanumeric character as before. + This makes no difference for valid keywords, but it allows some + error cases to be recognized properly. For + example, table-data will now be rejected, whereas + previously it was misinterpreted as table. + + + + + + + pg_restore failed to restore large + objects (BLOBs) from directory-format dumps made + by pg_dump versions + before PostgreSQL v12 (Pavel Stehule) + § + + + + + + + In pg_upgrade, check for inconsistent + inherited not-null constraints (Ali Akbar) + § + § + § + + + + PostgreSQL versions before 18 allow an + inherited column not-null constraint to be dropped. However, this + results in a schema that cannot be restored, leading to failure + in pg_upgrade. Detect such cases + during pg_upgrade's preflight checks to + allow users to fix them before initiating the upgrade. + + + + + + + Don't require that the target installation + have max_slot_wal_keep_size set to its default + during pg_upgrade (Dilip Kumar) + § + + + + + + + Avoid assertion failure if track_commit_timestamp + is enabled during initdb (Hayato Kuroda, + Andy Fan) + § + + + + + + + Fix pg_waldump to show information about + dropped statistics in PREPARE TRANSACTION WAL + records (Daniil Davydov) + § + + + + + + + Avoid possible leak of the open connection + during contrib/dblink connection establishment + (Tom Lane) + § + + + + In the rare scenario where we hit out-of-memory while inserting the + new connection object into dblink's hashtable, the open connection + would be leaked until end of session, leaving an idle session + sitting on the remote server. + + + + + + + Make contrib/pg_prewarm cope with very + large shared_buffers settings (Daria Shanina) + § + + + + Autoprewarm failed with a memory allocation error + if shared_buffers was larger than about 50 + million buffers (400GB). + + + + + + + Prevent assertion failure + in contrib/pg_prewarm (Masahiro Ikeda) + § + + + + Applying pg_prewarm() to a relation + lacking storage (such as a view) caused an assertion failure, + although there was no ill effect in non-assert builds. + Add an error check to reject that case. + + + + + + + In contrib/pg_stat_statements, avoid leaving + gaps in the set of parameter numbers used in a normalized query + (Sami Imseih) + § + + + + + + + Fix memory leakage in contrib/postgres_fdw's + DirectModify methods (Tom Lane) + § + + + + The PGresult holding the results of the + remote modify command would be leaked for the rest of the session if + the query fails between invocations of the DirectModify methods, + which could happen when there's RETURNING data to + process. + + + + + + + Ensure that directories listed + in configure's + + and options are searched before + system-supplied directories (Tom Lane) + § + + + + A common reason for using these options is to allow a user-built + version of some library to override the system-supplied version. + However, that failed to work in some environments because of + careless ordering of switches in the commands issued by the makefiles. + + + + + + + Fix configure's checks + for __cpuid() + and __cpuidex() (Lukas Fittl, Michael Paquier) + § + + + + configure failed to detect these + Windows-specific functions, so that they would not be used, + leading to slower-than-necessary CRC computations since the + availability of hardware instructions could not be verified. + The practical impact of this error was limited, because production + builds for Windows typically do not use the Autoconf toolchain. + + + + + + + Fix build failure with option on + Solaris-based platforms (Tom Lane) + § + + + + Solaris is inconsistent with other Unix platforms about the API for + PAM authentication. This manifested as an inconsistent + pointer compiler warning, which we never did anything about. + But as of GCC 14 it's an error not warning by default, so fix it. + + + + + + + Make our code portable to GNU Hurd (Michael Banck, Christoph Berg, + Samuel Thibault) + § + § + + + + Fix assumptions about IOV_MAX + and O_RDONLY that don't hold on Hurd. + + + + + + + Make our usage of memset_s() conform strictly + to the C11 standard (Tom Lane) + § + + + + This avoids compile failures on some platforms. + + + + + + + Silence compatibility warning when using Meson to build with MSVC + (Peter Eisentraut) + § + + + + + + + Prevent uninitialized-value compiler warnings in JSONB comparison + code (Tom Lane) + § + + + + + + + Avoid deprecation warnings when building + with libxml2 2.14 and later + (Michael Paquier) + § + + + + + + + Avoid problems when compiling pg_locale.h under + C++ (John Naylor) + § + + + + PostgreSQL header files generally need to + be wrapped in extern "C" { ... } in order to be + included in extensions written in C++. This failed + for pg_locale.h because of its use + of libicu headers, but we can work around + that by suppressing C++-only declarations in those headers. C++ + extensions that want to use libicu's C++ + APIs can do so by including the libicu + headers ahead of pg_locale.h. + + + + + + + + Release 17.5 From 66df3c3d3ae64f609f0d294268ad5d2027965192 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Sun, 10 Aug 2025 13:05:13 -0700 Subject: [PATCH 770/796] Remove, from stable branches, the new assertion of no pg_dump OID sort. Commit 0decd5e89db9f5edb9b27351082f0d74aae7a9b6 recently added the assertion to confirm dump order remains independent of OID values. The assertion remained reachable via DO_DEFAULT_ACL. Given the release wrap tomorrow, make the assertion master-only. Reported-by: Alexander Lakhin Reviewed-by: Robert Haas Reviewed-by: Tom Lane Discussion: https://postgr.es/m/d32aaa8d-df7c-4f94-bcb3-4c85f02bea21@gmail.com Backpatch-through: 13-18 --- src/bin/pg_dump/pg_dump_sort.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 62e27b8d176ce..ffaceb93644cf 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -450,7 +450,6 @@ DOTypeNameCompare(const void *p1, const void *p2) * consequence of the test using "pg_restore -j", which doesn't fully * constrain OID assignment order. */ - Assert(false); return oidcmp(obj1->catId.oid, obj2->catId.oid); } From e819d6147d0397218a56ac2364c859362589ef49 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 10 Aug 2025 16:31:53 -0400 Subject: [PATCH 771/796] Release notes for 17.6, 16.10, 15.14, 14.19, 13.22. --- doc/src/sgml/release-17.sgml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index 6961fe95b7b6b..212a38b868ee6 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -1159,12 +1159,20 @@ Branch: REL_16_STABLE [9affed263] 2025-07-31 06:38:00 -0700 Branch: REL_15_STABLE [e99010cbd] 2025-07-31 06:38:01 -0700 Branch: REL_14_STABLE [25388fb2c] 2025-07-31 06:38:02 -0700 Branch: REL_13_STABLE [cc9a62c51] 2025-07-31 06:38:03 -0700 +Author: Noah Misch +Branch: REL_18_STABLE [0d2734eac] 2025-08-10 13:05:13 -0700 +Branch: REL_17_STABLE [28e7252e4] 2025-08-10 13:05:16 -0700 +Branch: REL_16_STABLE [216683296] 2025-08-10 13:05:16 -0700 +Branch: REL_15_STABLE [70637d7ae] 2025-08-10 13:05:17 -0700 +Branch: REL_14_STABLE [7846f4709] 2025-08-10 13:05:17 -0700 +Branch: REL_13_STABLE [bc05590c7] 2025-08-10 13:05:17 -0700 --> Ensure stable sort ordering in pg_dump for all types of database objects (Noah Misch, Andreas Karlsson) § § + § From 520fa3696496eda3318b8adac6985c5ecfb6fbc1 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Mon, 11 Aug 2025 09:09:12 +0100 Subject: [PATCH 772/796] Fix security checks in selectivity estimation functions. Commit e2d4ef8de86 (the fix for CVE-2017-7484) added security checks to the selectivity estimation functions to prevent them from running user-supplied operators on data obtained from pg_statistic if the user lacks privileges to select from the underlying table. In cases involving inheritance/partitioning, those checks were originally performed against the child RTE (which for plain inheritance might actually refer to the parent table). Commit 553d2ec2710 then extended that to also check the parent RTE, allowing access if the user had permissions on either the parent or the child. It turns out, however, that doing any checks using the child RTE is incorrect, since securityQuals is set to NULL when creating an RTE for an inheritance child (whether it refers to the parent table or the child table), and therefore such checks do not correctly account for any RLS policies or security barrier views. Therefore, do the security checks using only the parent RTE. This is consistent with how RLS policies are applied, and the executor's ACL checks, both of which use only the parent table's permissions/policies. Similar checks are performed in the extended stats code, so update that in the same way, centralizing all the checks in a new function. In addition, note that these checks by themselves are insufficient to ensure that the user has access to the table's data because, in a query that goes via a view, they only check that the view owner has permissions on the underlying table, not that the current user has permissions on the view itself. In the selectivity estimation functions, there is no easy way to navigate from underlying tables to views, so add permissions checks for all views mentioned in the query to the planner startup code. If the user lacks permissions on a view, a permissions error will now be reported at planner-startup, and the selectivity estimation functions will not be run. Checking view permissions at planner-startup in this way is a little ugly, since the same checks will be repeated at executor-startup. Longer-term, it might be better to move all the permissions checks from the executor to the planner so that permissions errors can be reported sooner, instead of creating a plan that won't ever be run. However, such a change seems too far-reaching to be back-patched. Back-patch to all supported versions. In v13, there is the added complication that UPDATEs and DELETEs on inherited target tables are planned using inheritance_planner(), which plans each inheritance child table separately, so that the selectivity estimation functions do not know that they are dealing with a child table accessed via its parent. Handle that by checking access permissions on the top parent table at planner-startup, in the same way as we do for views. Any securityQuals on the top parent table are moved down to the child tables by inheritance_planner(), so they continue to be checked by the selectivity estimation functions. Author: Dean Rasheed Reviewed-by: Tom Lane Reviewed-by: Noah Misch Backpatch-through: 13 Security: CVE-2025-8713 --- src/backend/executor/execMain.c | 3 +- src/backend/optimizer/plan/planner.c | 33 ++ src/backend/statistics/extended_stats.c | 108 ++--- src/backend/utils/adt/selfuncs.c | 486 ++++++++++++---------- src/include/executor/executor.h | 1 + src/include/utils/selfuncs.h | 4 +- src/test/regress/expected/privileges.out | 13 +- src/test/regress/expected/rowsecurity.out | 73 +++- src/test/regress/expected/stats_ext.out | 60 ++- src/test/regress/sql/privileges.sql | 12 +- src/test/regress/sql/rowsecurity.sql | 51 ++- src/test/regress/sql/stats_ext.sql | 39 ++ 12 files changed, 583 insertions(+), 300 deletions(-) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 8048b810e51e5..e35ddd0e898df 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -81,7 +81,6 @@ static void ExecutePlan(QueryDesc *queryDesc, uint64 numberTuples, ScanDirection direction, DestReceiver *dest); -static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo); static bool ExecCheckPermissionsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols, AclMode requiredPerms); @@ -633,7 +632,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos, * ExecCheckOneRelPerms * Check access permissions for a single relation. */ -static bool +bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo) { AclMode requiredPerms; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 72a26695f06b6..5e2af9808f694 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -58,6 +58,7 @@ #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "partitioning/partdesc.h" +#include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -813,6 +814,38 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, bms_make_singleton(parse->resultRelation); } + /* + * This would be a convenient time to check access permissions for all + * relations mentioned in the query, since it would be better to fail now, + * before doing any detailed planning. However, for historical reasons, + * we leave this to be done at executor startup. + * + * Note, however, that we do need to check access permissions for any view + * relations mentioned in the query, in order to prevent information being + * leaked by selectivity estimation functions, which only check view owner + * permissions on underlying tables (see all_rows_selectable() and its + * callers). This is a little ugly, because it means that access + * permissions for views will be checked twice, which is another reason + * why it would be better to do all the ACL checks here. + */ + foreach(l, parse->rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, l); + + if (rte->perminfoindex != 0 && + rte->relkind == RELKIND_VIEW) + { + RTEPermissionInfo *perminfo; + bool result; + + perminfo = getRTEPermissionInfo(parse->rteperminfos, rte); + result = ExecCheckOneRelPerms(perminfo); + if (!result) + aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_VIEW, + get_rel_name(perminfo->relid)); + } + } + /* * Preprocess RowMark information. We need to do this after subquery * pullup, so that all base relations are present. diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 99fdf208dba1c..b84ee2979ff50 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -1320,6 +1320,9 @@ choose_best_statistics(List *stats, char requiredkind, bool inh, * so we can't cope with system columns. * *exprs: input/output parameter collecting primitive subclauses within * the clause tree + * *leakproof: input/output parameter recording the leakproofness of the + * clause tree. This should be true initially, and will be set to false + * if any operator function used in an OpExpr is not leakproof. * * Returns false if there is something we definitively can't handle. * On true return, we can proceed to match the *exprs against statistics. @@ -1327,7 +1330,7 @@ choose_best_statistics(List *stats, char requiredkind, bool inh, static bool statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, Index relid, Bitmapset **attnums, - List **exprs) + List **exprs, bool *leakproof) { /* Look inside any binary-compatible relabeling (as in examine_variable) */ if (IsA(clause, RelabelType)) @@ -1362,7 +1365,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, /* (Var/Expr op Const) or (Const op Var/Expr) */ if (is_opclause(clause)) { - RangeTblEntry *rte = root->simple_rte_array[relid]; OpExpr *expr = (OpExpr *) clause; Node *clause_expr; @@ -1397,24 +1399,15 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, return false; } - /* - * If there are any securityQuals on the RTE from security barrier - * views or RLS policies, then the user may not have access to all the - * table's data, and we must check that the operator is leak-proof. - * - * If the operator is leaky, then we must ignore this clause for the - * purposes of estimating with MCV lists, otherwise the operator might - * reveal values from the MCV list that the user doesn't have - * permission to see. - */ - if (rte->securityQuals != NIL && - !get_func_leakproof(get_opcode(expr->opno))) - return false; + /* Check if the operator is leakproof */ + if (*leakproof) + *leakproof = get_func_leakproof(get_opcode(expr->opno)); /* Check (Var op Const) or (Const op Var) clauses by recursing. */ if (IsA(clause_expr, Var)) return statext_is_compatible_clause_internal(root, clause_expr, - relid, attnums, exprs); + relid, attnums, + exprs, leakproof); /* Otherwise we have (Expr op Const) or (Const op Expr). */ *exprs = lappend(*exprs, clause_expr); @@ -1424,7 +1417,6 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, /* Var/Expr IN Array */ if (IsA(clause, ScalarArrayOpExpr)) { - RangeTblEntry *rte = root->simple_rte_array[relid]; ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause; Node *clause_expr; bool expronleft; @@ -1464,24 +1456,15 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, return false; } - /* - * If there are any securityQuals on the RTE from security barrier - * views or RLS policies, then the user may not have access to all the - * table's data, and we must check that the operator is leak-proof. - * - * If the operator is leaky, then we must ignore this clause for the - * purposes of estimating with MCV lists, otherwise the operator might - * reveal values from the MCV list that the user doesn't have - * permission to see. - */ - if (rte->securityQuals != NIL && - !get_func_leakproof(get_opcode(expr->opno))) - return false; + /* Check if the operator is leakproof */ + if (*leakproof) + *leakproof = get_func_leakproof(get_opcode(expr->opno)); /* Check Var IN Array clauses by recursing. */ if (IsA(clause_expr, Var)) return statext_is_compatible_clause_internal(root, clause_expr, - relid, attnums, exprs); + relid, attnums, + exprs, leakproof); /* Otherwise we have Expr IN Array. */ *exprs = lappend(*exprs, clause_expr); @@ -1518,7 +1501,8 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, */ if (!statext_is_compatible_clause_internal(root, (Node *) lfirst(lc), - relid, attnums, exprs)) + relid, attnums, exprs, + leakproof)) return false; } @@ -1532,8 +1516,10 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, /* Check Var IS NULL clauses by recursing. */ if (IsA(nt->arg, Var)) - return statext_is_compatible_clause_internal(root, (Node *) (nt->arg), - relid, attnums, exprs); + return statext_is_compatible_clause_internal(root, + (Node *) (nt->arg), + relid, attnums, + exprs, leakproof); /* Otherwise we have Expr IS NULL. */ *exprs = lappend(*exprs, nt->arg); @@ -1572,11 +1558,9 @@ static bool statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid, Bitmapset **attnums, List **exprs) { - RangeTblEntry *rte = root->simple_rte_array[relid]; - RelOptInfo *rel = root->simple_rel_array[relid]; RestrictInfo *rinfo; int clause_relid; - Oid userid; + bool leakproof; /* * Special-case handling for bare BoolExpr AND clauses, because the @@ -1616,18 +1600,31 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid, clause_relid != relid) return false; - /* Check the clause and determine what attributes it references. */ + /* + * Check the clause, determine what attributes it references, and whether + * it includes any non-leakproof operators. + */ + leakproof = true; if (!statext_is_compatible_clause_internal(root, (Node *) rinfo->clause, - relid, attnums, exprs)) + relid, attnums, exprs, + &leakproof)) return false; /* - * Check that the user has permission to read all required attributes. + * If the clause includes any non-leakproof operators, check that the user + * has permission to read all required attributes, otherwise the operators + * might reveal values from the MCV list that the user doesn't have + * permission to see. We require all rows to be selectable --- there must + * be no securityQuals from security barrier views or RLS policies. See + * similar code in examine_variable(), examine_simple_variable(), and + * statistic_proc_security_check(). + * + * Note that for an inheritance child, the permission checks are performed + * on the inheritance root parent, and whole-table select privilege on the + * parent doesn't guarantee that the user could read all columns of the + * child. Therefore we must check all referenced columns. */ - userid = OidIsValid(rel->userid) ? rel->userid : GetUserId(); - - /* Table-level SELECT privilege is sufficient for all columns */ - if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) != ACLCHECK_OK) + if (!leakproof) { Bitmapset *clause_attnums = NULL; int attnum = -1; @@ -1652,26 +1649,9 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid, if (*exprs != NIL) pull_varattnos((Node *) *exprs, relid, &clause_attnums); - attnum = -1; - while ((attnum = bms_next_member(clause_attnums, attnum)) >= 0) - { - /* Undo the offset */ - AttrNumber attno = attnum + FirstLowInvalidHeapAttributeNumber; - - if (attno == InvalidAttrNumber) - { - /* Whole-row reference, so must have access to all columns */ - if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT, - ACLMASK_ALL) != ACLCHECK_OK) - return false; - } - else - { - if (pg_attribute_aclcheck(rte->relid, attno, userid, - ACL_SELECT) != ACLCHECK_OK) - return false; - } - } + /* Must have permission to read all rows from these columns */ + if (!all_rows_selectable(root, relid, clause_attnums)) + return false; } /* If we reach here, the clause is OK */ diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index f420517961f01..4670a3d648d28 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5015,8 +5015,8 @@ ReleaseDummy(HeapTuple tuple) * this query. (Caution: this should be trusted for statistical * purposes only, since we do not check indimmediate nor verify that * the exact same definition of equality applies.) - * acl_ok: true if current user has permission to read the column(s) - * underlying the pg_statistic entry. This is consulted by + * acl_ok: true if current user has permission to read all table rows from + * the column(s) underlying the pg_statistic entry. This is consulted by * statistic_proc_security_check(). * * Caller is responsible for doing ReleaseVariableStats() before exiting. @@ -5135,7 +5135,6 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, */ ListCell *ilist; ListCell *slist; - Oid userid; /* * The nullingrels bits within the expression could prevent us from @@ -5145,17 +5144,6 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, if (bms_overlap(varnos, root->outer_join_rels)) node = remove_nulling_relids(node, root->outer_join_rels, NULL); - /* - * Determine the user ID to use for privilege checks: either - * onerel->userid if it's set (e.g., in case we're accessing the table - * via a view), or the current user otherwise. - * - * If we drill down to child relations, we keep using the same userid: - * it's going to be the same anyway, due to how we set up the relation - * tree (q.v. build_simple_rel). - */ - userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId(); - foreach(ilist, onerel->indexlist) { IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist); @@ -5223,69 +5211,32 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, if (HeapTupleIsValid(vardata->statsTuple)) { - /* Get index's table for permission check */ - RangeTblEntry *rte; - - rte = planner_rt_fetch(index->rel->relid, root); - Assert(rte->rtekind == RTE_RELATION); - /* + * Test if user has permission to access all + * rows from the index's table. + * * For simplicity, we insist on the whole * table being selectable, rather than trying * to identify which column(s) the index - * depends on. Also require all rows to be - * selectable --- there must be no - * securityQuals from security barrier views - * or RLS policies. + * depends on. + * + * Note that for an inheritance child, + * permissions are checked on the inheritance + * root parent, and whole-table select + * privilege on the parent doesn't quite + * guarantee that the user could read all + * columns of the child. But in practice it's + * unlikely that any interesting security + * violation could result from allowing access + * to the expression index's stats, so we + * allow it anyway. See similar code in + * examine_simple_variable() for additional + * comments. */ vardata->acl_ok = - rte->securityQuals == NIL && - (pg_class_aclcheck(rte->relid, userid, - ACL_SELECT) == ACLCHECK_OK); - - /* - * If the user doesn't have permissions to - * access an inheritance child relation, check - * the permissions of the table actually - * mentioned in the query, since most likely - * the user does have that permission. Note - * that whole-table select privilege on the - * parent doesn't quite guarantee that the - * user could read all columns of the child. - * But in practice it's unlikely that any - * interesting security violation could result - * from allowing access to the expression - * index's stats, so we allow it anyway. See - * similar code in examine_simple_variable() - * for additional comments. - */ - if (!vardata->acl_ok && - root->append_rel_array != NULL) - { - AppendRelInfo *appinfo; - Index varno = index->rel->relid; - - appinfo = root->append_rel_array[varno]; - while (appinfo && - planner_rt_fetch(appinfo->parent_relid, - root)->rtekind == RTE_RELATION) - { - varno = appinfo->parent_relid; - appinfo = root->append_rel_array[varno]; - } - if (varno != index->rel->relid) - { - /* Repeat access check on this rel */ - rte = planner_rt_fetch(varno, root); - Assert(rte->rtekind == RTE_RELATION); - - vardata->acl_ok = - rte->securityQuals == NIL && - (pg_class_aclcheck(rte->relid, - userid, - ACL_SELECT) == ACLCHECK_OK); - } - } + all_rows_selectable(root, + index->rel->relid, + NULL); } else { @@ -5355,58 +5306,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, vardata->freefunc = ReleaseDummy; /* + * Test if user has permission to access all rows from the + * table. + * * For simplicity, we insist on the whole table being * selectable, rather than trying to identify which - * column(s) the statistics object depends on. Also - * require all rows to be selectable --- there must be no - * securityQuals from security barrier views or RLS - * policies. + * column(s) the statistics object depends on. + * + * Note that for an inheritance child, permissions are + * checked on the inheritance root parent, and whole-table + * select privilege on the parent doesn't quite guarantee + * that the user could read all columns of the child. But + * in practice it's unlikely that any interesting security + * violation could result from allowing access to the + * expression stats, so we allow it anyway. See similar + * code in examine_simple_variable() for additional + * comments. */ - vardata->acl_ok = - rte->securityQuals == NIL && - (pg_class_aclcheck(rte->relid, userid, - ACL_SELECT) == ACLCHECK_OK); - - /* - * If the user doesn't have permissions to access an - * inheritance child relation, check the permissions of - * the table actually mentioned in the query, since most - * likely the user does have that permission. Note that - * whole-table select privilege on the parent doesn't - * quite guarantee that the user could read all columns of - * the child. But in practice it's unlikely that any - * interesting security violation could result from - * allowing access to the expression stats, so we allow it - * anyway. See similar code in examine_simple_variable() - * for additional comments. - */ - if (!vardata->acl_ok && - root->append_rel_array != NULL) - { - AppendRelInfo *appinfo; - Index varno = onerel->relid; - - appinfo = root->append_rel_array[varno]; - while (appinfo && - planner_rt_fetch(appinfo->parent_relid, - root)->rtekind == RTE_RELATION) - { - varno = appinfo->parent_relid; - appinfo = root->append_rel_array[varno]; - } - if (varno != onerel->relid) - { - /* Repeat access check on this rel */ - rte = planner_rt_fetch(varno, root); - Assert(rte->rtekind == RTE_RELATION); - - vardata->acl_ok = - rte->securityQuals == NIL && - (pg_class_aclcheck(rte->relid, - userid, - ACL_SELECT) == ACLCHECK_OK); - } - } + vardata->acl_ok = all_rows_selectable(root, + onerel->relid, + NULL); break; } @@ -5461,109 +5380,20 @@ examine_simple_variable(PlannerInfo *root, Var *var, if (HeapTupleIsValid(vardata->statsTuple)) { - RelOptInfo *onerel = find_base_rel_noerr(root, var->varno); - Oid userid; - /* - * Check if user has permission to read this column. We require - * all rows to be accessible, so there must be no securityQuals - * from security barrier views or RLS policies. + * Test if user has permission to read all rows from this column. * - * Normally the Var will have an associated RelOptInfo from which - * we can find out which userid to do the check as; but it might - * not if it's a RETURNING Var for an INSERT target relation. In - * that case use the RTEPermissionInfo associated with the RTE. + * This requires that the user has the appropriate SELECT + * privileges and that there are no securityQuals from security + * barrier views or RLS policies. If that's not the case, then we + * only permit leakproof functions to be passed pg_statistic data + * in vardata, otherwise the functions might reveal data that the + * user doesn't have permission to see --- see + * statistic_proc_security_check(). */ - if (onerel) - userid = onerel->userid; - else - { - RTEPermissionInfo *perminfo; - - perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); - userid = perminfo->checkAsUser; - } - if (!OidIsValid(userid)) - userid = GetUserId(); - vardata->acl_ok = - rte->securityQuals == NIL && - ((pg_class_aclcheck(rte->relid, userid, - ACL_SELECT) == ACLCHECK_OK) || - (pg_attribute_aclcheck(rte->relid, var->varattno, userid, - ACL_SELECT) == ACLCHECK_OK)); - - /* - * If the user doesn't have permissions to access an inheritance - * child relation or specifically this attribute, check the - * permissions of the table/column actually mentioned in the - * query, since most likely the user does have that permission - * (else the query will fail at runtime), and if the user can read - * the column there then he can get the values of the child table - * too. To do that, we must find out which of the root parent's - * attributes the child relation's attribute corresponds to. - */ - if (!vardata->acl_ok && var->varattno > 0 && - root->append_rel_array != NULL) - { - AppendRelInfo *appinfo; - Index varno = var->varno; - int varattno = var->varattno; - bool found = false; - - appinfo = root->append_rel_array[varno]; - - /* - * Partitions are mapped to their immediate parent, not the - * root parent, so must be ready to walk up multiple - * AppendRelInfos. But stop if we hit a parent that is not - * RTE_RELATION --- that's a flattened UNION ALL subquery, not - * an inheritance parent. - */ - while (appinfo && - planner_rt_fetch(appinfo->parent_relid, - root)->rtekind == RTE_RELATION) - { - int parent_varattno; - - found = false; - if (varattno <= 0 || varattno > appinfo->num_child_cols) - break; /* safety check */ - parent_varattno = appinfo->parent_colnos[varattno - 1]; - if (parent_varattno == 0) - break; /* Var is local to child */ - - varno = appinfo->parent_relid; - varattno = parent_varattno; - found = true; - - /* If the parent is itself a child, continue up. */ - appinfo = root->append_rel_array[varno]; - } - - /* - * In rare cases, the Var may be local to the child table, in - * which case, we've got to live with having no access to this - * column's stats. - */ - if (!found) - return; - - /* Repeat the access check on this parent rel & column */ - rte = planner_rt_fetch(varno, root); - Assert(rte->rtekind == RTE_RELATION); - - /* - * Fine to use the same userid as it's the same in all - * relations of a given inheritance tree. - */ - vardata->acl_ok = - rte->securityQuals == NIL && - ((pg_class_aclcheck(rte->relid, userid, - ACL_SELECT) == ACLCHECK_OK) || - (pg_attribute_aclcheck(rte->relid, varattno, userid, - ACL_SELECT) == ACLCHECK_OK)); - } + all_rows_selectable(root, var->varno, + bms_make_singleton(var->varattno - FirstLowInvalidHeapAttributeNumber)); } else { @@ -5751,17 +5581,227 @@ examine_simple_variable(PlannerInfo *root, Var *var, } } +/* + * all_rows_selectable + * Test whether the user has permission to select all rows from a given + * relation. + * + * Inputs: + * root: the planner info + * varno: the index of the relation (assumed to be an RTE_RELATION) + * varattnos: the attributes for which permission is required, or NULL if + * whole-table access is required + * + * Returns true if the user has the required select permissions, and there are + * no securityQuals from security barrier views or RLS policies. + * + * Note that if the relation is an inheritance child relation, securityQuals + * and access permissions are checked against the inheritance root parent (the + * relation actually mentioned in the query) --- see the comments in + * expand_single_inheritance_child() for an explanation of why it has to be + * done this way. + * + * If varattnos is non-NULL, its attribute numbers should be offset by + * FirstLowInvalidHeapAttributeNumber so that system attributes can be + * checked. If varattnos is NULL, only table-level SELECT privileges are + * checked, not any column-level privileges. + * + * Note: if the relation is accessed via a view, this function actually tests + * whether the view owner has permission to select from the relation. To + * ensure that the current user has permission, it is also necessary to check + * that the current user has permission to select from the view, which we do + * at planner-startup --- see subquery_planner(). + * + * This is exported so that other estimation functions can use it. + */ +bool +all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos) +{ + RelOptInfo *rel = find_base_rel_noerr(root, varno); + RangeTblEntry *rte = planner_rt_fetch(varno, root); + Oid userid; + int varattno; + + Assert(rte->rtekind == RTE_RELATION); + + /* + * Determine the user ID to use for privilege checks (either the current + * user or the view owner, if we're accessing the table via a view). + * + * Normally the relation will have an associated RelOptInfo from which we + * can find the userid, but it might not if it's a RETURNING Var for an + * INSERT target relation. In that case use the RTEPermissionInfo + * associated with the RTE. + * + * If we navigate up to a parent relation, we keep using the same userid, + * since it's the same in all relations of a given inheritance tree. + */ + if (rel) + userid = rel->userid; + else + { + RTEPermissionInfo *perminfo; + + perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); + userid = perminfo->checkAsUser; + } + if (!OidIsValid(userid)) + userid = GetUserId(); + + /* + * Permissions and securityQuals must be checked on the table actually + * mentioned in the query, so if this is an inheritance child, navigate up + * to the inheritance root parent. If the user can read the whole table + * or the required columns there, then they can read from the child table + * too. For per-column checks, we must find out which of the root + * parent's attributes the child relation's attributes correspond to. + */ + if (root->append_rel_array != NULL) + { + AppendRelInfo *appinfo; + + appinfo = root->append_rel_array[varno]; + + /* + * Partitions are mapped to their immediate parent, not the root + * parent, so must be ready to walk up multiple AppendRelInfos. But + * stop if we hit a parent that is not RTE_RELATION --- that's a + * flattened UNION ALL subquery, not an inheritance parent. + */ + while (appinfo && + planner_rt_fetch(appinfo->parent_relid, + root)->rtekind == RTE_RELATION) + { + Bitmapset *parent_varattnos = NULL; + + /* + * For each child attribute, find the corresponding parent + * attribute. In rare cases, the attribute may be local to the + * child table, in which case, we've got to live with having no + * access to this column. + */ + varattno = -1; + while ((varattno = bms_next_member(varattnos, varattno)) >= 0) + { + AttrNumber attno; + AttrNumber parent_attno; + + attno = varattno + FirstLowInvalidHeapAttributeNumber; + + if (attno == InvalidAttrNumber) + { + /* + * Whole-row reference, so must map each column of the + * child to the parent table. + */ + for (attno = 1; attno <= appinfo->num_child_cols; attno++) + { + parent_attno = appinfo->parent_colnos[attno - 1]; + if (parent_attno == 0) + return false; /* attr is local to child */ + parent_varattnos = + bms_add_member(parent_varattnos, + parent_attno - FirstLowInvalidHeapAttributeNumber); + } + } + else + { + if (attno < 0) + { + /* System attnos are the same in all tables */ + parent_attno = attno; + } + else + { + if (attno > appinfo->num_child_cols) + return false; /* safety check */ + parent_attno = appinfo->parent_colnos[attno - 1]; + if (parent_attno == 0) + return false; /* attr is local to child */ + } + parent_varattnos = + bms_add_member(parent_varattnos, + parent_attno - FirstLowInvalidHeapAttributeNumber); + } + } + + /* If the parent is itself a child, continue up */ + varno = appinfo->parent_relid; + varattnos = parent_varattnos; + appinfo = root->append_rel_array[varno]; + } + + /* Perform the access check on this parent rel */ + rte = planner_rt_fetch(varno, root); + Assert(rte->rtekind == RTE_RELATION); + } + + /* + * For all rows to be accessible, there must be no securityQuals from + * security barrier views or RLS policies. + */ + if (rte->securityQuals != NIL) + return false; + + /* + * Test for table-level SELECT privilege. + * + * If varattnos is non-NULL, this is sufficient to give access to all + * requested attributes, even for a child table, since we have verified + * that all required child columns have matching parent columns. + * + * If varattnos is NULL (whole-table access requested), this doesn't + * necessarily guarantee that the user can read all columns of a child + * table, but we allow it anyway (see comments in examine_variable()) and + * don't bother checking any column privileges. + */ + if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) == ACLCHECK_OK) + return true; + + if (varattnos == NULL) + return false; /* whole-table access requested */ + + /* + * Don't have table-level SELECT privilege, so check per-column + * privileges. + */ + varattno = -1; + while ((varattno = bms_next_member(varattnos, varattno)) >= 0) + { + AttrNumber attno = varattno + FirstLowInvalidHeapAttributeNumber; + + if (attno == InvalidAttrNumber) + { + /* Whole-row reference, so must have access to all columns */ + if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT, + ACLMASK_ALL) != ACLCHECK_OK) + return false; + } + else + { + if (pg_attribute_aclcheck(rte->relid, attno, userid, + ACL_SELECT) != ACLCHECK_OK) + return false; + } + } + + /* If we reach here, have all required column privileges */ + return true; +} + /* * Check whether it is permitted to call func_oid passing some of the - * pg_statistic data in vardata. We allow this either if the user has SELECT - * privileges on the table or column underlying the pg_statistic data or if - * the function is marked leak-proof. + * pg_statistic data in vardata. We allow this if either of the following + * conditions is met: (1) the user has SELECT privileges on the table or + * column underlying the pg_statistic data and there are no securityQuals from + * security barrier views or RLS policies, or (2) the function is marked + * leakproof. */ bool statistic_proc_security_check(VariableStatData *vardata, Oid func_oid) { if (vardata->acl_ok) - return true; + return true; /* have SELECT privs and no securityQuals */ if (!OidIsValid(func_oid)) return false; diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 9770752ea3c1b..7e6e366ceacd6 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -210,6 +210,7 @@ extern void standard_ExecutorEnd(QueryDesc *queryDesc); extern void ExecutorRewind(QueryDesc *queryDesc); extern bool ExecCheckPermissions(List *rangeTable, List *rteperminfos, bool ereport_on_violation); +extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo); extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation, List *mergeActions); extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index f2563ad1cb3a8..0de77aeaeabc1 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -95,7 +95,8 @@ typedef struct VariableStatData Oid atttype; /* actual type (after stripping relabel) */ int32 atttypmod; /* actual typmod (after stripping relabel) */ bool isunique; /* matches unique index or DISTINCT clause */ - bool acl_ok; /* result of ACL check on table or column */ + bool acl_ok; /* true if user has SELECT privilege on all + * rows from the table or column */ } VariableStatData; #define ReleaseVariableStats(vardata) \ @@ -152,6 +153,7 @@ extern PGDLLIMPORT get_index_stats_hook_type get_index_stats_hook; extern void examine_variable(PlannerInfo *root, Node *node, int varRelid, VariableStatData *vardata); +extern bool all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos); extern bool statistic_proc_security_check(VariableStatData *vardata, Oid func_oid); extern bool get_restriction_variable(PlannerInfo *root, List *args, int varRelid, diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 43845b6f7a8aa..cf5f9fd6aa14f 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -454,8 +454,6 @@ CREATE VIEW atest12v AS SELECT * FROM atest12 WHERE b <<< 5; CREATE VIEW atest12sbv WITH (security_barrier=true) AS SELECT * FROM atest12 WHERE b <<< 5; -GRANT SELECT ON atest12v TO PUBLIC; -GRANT SELECT ON atest12sbv TO PUBLIC; -- This plan should use nestloop, knowing that few rows will be selected. EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; QUERY PLAN @@ -501,9 +499,18 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean LANGUAGE plpgsql immutable; CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer, restrict = scalargtsel); --- This should not show any "leak" notices before failing. +-- These should not show any "leak" notices before failing. EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0; ERROR: permission denied for table atest12 +EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0; +ERROR: permission denied for view atest12v +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0; +ERROR: permission denied for view atest12sbv +-- Now regress_priv_user1 grants access to regress_priv_user2 via the views. +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT ON atest12v TO PUBLIC; +GRANT SELECT ON atest12sbv TO PUBLIC; +SET SESSION AUTHORIZATION regress_priv_user2; -- These plans should continue to use a nestloop, since they execute with the -- privileges of the view owner. EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 51bba175ec027..2e23dcee6dac2 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -4475,7 +4475,7 @@ RESET SESSION AUTHORIZATION; DROP VIEW rls_view; DROP TABLE rls_tbl; DROP TABLE ref_tbl; --- Leaky operator test +-- Leaky operator tests CREATE TABLE rls_tbl (a int); INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; ANALYZE rls_tbl; @@ -4492,9 +4492,80 @@ SELECT * FROM rls_tbl WHERE a <<< 1000; --- (0 rows) +RESET SESSION AUTHORIZATION; +CREATE TABLE rls_child_tbl () INHERITS (rls_tbl); +INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_child_tbl; +CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a); +CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100); +INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_ptbl, rls_part; +ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_ptbl TO regress_rls_alice; +GRANT SELECT ON rls_part TO regress_rls_alice; +CREATE POLICY p1 ON rls_tbl USING (a < 0); +CREATE POLICY p2 ON rls_ptbl USING (a < 0); +CREATE POLICY p3 ON rls_part USING (a < 0); +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_tbl WHERE a <<< 1000; + a +--- +(0 rows) + +SELECT * FROM rls_child_tbl WHERE a <<< 1000; +ERROR: permission denied for table rls_child_tbl +SELECT * FROM rls_ptbl WHERE a <<< 1000; + a +--- +(0 rows) + +SELECT * FROM rls_part WHERE a <<< 1000; + a +--- +(0 rows) + +SELECT * FROM (SELECT * FROM rls_tbl UNION ALL + SELECT * FROM rls_tbl) t WHERE a <<< 1000; + a +--- +(0 rows) + +SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL + SELECT * FROM rls_child_tbl) t WHERE a <<< 1000; +ERROR: permission denied for table rls_child_tbl +RESET SESSION AUTHORIZATION; +REVOKE SELECT ON rls_tbl FROM regress_rls_alice; +CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl; +ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_child_tbl TO regress_rls_alice; +CREATE POLICY p4 ON rls_child_tbl USING (a < 0); +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_tbl WHERE a <<< 1000; +ERROR: permission denied for table rls_tbl +SELECT * FROM rls_tbl_view WHERE a <<< 1000; +ERROR: permission denied for view rls_tbl_view +SELECT * FROM rls_child_tbl WHERE a <<< 1000; + a +--- +(0 rows) + +SELECT * FROM (SELECT * FROM rls_tbl UNION ALL + SELECT * FROM rls_tbl) t WHERE a <<< 1000; +ERROR: permission denied for table rls_tbl +SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL + SELECT * FROM rls_child_tbl) t WHERE a <<< 1000; + a +--- +(0 rows) + DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); RESET SESSION AUTHORIZATION; +DROP TABLE rls_part; +DROP TABLE rls_ptbl; +DROP TABLE rls_child_tbl; +DROP VIEW rls_tbl_view; DROP TABLE rls_tbl; -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects SET SESSION AUTHORIZATION regress_rls_alice; diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 8c4da95508408..43539dfe27fae 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -3252,8 +3252,16 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool LANGUAGE plpgsql; CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, restrict = scalarltsel); +CREATE FUNCTION op_leak(record, record) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record, + restrict = scalarltsel); SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied ERROR: permission denied for table priv_test_tbl +SELECT * FROM tststats.priv_test_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied +ERROR: permission denied for table priv_test_tbl DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied ERROR: permission denied for table priv_test_tbl -- Grant access via a security barrier view, but hide all data @@ -3268,10 +3276,17 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not l ---+--- (0 rows) +SELECT * FROM tststats.priv_test_view t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak + a | b +---+--- +(0 rows) + DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak -- Grant table access, but hide all data with RLS RESET SESSION AUTHORIZATION; ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0); GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1; -- Should now have direct table access, but see nothing and leak nothing SET SESSION AUTHORIZATION regress_stats_user1; @@ -3280,7 +3295,45 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le ---+--- (0 rows) +SELECT * FROM tststats.priv_test_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak + a | b +---+--- +(0 rows) + DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +-- Create plain inheritance parent table with no access permissions +RESET SESSION AUTHORIZATION; +CREATE TABLE tststats.priv_test_parent_tbl (a int, b int); +ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl; +-- Should not have access to parent, and should leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +ERROR: permission denied for table priv_test_parent_tbl +SELECT * FROM tststats.priv_test_parent_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied +ERROR: permission denied for table priv_test_parent_tbl +DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +ERROR: permission denied for table priv_test_parent_tbl +-- Grant table access to parent, but hide all data with RLS +RESET SESSION AUTHORIZATION; +ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0); +GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1; +-- Should now have direct table access to parent, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak + a | b +---+--- +(0 rows) + +SELECT * FROM tststats.priv_test_parent_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak + a | b +---+--- +(0 rows) + +DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak -- privilege checks for pg_stats_ext and pg_stats_ext_exprs RESET SESSION AUTHORIZATION; CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT); @@ -3326,10 +3379,13 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x -- Tidy up DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); +DROP OPERATOR <<< (record, record); +DROP FUNCTION op_leak(record, record); RESET SESSION AUTHORIZATION; DROP TABLE stats_ext_tbl; DROP SCHEMA tststats CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table tststats.priv_test_tbl +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table tststats.priv_test_parent_tbl +drop cascades to table tststats.priv_test_tbl drop cascades to view tststats.priv_test_view DROP USER regress_stats_user1; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 54b82b610a4fd..e93b179b42957 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -325,8 +325,6 @@ CREATE VIEW atest12v AS SELECT * FROM atest12 WHERE b <<< 5; CREATE VIEW atest12sbv WITH (security_barrier=true) AS SELECT * FROM atest12 WHERE b <<< 5; -GRANT SELECT ON atest12v TO PUBLIC; -GRANT SELECT ON atest12sbv TO PUBLIC; -- This plan should use nestloop, knowing that few rows will be selected. EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; @@ -348,8 +346,16 @@ CREATE FUNCTION leak2(integer,integer) RETURNS boolean CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer, restrict = scalargtsel); --- This should not show any "leak" notices before failing. +-- These should not show any "leak" notices before failing. EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0; +EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0; +EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0; + +-- Now regress_priv_user1 grants access to regress_priv_user2 via the views. +SET SESSION AUTHORIZATION regress_priv_user1; +GRANT SELECT ON atest12v TO PUBLIC; +GRANT SELECT ON atest12sbv TO PUBLIC; +SET SESSION AUTHORIZATION regress_priv_user2; -- These plans should continue to use a nestloop, since they execute with the -- privileges of the view owner. diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index eab7d99003eec..6e71dc7236b98 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -2162,7 +2162,7 @@ DROP VIEW rls_view; DROP TABLE rls_tbl; DROP TABLE ref_tbl; --- Leaky operator test +-- Leaky operator tests CREATE TABLE rls_tbl (a int); INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; ANALYZE rls_tbl; @@ -2177,9 +2177,58 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, restrict = scalarltsel); SELECT * FROM rls_tbl WHERE a <<< 1000; +RESET SESSION AUTHORIZATION; + +CREATE TABLE rls_child_tbl () INHERITS (rls_tbl); +INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_child_tbl; + +CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a); +CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100); +INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_ptbl, rls_part; + +ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_ptbl TO regress_rls_alice; +GRANT SELECT ON rls_part TO regress_rls_alice; +CREATE POLICY p1 ON rls_tbl USING (a < 0); +CREATE POLICY p2 ON rls_ptbl USING (a < 0); +CREATE POLICY p3 ON rls_part USING (a < 0); + +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_tbl WHERE a <<< 1000; +SELECT * FROM rls_child_tbl WHERE a <<< 1000; +SELECT * FROM rls_ptbl WHERE a <<< 1000; +SELECT * FROM rls_part WHERE a <<< 1000; +SELECT * FROM (SELECT * FROM rls_tbl UNION ALL + SELECT * FROM rls_tbl) t WHERE a <<< 1000; +SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL + SELECT * FROM rls_child_tbl) t WHERE a <<< 1000; +RESET SESSION AUTHORIZATION; + +REVOKE SELECT ON rls_tbl FROM regress_rls_alice; +CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl; + +ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_child_tbl TO regress_rls_alice; +CREATE POLICY p4 ON rls_child_tbl USING (a < 0); + +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_tbl WHERE a <<< 1000; +SELECT * FROM rls_tbl_view WHERE a <<< 1000; +SELECT * FROM rls_child_tbl WHERE a <<< 1000; +SELECT * FROM (SELECT * FROM rls_tbl UNION ALL + SELECT * FROM rls_tbl) t WHERE a <<< 1000; +SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL + SELECT * FROM rls_child_tbl) t WHERE a <<< 1000; DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); RESET SESSION AUTHORIZATION; +DROP TABLE rls_part; +DROP TABLE rls_ptbl; +DROP TABLE rls_child_tbl; +DROP VIEW rls_tbl_view; DROP TABLE rls_tbl; -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 0c08a6cc42e89..8f54c363b1ff6 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -1633,7 +1633,14 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool LANGUAGE plpgsql; CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, restrict = scalarltsel); +CREATE FUNCTION op_leak(record, record) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record, + restrict = scalarltsel); SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +SELECT * FROM tststats.priv_test_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied -- Grant access via a security barrier view, but hide all data @@ -1645,18 +1652,48 @@ GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1; -- Should now have access via the view, but see nothing and leak nothing SET SESSION AUTHORIZATION regress_stats_user1; SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak +SELECT * FROM tststats.priv_test_view t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak -- Grant table access, but hide all data with RLS RESET SESSION AUTHORIZATION; ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0); GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1; -- Should now have direct table access, but see nothing and leak nothing SET SESSION AUTHORIZATION regress_stats_user1; SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +SELECT * FROM tststats.priv_test_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +-- Create plain inheritance parent table with no access permissions +RESET SESSION AUTHORIZATION; +CREATE TABLE tststats.priv_test_parent_tbl (a int, b int); +ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl; + +-- Should not have access to parent, and should leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied +SELECT * FROM tststats.priv_test_parent_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied +DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied + +-- Grant table access to parent, but hide all data with RLS +RESET SESSION AUTHORIZATION; +ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0); +GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1; + +-- Should now have direct table access to parent, but see nothing and leak nothing +SET SESSION AUTHORIZATION regress_stats_user1; +SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak +SELECT * FROM tststats.priv_test_parent_tbl t + WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak +DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak + -- privilege checks for pg_stats_ext and pg_stats_ext_exprs RESET SESSION AUTHORIZATION; CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT); @@ -1686,6 +1723,8 @@ SELECT statistics_name, most_common_vals FROM pg_stats_ext_exprs x -- Tidy up DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int); +DROP OPERATOR <<< (record, record); +DROP FUNCTION op_leak(record, record); RESET SESSION AUTHORIZATION; DROP TABLE stats_ext_tbl; DROP SCHEMA tststats CASCADE; From b0c0089df6c5b9d666180c5cc82d978c380ffa24 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 11 Aug 2025 14:40:51 +0200 Subject: [PATCH 773/796] Translation updates Source-Git-URL: https://git.postgresql.org/git/pgtranslation/messages.git Source-Git-Hash: 4f9af069289c30fc32337b844fb1db25d7b11e9b --- src/backend/po/de.po | 2214 +++++++++---------- src/backend/po/ja.po | 1373 ++++++------ src/backend/po/ru.po | 2148 +++++++++---------- src/backend/po/sv.po | 2928 +++++++++++++------------- src/bin/pg_basebackup/po/ru.po | 138 +- src/bin/pg_combinebackup/po/ru.po | 44 +- src/bin/pg_dump/po/ru.po | 522 ++--- src/bin/pg_dump/po/sv.po | 420 ++-- src/bin/pg_rewind/po/ru.po | 60 +- src/bin/pg_upgrade/po/de.po | 254 ++- src/bin/pg_upgrade/po/fr.po | 253 ++- src/bin/pg_upgrade/po/ja.po | 238 ++- src/bin/pg_upgrade/po/ru.po | 215 +- src/bin/pg_upgrade/po/sv.po | 255 ++- src/bin/pg_verifybackup/po/ru.po | 44 +- src/bin/pg_waldump/po/ru.po | 60 +- src/bin/psql/po/ru.po | 4 +- src/interfaces/ecpg/ecpglib/po/ru.po | 6 +- src/interfaces/libpq/po/ru.po | 65 +- src/pl/plpgsql/src/po/ru.po | 28 +- src/pl/plpython/po/ru.po | 4 +- 21 files changed, 5733 insertions(+), 5540 deletions(-) diff --git a/src/backend/po/de.po b/src/backend/po/de.po index 63f99a49f56d2..06d6a4cd14e66 100644 --- a/src/backend/po/de.po +++ b/src/backend/po/de.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-01 10:59+0000\n" -"PO-Revision-Date: 2025-05-01 21:09+0200\n" +"POT-Creation-Date: 2025-08-08 02:00+0000\n" +"PO-Revision-Date: 2025-08-08 08:29+0200\n" "Last-Translator: Peter Eisentraut \n" "Language-Team: German \n" "Language: de\n" @@ -88,15 +88,15 @@ msgstr "konnte Datei »%s« nicht zum Lesen öffnen: %m" #: ../common/controldata_utils.c:108 ../common/controldata_utils.c:110 #: access/transam/timeline.c:143 access/transam/timeline.c:362 -#: access/transam/twophase.c:1353 access/transam/xlog.c:3477 -#: access/transam/xlog.c:4341 access/transam/xlogrecovery.c:1238 +#: access/transam/twophase.c:1353 access/transam/xlog.c:3459 +#: access/transam/xlog.c:4323 access/transam/xlogrecovery.c:1238 #: access/transam/xlogrecovery.c:1336 access/transam/xlogrecovery.c:1373 #: access/transam/xlogrecovery.c:1440 backup/basebackup.c:2123 #: backup/walsummary.c:283 commands/extension.c:3548 libpq/hba.c:764 #: replication/logical/origin.c:745 replication/logical/origin.c:781 -#: replication/logical/reorderbuffer.c:5113 -#: replication/logical/snapbuild.c:2091 replication/slot.c:2236 -#: replication/slot.c:2277 replication/walsender.c:659 +#: replication/logical/reorderbuffer.c:5243 +#: replication/logical/snapbuild.c:2099 replication/slot.c:2232 +#: replication/slot.c:2273 replication/walsender.c:659 #: storage/file/buffile.c:470 storage/file/copydir.c:185 #: utils/adt/genfile.c:197 utils/adt/misc.c:1028 utils/cache/relmapper.c:829 #, c-format @@ -104,10 +104,10 @@ msgid "could not read file \"%s\": %m" msgstr "konnte Datei »%s« nicht lesen: %m" #: ../common/controldata_utils.c:116 ../common/controldata_utils.c:119 -#: access/transam/xlog.c:3482 access/transam/xlog.c:4346 +#: access/transam/xlog.c:3464 access/transam/xlog.c:4328 #: replication/logical/origin.c:750 replication/logical/origin.c:789 -#: replication/logical/snapbuild.c:2096 replication/slot.c:2240 -#: replication/slot.c:2281 replication/walsender.c:664 +#: replication/logical/snapbuild.c:2104 replication/slot.c:2236 +#: replication/slot.c:2277 replication/walsender.c:664 #: utils/cache/relmapper.c:833 #, c-format msgid "could not read file \"%s\": read %d of %zu" @@ -118,17 +118,17 @@ msgstr "konnte Datei »%s« nicht lesen: %d von %zu gelesen" #: access/heap/rewriteheap.c:1141 access/heap/rewriteheap.c:1246 #: access/transam/timeline.c:392 access/transam/timeline.c:438 #: access/transam/timeline.c:512 access/transam/twophase.c:1365 -#: access/transam/twophase.c:1784 access/transam/xlog.c:3323 -#: access/transam/xlog.c:3517 access/transam/xlog.c:3522 -#: access/transam/xlog.c:3658 access/transam/xlog.c:4311 -#: access/transam/xlog.c:5246 commands/copyfrom.c:1799 commands/copyto.c:325 +#: access/transam/twophase.c:1784 access/transam/xlog.c:3305 +#: access/transam/xlog.c:3499 access/transam/xlog.c:3504 +#: access/transam/xlog.c:3640 access/transam/xlog.c:4293 +#: access/transam/xlog.c:5228 commands/copyfrom.c:1799 commands/copyto.c:325 #: libpq/be-fsstubs.c:470 libpq/be-fsstubs.c:540 #: replication/logical/origin.c:683 replication/logical/origin.c:822 -#: replication/logical/reorderbuffer.c:5165 -#: replication/logical/snapbuild.c:1858 replication/logical/snapbuild.c:1982 -#: replication/slot.c:2126 replication/slot.c:2288 replication/walsender.c:674 -#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:828 -#: storage/file/fd.c:3753 storage/file/fd.c:3859 utils/cache/relmapper.c:841 +#: replication/logical/reorderbuffer.c:5295 +#: replication/logical/snapbuild.c:1866 replication/logical/snapbuild.c:1990 +#: replication/slot.c:2122 replication/slot.c:2284 replication/walsender.c:674 +#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:825 +#: storage/file/fd.c:3750 storage/file/fd.c:3856 utils/cache/relmapper.c:841 #: utils/cache/relmapper.c:956 #, c-format msgid "could not close file \"%s\": %m" @@ -157,31 +157,31 @@ msgstr "" #: ../common/file_utils.c:406 ../common/file_utils.c:480 #: access/heap/rewriteheap.c:1229 access/transam/timeline.c:111 #: access/transam/timeline.c:251 access/transam/timeline.c:348 -#: access/transam/twophase.c:1309 access/transam/xlog.c:3230 -#: access/transam/xlog.c:3393 access/transam/xlog.c:3432 -#: access/transam/xlog.c:3625 access/transam/xlog.c:4331 +#: access/transam/twophase.c:1309 access/transam/xlog.c:3212 +#: access/transam/xlog.c:3375 access/transam/xlog.c:3414 +#: access/transam/xlog.c:3607 access/transam/xlog.c:4313 #: access/transam/xlogrecovery.c:4264 access/transam/xlogrecovery.c:4367 #: access/transam/xlogutils.c:836 backup/basebackup.c:547 #: backup/basebackup.c:1598 backup/walsummary.c:220 libpq/hba.c:624 #: postmaster/syslogger.c:1511 replication/logical/origin.c:735 -#: replication/logical/reorderbuffer.c:3766 -#: replication/logical/reorderbuffer.c:4320 -#: replication/logical/reorderbuffer.c:5093 -#: replication/logical/snapbuild.c:1813 replication/logical/snapbuild.c:1923 -#: replication/slot.c:2208 replication/walsender.c:632 -#: replication/walsender.c:3081 storage/file/copydir.c:151 -#: storage/file/fd.c:803 storage/file/fd.c:3510 storage/file/fd.c:3740 -#: storage/file/fd.c:3830 storage/smgr/md.c:661 utils/cache/relmapper.c:818 +#: replication/logical/reorderbuffer.c:3896 +#: replication/logical/reorderbuffer.c:4450 +#: replication/logical/reorderbuffer.c:5223 +#: replication/logical/snapbuild.c:1821 replication/logical/snapbuild.c:1931 +#: replication/slot.c:2204 replication/walsender.c:632 +#: replication/walsender.c:3085 storage/file/copydir.c:151 +#: storage/file/fd.c:800 storage/file/fd.c:3507 storage/file/fd.c:3737 +#: storage/file/fd.c:3827 storage/smgr/md.c:661 utils/cache/relmapper.c:818 #: utils/cache/relmapper.c:935 utils/error/elog.c:2124 #: utils/init/miscinit.c:1580 utils/init/miscinit.c:1714 -#: utils/init/miscinit.c:1791 utils/misc/guc.c:4777 utils/misc/guc.c:4827 +#: utils/init/miscinit.c:1791 utils/misc/guc.c:4782 utils/misc/guc.c:4832 #, c-format msgid "could not open file \"%s\": %m" msgstr "konnte Datei »%s« nicht öffnen: %m" #: ../common/controldata_utils.c:246 ../common/controldata_utils.c:249 #: access/transam/twophase.c:1757 access/transam/twophase.c:1766 -#: access/transam/xlog.c:9280 access/transam/xlogfuncs.c:698 +#: access/transam/xlog.c:9306 access/transam/xlogfuncs.c:698 #: backup/basebackup_server.c:173 backup/basebackup_server.c:266 #: backup/walsummary.c:304 postmaster/postmaster.c:4127 #: postmaster/syslogger.c:1522 postmaster/syslogger.c:1535 @@ -195,12 +195,12 @@ msgstr "konnte Datei »%s« nicht schreiben: %m" #: access/heap/rewriteheap.c:925 access/heap/rewriteheap.c:1135 #: access/heap/rewriteheap.c:1240 access/transam/timeline.c:432 #: access/transam/timeline.c:506 access/transam/twophase.c:1778 -#: access/transam/xlog.c:3316 access/transam/xlog.c:3511 -#: access/transam/xlog.c:4304 access/transam/xlog.c:8655 -#: access/transam/xlog.c:8700 backup/basebackup_server.c:207 -#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1851 -#: replication/slot.c:2112 replication/slot.c:2218 storage/file/fd.c:820 -#: storage/file/fd.c:3851 storage/smgr/md.c:1331 storage/smgr/md.c:1376 +#: access/transam/xlog.c:3298 access/transam/xlog.c:3493 +#: access/transam/xlog.c:4286 access/transam/xlog.c:8681 +#: access/transam/xlog.c:8726 backup/basebackup_server.c:207 +#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1859 +#: replication/slot.c:2108 replication/slot.c:2214 storage/file/fd.c:817 +#: storage/file/fd.c:3848 storage/smgr/md.c:1331 storage/smgr/md.c:1376 #: storage/sync/sync.c:446 utils/misc/guc.c:4530 #, c-format msgid "could not fsync file \"%s\": %m" @@ -214,15 +214,16 @@ msgstr "konnte Datei »%s« nicht fsyncen: %m" #: ../common/parse_manifest.c:852 ../common/psprintf.c:143 #: ../common/scram-common.c:268 ../common/stringinfo.c:314 ../port/path.c:828 #: ../port/path.c:865 ../port/path.c:882 access/transam/twophase.c:1418 -#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1352 -#: libpq/auth.c:1396 libpq/auth.c:1953 libpq/be-secure-gssapi.c:524 -#: postmaster/bgworker.c:355 postmaster/bgworker.c:945 -#: postmaster/postmaster.c:3560 postmaster/postmaster.c:4021 -#: postmaster/postmaster.c:4383 postmaster/walsummarizer.c:935 +#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1360 +#: libpq/auth.c:1404 libpq/auth.c:1961 libpq/be-secure-gssapi.c:534 +#: libpq/be-secure-gssapi.c:714 postmaster/bgworker.c:355 +#: postmaster/bgworker.c:945 postmaster/postmaster.c:3560 +#: postmaster/postmaster.c:4021 postmaster/postmaster.c:4383 +#: postmaster/walsummarizer.c:935 #: replication/libpqwalreceiver/libpqwalreceiver.c:387 -#: replication/logical/logical.c:210 replication/walsender.c:839 -#: storage/buffer/localbuf.c:606 storage/file/fd.c:912 storage/file/fd.c:1443 -#: storage/file/fd.c:1604 storage/file/fd.c:2531 storage/ipc/procarray.c:1465 +#: replication/logical/logical.c:212 replication/walsender.c:839 +#: storage/buffer/localbuf.c:606 storage/file/fd.c:909 storage/file/fd.c:1440 +#: storage/file/fd.c:1601 storage/file/fd.c:2528 storage/ipc/procarray.c:1465 #: storage/ipc/procarray.c:2219 storage/ipc/procarray.c:2226 #: storage/ipc/procarray.c:2731 storage/ipc/procarray.c:3435 #: utils/adt/formatting.c:1725 utils/adt/formatting.c:1873 @@ -312,7 +313,7 @@ msgstr "Speicher aufgebraucht\n" msgid "cannot duplicate null pointer (internal error)\n" msgstr "kann NULL-Zeiger nicht kopieren (interner Fehler)\n" -#: ../common/file_utils.c:76 storage/file/fd.c:3516 +#: ../common/file_utils.c:76 storage/file/fd.c:3513 #, c-format msgid "could not synchronize file system for file \"%s\": %m" msgstr "konnte Dateisystem für Datei »%s« nicht synchronisieren: %m" @@ -324,8 +325,8 @@ msgstr "konnte Dateisystem für Datei »%s« nicht synchronisieren: %m" #: backup/walsummary.c:247 backup/walsummary.c:254 commands/copyfrom.c:1749 #: commands/copyto.c:700 commands/extension.c:3527 commands/tablespace.c:804 #: commands/tablespace.c:893 postmaster/pgarch.c:680 -#: replication/logical/snapbuild.c:1709 replication/logical/snapbuild.c:2212 -#: storage/file/fd.c:1968 storage/file/fd.c:2054 storage/file/fd.c:3564 +#: replication/logical/snapbuild.c:1717 replication/logical/snapbuild.c:2220 +#: storage/file/fd.c:1965 storage/file/fd.c:2051 storage/file/fd.c:3561 #: utils/adt/dbsize.c:105 utils/adt/dbsize.c:257 utils/adt/dbsize.c:337 #: utils/adt/genfile.c:437 utils/adt/genfile.c:612 utils/adt/misc.c:340 #, c-format @@ -340,22 +341,22 @@ msgstr "diese Installation unterstützt Sync-Methode »%s« nicht" #: ../common/file_utils.c:151 ../common/file_utils.c:281 #: ../common/pgfnames.c:48 ../common/rmtree.c:63 commands/tablespace.c:728 #: commands/tablespace.c:738 postmaster/postmaster.c:1470 -#: storage/file/fd.c:2933 storage/file/reinit.c:126 utils/adt/misc.c:256 +#: storage/file/fd.c:2930 storage/file/reinit.c:126 utils/adt/misc.c:256 #: utils/misc/tzparser.c:339 #, c-format msgid "could not open directory \"%s\": %m" msgstr "konnte Verzeichnis »%s« nicht öffnen: %m" #: ../common/file_utils.c:169 ../common/file_utils.c:315 -#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2945 +#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2942 #, c-format msgid "could not read directory \"%s\": %m" msgstr "konnte Verzeichnis »%s« nicht lesen: %m" #: ../common/file_utils.c:498 access/transam/xlogarchive.c:389 #: postmaster/pgarch.c:834 postmaster/syslogger.c:1559 -#: replication/logical/snapbuild.c:1870 replication/slot.c:936 -#: replication/slot.c:1998 replication/slot.c:2140 storage/file/fd.c:838 +#: replication/logical/snapbuild.c:1878 replication/slot.c:936 +#: replication/slot.c:1994 replication/slot.c:2136 storage/file/fd.c:835 #: utils/time/snapmgr.c:1255 #, c-format msgid "could not rename file \"%s\" to \"%s\": %m" @@ -365,101 +366,101 @@ msgstr "konnte Datei »%s« nicht in »%s« umbenennen: %m" msgid "internal error" msgstr "interner Fehler" -#: ../common/jsonapi.c:2121 +#: ../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "Parser mit rekursivem Abstieg kann inkrementellen Lexer nicht benutzen." -#: ../common/jsonapi.c:2123 +#: ../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "Inkrementeller Parser benötigt inkrementellen Lexer." -#: ../common/jsonapi.c:2125 +#: ../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "JSON zu tief geschachtelt, maximale erlaubte Tiefe ist 6400." -#: ../common/jsonapi.c:2127 +#: ../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "Escape-Sequenz »\\%.*s« ist nicht gültig." -#: ../common/jsonapi.c:2131 +#: ../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "Zeichen mit Wert 0x%02x muss escapt werden." -#: ../common/jsonapi.c:2135 +#: ../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "Ende der Eingabe erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2138 +#: ../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "Array-Element oder »]« erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2141 +#: ../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "»,« oder »]« erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2144 +#: ../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "»:« erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2147 +#: ../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "JSON-Wert erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2150 +#: ../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "Die Eingabezeichenkette endete unerwartet." -#: ../common/jsonapi.c:2152 +#: ../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "Zeichenkette oder »}« erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2155 +#: ../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "»,« oder »}« erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2158 +#: ../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "Zeichenkette erwartet, aber »%.*s« gefunden." -#: ../common/jsonapi.c:2161 +#: ../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "Token »%.*s« ist ungültig." -#: ../common/jsonapi.c:2164 jsonpath_scan.l:608 +#: ../common/jsonapi.c:2167 jsonpath_scan.l:608 #, c-format msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 kann nicht in »text« umgewandelt werden." -#: ../common/jsonapi.c:2166 +#: ../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "Nach »\\u« müssen vier Hexadezimalziffern folgen." -#: ../common/jsonapi.c:2169 +#: ../common/jsonapi.c:2172 msgid "Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8." msgstr "Unicode-Escape-Werte können nicht für Code-Punkt-Werte über 007F verwendet werden, wenn die Kodierung nicht UTF8 ist." -#: ../common/jsonapi.c:2178 +#: ../common/jsonapi.c:2181 #, c-format msgid "Unicode escape value could not be translated to the server's encoding %s." msgstr "Unicode-Escape-Wert konnte nicht in die Serverkodierung %s umgewandelt werden." -#: ../common/jsonapi.c:2185 jsonpath_scan.l:641 +#: ../common/jsonapi.c:2188 jsonpath_scan.l:641 #, c-format msgid "Unicode high surrogate must not follow a high surrogate." msgstr "Unicode-High-Surrogate darf nicht auf ein High-Surrogate folgen." -#: ../common/jsonapi.c:2187 jsonpath_scan.l:652 jsonpath_scan.l:662 +#: ../common/jsonapi.c:2190 jsonpath_scan.l:652 jsonpath_scan.l:662 #: jsonpath_scan.l:713 #, c-format msgid "Unicode low surrogate must follow a high surrogate." @@ -645,8 +646,8 @@ msgstr "konnte Backup-Manifest nicht parsen: %s" #: ../common/percentrepl.c:79 ../common/percentrepl.c:85 #: ../common/percentrepl.c:118 ../common/percentrepl.c:124 #: tcop/backend_startup.c:741 utils/misc/guc.c:3167 utils/misc/guc.c:3208 -#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6931 -#: utils/misc/guc.c:6972 +#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6942 +#: utils/misc/guc.c:6983 #, c-format msgid "invalid value for parameter \"%s\": \"%s\"" msgstr "ungültiger Wert für Parameter »%s«: »%s«" @@ -710,10 +711,10 @@ msgstr "konnte Statuscode des Subprozesses nicht ermitteln: Fehlercode %lu" #: access/transam/twophase.c:1717 access/transam/xlogarchive.c:119 #: access/transam/xlogarchive.c:399 postmaster/postmaster.c:1048 #: postmaster/syslogger.c:1488 replication/logical/origin.c:591 -#: replication/logical/reorderbuffer.c:4589 -#: replication/logical/snapbuild.c:1751 replication/logical/snapbuild.c:2185 -#: replication/slot.c:2192 storage/file/fd.c:878 storage/file/fd.c:3378 -#: storage/file/fd.c:3440 storage/file/reinit.c:261 storage/ipc/dsm.c:343 +#: replication/logical/reorderbuffer.c:4719 +#: replication/logical/snapbuild.c:1759 replication/logical/snapbuild.c:2193 +#: replication/slot.c:2188 storage/file/fd.c:875 storage/file/fd.c:3375 +#: storage/file/fd.c:3437 storage/file/reinit.c:261 storage/ipc/dsm.c:343 #: storage/smgr/md.c:381 storage/smgr/md.c:440 storage/sync/sync.c:243 #: utils/time/snapmgr.c:1591 #, c-format @@ -721,8 +722,8 @@ msgid "could not remove file \"%s\": %m" msgstr "konnte Datei »%s« nicht löschen: %m" #: ../common/rmtree.c:124 commands/tablespace.c:767 commands/tablespace.c:780 -#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3370 -#: storage/file/fd.c:3779 +#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3367 +#: storage/file/fd.c:3776 #, c-format msgid "could not remove directory \"%s\": %m" msgstr "konnte Verzeichnis »%s« nicht löschen: %m" @@ -760,7 +761,7 @@ msgstr "" msgid "could not look up effective user ID %ld: %s" msgstr "konnte effektive Benutzer-ID %ld nicht nachschlagen: %s" -#: ../common/username.c:45 libpq/auth.c:1888 +#: ../common/username.c:45 libpq/auth.c:1896 msgid "user does not exist" msgstr "Benutzer existiert nicht" @@ -1077,67 +1078,72 @@ msgstr "Wertebereich des Typs für benutzerdefinierte Relationsparameter übersc msgid "RESET must not include values for parameters" msgstr "RESET darf keinen Parameterwert enthalten" -#: access/common/reloptions.c:1263 +#: access/common/reloptions.c:1264 #, c-format msgid "unrecognized parameter namespace \"%s\"" msgstr "unbekannter Parameter-Namensraum »%s«" -#: access/common/reloptions.c:1300 commands/variable.c:1214 +#: access/common/reloptions.c:1294 commands/foreigncmds.c:86 +#, c-format +msgid "invalid option name \"%s\": must not contain \"=\"" +msgstr "ungültiger Optionsname »%s«: darf nicht »=« enthalten" + +#: access/common/reloptions.c:1309 commands/variable.c:1214 #, c-format msgid "tables declared WITH OIDS are not supported" msgstr "Tabellen mit WITH OIDS werden nicht unterstützt" -#: access/common/reloptions.c:1468 +#: access/common/reloptions.c:1477 #, c-format msgid "unrecognized parameter \"%s\"" msgstr "unbekannter Parameter »%s«" -#: access/common/reloptions.c:1580 +#: access/common/reloptions.c:1589 #, c-format msgid "parameter \"%s\" specified more than once" msgstr "Parameter »%s« mehrmals angegeben" -#: access/common/reloptions.c:1596 +#: access/common/reloptions.c:1605 #, c-format msgid "invalid value for boolean option \"%s\": %s" msgstr "ungültiger Wert für Boole’sche Option »%s«: »%s«" -#: access/common/reloptions.c:1608 +#: access/common/reloptions.c:1617 #, c-format msgid "invalid value for integer option \"%s\": %s" msgstr "ungültiger Wert für ganzzahlige Option »%s«: »%s«" -#: access/common/reloptions.c:1614 access/common/reloptions.c:1634 +#: access/common/reloptions.c:1623 access/common/reloptions.c:1643 #, c-format msgid "value %s out of bounds for option \"%s\"" msgstr "Wert %s ist außerhalb des gültigen Bereichs für Option »%s«" -#: access/common/reloptions.c:1616 +#: access/common/reloptions.c:1625 #, c-format msgid "Valid values are between \"%d\" and \"%d\"." msgstr "Gültige Werte sind zwischen »%d« und »%d«." -#: access/common/reloptions.c:1628 +#: access/common/reloptions.c:1637 #, c-format msgid "invalid value for floating point option \"%s\": %s" msgstr "ungültiger Wert für Gleitkommaoption »%s«: »%s«" -#: access/common/reloptions.c:1636 +#: access/common/reloptions.c:1645 #, c-format msgid "Valid values are between \"%f\" and \"%f\"." msgstr "Gültige Werte sind zwischen »%f« und »%f«." -#: access/common/reloptions.c:1658 +#: access/common/reloptions.c:1667 #, c-format msgid "invalid value for enum option \"%s\": %s" msgstr "ungültiger Wert für Enum-Option »%s«: »%s«" -#: access/common/reloptions.c:1989 +#: access/common/reloptions.c:1998 #, c-format msgid "cannot specify storage parameters for a partitioned table" msgstr "für eine partitionierte Tabelle können keine Storage-Parameter angegeben werden" -#: access/common/reloptions.c:1990 +#: access/common/reloptions.c:1999 #, c-format msgid "Specify storage parameters for its leaf partitions instead." msgstr "Geben Sie Storage-Parameter stattdessen für ihre Blattpartitionen an." @@ -1280,7 +1286,7 @@ msgstr "konnte die für das Zeichenketten-Hashing zu verwendende Sortierfolge ni #: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:673 #: catalog/heap.c:679 commands/createas.c:201 commands/createas.c:508 -#: commands/indexcmds.c:2021 commands/tablecmds.c:18232 commands/view.c:81 +#: commands/indexcmds.c:2021 commands/tablecmds.c:18242 commands/view.c:81 #: regex/regc_pg_locale.c:245 utils/adt/formatting.c:1653 #: utils/adt/formatting.c:1801 utils/adt/formatting.c:1991 utils/adt/like.c:189 #: utils/adt/like_support.c:1024 utils/adt/varchar.c:738 @@ -1335,39 +1341,39 @@ msgstr "in Operatorfamilie »%s« für Zugriffsmethode %s fehlt Support-Funktion msgid "operator family \"%s\" of access method %s is missing cross-type operator(s)" msgstr "in Operatorfamilie »%s« für Zugriffsmethode %s fehlen typübergreifende Operatoren" -#: access/heap/heapam.c:2206 +#: access/heap/heapam.c:2241 #, c-format msgid "cannot insert tuples in a parallel worker" msgstr "in einem parallelen Arbeitsprozess können keine Tupel eingefügt werden" -#: access/heap/heapam.c:2725 +#: access/heap/heapam.c:2764 #, c-format msgid "cannot delete tuples during a parallel operation" msgstr "während einer parallelen Operation können keine Tupel gelöscht werden" -#: access/heap/heapam.c:2772 +#: access/heap/heapam.c:2811 #, c-format msgid "attempted to delete invisible tuple" msgstr "Versuch ein unsichtbares Tupel zu löschen" -#: access/heap/heapam.c:3220 access/heap/heapam.c:6501 access/index/genam.c:818 +#: access/heap/heapam.c:3261 access/heap/heapam.c:6542 access/index/genam.c:818 #, c-format msgid "cannot update tuples during a parallel operation" msgstr "während einer parallelen Operation können keine Tupel aktualisiert werden" -#: access/heap/heapam.c:3397 +#: access/heap/heapam.c:3438 #, c-format msgid "attempted to update invisible tuple" msgstr "Versuch ein unsichtbares Tupel zu aktualisieren" -#: access/heap/heapam.c:4908 access/heap/heapam.c:4946 -#: access/heap/heapam.c:5211 access/heap/heapam_handler.c:468 +#: access/heap/heapam.c:4949 access/heap/heapam.c:4987 +#: access/heap/heapam.c:5252 access/heap/heapam_handler.c:468 #, c-format msgid "could not obtain lock on row in relation \"%s\"" msgstr "konnte Sperre für Zeile in Relation »%s« nicht setzen" -#: access/heap/heapam.c:6314 commands/trigger.c:3340 -#: executor/nodeModifyTable.c:2396 executor/nodeModifyTable.c:2487 +#: access/heap/heapam.c:6355 commands/trigger.c:3425 +#: executor/nodeModifyTable.c:2399 executor/nodeModifyTable.c:2490 #, c-format msgid "tuple to be updated was already modified by an operation triggered by the current command" msgstr "das zu aktualisierende Tupel wurde schon durch eine vom aktuellen Befehl ausgelöste Operation verändert" @@ -1389,13 +1395,13 @@ msgstr "konnte nicht in Datei »%s« schreiben, %d von %d geschrieben: %m" #: access/heap/rewriteheap.c:977 access/heap/rewriteheap.c:1094 #: access/transam/timeline.c:329 access/transam/timeline.c:481 -#: access/transam/xlog.c:3255 access/transam/xlog.c:3446 -#: access/transam/xlog.c:4283 access/transam/xlog.c:9269 +#: access/transam/xlog.c:3237 access/transam/xlog.c:3428 +#: access/transam/xlog.c:4265 access/transam/xlog.c:9295 #: access/transam/xlogfuncs.c:692 backup/basebackup_server.c:149 #: backup/basebackup_server.c:242 commands/dbcommands.c:494 #: postmaster/launch_backend.c:340 postmaster/postmaster.c:4114 #: postmaster/walsummarizer.c:1212 replication/logical/origin.c:603 -#: replication/slot.c:2059 storage/file/copydir.c:157 storage/smgr/md.c:230 +#: replication/slot.c:2055 storage/file/copydir.c:157 storage/smgr/md.c:230 #: utils/time/snapmgr.c:1234 #, c-format msgid "could not create file \"%s\": %m" @@ -1408,15 +1414,15 @@ msgstr "konnte Datei »%s« nicht auf %u kürzen: %m" #: access/heap/rewriteheap.c:1122 access/transam/timeline.c:384 #: access/transam/timeline.c:424 access/transam/timeline.c:498 -#: access/transam/xlog.c:3305 access/transam/xlog.c:3502 -#: access/transam/xlog.c:4295 commands/dbcommands.c:506 +#: access/transam/xlog.c:3287 access/transam/xlog.c:3484 +#: access/transam/xlog.c:4277 commands/dbcommands.c:506 #: postmaster/launch_backend.c:351 postmaster/launch_backend.c:363 #: replication/logical/origin.c:615 replication/logical/origin.c:657 -#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1827 -#: replication/slot.c:2094 storage/file/buffile.c:545 +#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1835 +#: replication/slot.c:2090 storage/file/buffile.c:545 #: storage/file/copydir.c:197 utils/init/miscinit.c:1655 #: utils/init/miscinit.c:1666 utils/init/miscinit.c:1674 utils/misc/guc.c:4491 -#: utils/misc/guc.c:4522 utils/misc/guc.c:5675 utils/misc/guc.c:5693 +#: utils/misc/guc.c:4522 utils/misc/guc.c:5680 utils/misc/guc.c:5698 #: utils/time/snapmgr.c:1239 utils/time/snapmgr.c:1246 #, c-format msgid "could not write to file \"%s\": %m" @@ -1548,22 +1554,22 @@ msgstr "WAL-Benutzung: %lld Einträge, %lld Full Page Images, %llu Bytes\n" msgid "system usage: %s" msgstr "Systembenutzung: %s" -#: access/heap/vacuumlazy.c:2173 +#: access/heap/vacuumlazy.c:2178 #, c-format msgid "table \"%s\": removed %lld dead item identifiers in %u pages" msgstr "Tabelle »%s«: %lld tote Item-Bezeichner in %u Seiten entfernt" -#: access/heap/vacuumlazy.c:2327 +#: access/heap/vacuumlazy.c:2332 #, c-format msgid "bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans" msgstr "umgehe nicht essentielle Wartung der Tabelle »%s.%s.%s« als Ausfallsicherung nach %d Index-Scans" -#: access/heap/vacuumlazy.c:2330 +#: access/heap/vacuumlazy.c:2335 #, c-format msgid "The table's relfrozenxid or relminmxid is too far in the past." msgstr "relfrozenxid oder relminmxid der Tabelle ist zu weit in der Vergangenheit." -#: access/heap/vacuumlazy.c:2331 +#: access/heap/vacuumlazy.c:2336 #, c-format msgid "" "Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n" @@ -1572,67 +1578,67 @@ msgstr "" "Erhöhen Sie eventuell die Konfigurationsparameter »maintenance_work_mem« oder »autovacuum_work_mem«.\n" "Sie müssen möglicherweise auch andere Wege in Betracht ziehen, wie VACUUM mit der Benutzung von Transaktions-IDs mithalten kann." -#: access/heap/vacuumlazy.c:2593 +#: access/heap/vacuumlazy.c:2598 #, c-format msgid "\"%s\": stopping truncate due to conflicting lock request" msgstr "»%s«: Truncate wird gestoppt wegen Sperrkonflikt" -#: access/heap/vacuumlazy.c:2663 +#: access/heap/vacuumlazy.c:2668 #, c-format msgid "table \"%s\": truncated %u to %u pages" msgstr "Tabelle »%s«: von %u auf %u Seiten verkürzt" -#: access/heap/vacuumlazy.c:2725 +#: access/heap/vacuumlazy.c:2730 #, c-format msgid "table \"%s\": suspending truncate due to conflicting lock request" msgstr "Tabelle »%s«: Truncate wird ausgesetzt wegen Sperrkonflikt" -#: access/heap/vacuumlazy.c:2844 +#: access/heap/vacuumlazy.c:2849 #, c-format msgid "disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary tables in parallel" msgstr "Paralleloption für Vacuum von »%s« wird deaktiviert --- Vacuum in temporären Tabellen kann nicht parallel ausgeführt werden" -#: access/heap/vacuumlazy.c:3111 +#: access/heap/vacuumlazy.c:3116 #, c-format msgid "while scanning block %u offset %u of relation \"%s.%s\"" msgstr "beim Scannen von Block %u Offset %u von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3114 +#: access/heap/vacuumlazy.c:3119 #, c-format msgid "while scanning block %u of relation \"%s.%s\"" msgstr "beim Scannen von Block %u von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3118 +#: access/heap/vacuumlazy.c:3123 #, c-format msgid "while scanning relation \"%s.%s\"" msgstr "beim Scannen von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3126 +#: access/heap/vacuumlazy.c:3131 #, c-format msgid "while vacuuming block %u offset %u of relation \"%s.%s\"" msgstr "beim Vacuum von Block %u Offset %u von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3129 +#: access/heap/vacuumlazy.c:3134 #, c-format msgid "while vacuuming block %u of relation \"%s.%s\"" msgstr "beim Vacuum von Block %u von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3133 +#: access/heap/vacuumlazy.c:3138 #, c-format msgid "while vacuuming relation \"%s.%s\"" msgstr "beim Vacuum von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3138 commands/vacuumparallel.c:1112 +#: access/heap/vacuumlazy.c:3143 commands/vacuumparallel.c:1112 #, c-format msgid "while vacuuming index \"%s\" of relation \"%s.%s\"" msgstr "beim Vacuum von Index »%s« von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3143 commands/vacuumparallel.c:1118 +#: access/heap/vacuumlazy.c:3148 commands/vacuumparallel.c:1118 #, c-format msgid "while cleaning up index \"%s\" of relation \"%s.%s\"" msgstr "beim Säubern von Index »%s« von Relation »%s.%s«" -#: access/heap/vacuumlazy.c:3149 +#: access/heap/vacuumlazy.c:3154 #, c-format msgid "while truncating relation \"%s.%s\" to %u blocks" msgstr "beim Trunkieren von Relation »%s.%s« auf %u Blöcke" @@ -1658,8 +1664,8 @@ msgid "cannot access index \"%s\" while it is being reindexed" msgstr "auf Index »%s« kann nicht zugegriffen werden, während er reindiziert wird" #: access/index/indexam.c:203 catalog/objectaddress.c:1356 -#: commands/indexcmds.c:2851 commands/tablecmds.c:281 commands/tablecmds.c:305 -#: commands/tablecmds.c:17927 commands/tablecmds.c:19816 +#: commands/indexcmds.c:2885 commands/tablecmds.c:281 commands/tablecmds.c:305 +#: commands/tablecmds.c:17937 commands/tablecmds.c:19834 #, c-format msgid "\"%s\" is not an index" msgstr "»%s« ist kein Index" @@ -1705,17 +1711,17 @@ msgstr "Index »%s« enthält eine halbtote interne Seite" msgid "This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it." msgstr "Die Ursache kann ein unterbrochenes VACUUM in Version 9.3 oder älter vor dem Upgrade sein. Bitte REINDEX durchführen." -#: access/nbtree/nbtutils.c:5108 +#: access/nbtree/nbtutils.c:5114 #, c-format msgid "index row size %zu exceeds btree version %u maximum %zu for index \"%s\"" msgstr "Größe %zu der Indexzeile überschreitet btree-Version %u Maximum %zu für Index »%s«" -#: access/nbtree/nbtutils.c:5114 +#: access/nbtree/nbtutils.c:5120 #, c-format msgid "Index row references tuple (%u,%u) in relation \"%s\"." msgstr "Indexzeile verweist auf Tupel (%u,%u) in Relation »%s«." -#: access/nbtree/nbtutils.c:5118 +#: access/nbtree/nbtutils.c:5124 #, c-format msgid "" "Values larger than 1/3 of a buffer page cannot be indexed.\n" @@ -2204,7 +2210,7 @@ msgid "calculated CRC checksum does not match value stored in file \"%s\"" msgstr "berechnete CRC-Prüfsumme stimmt nicht mit dem Wert in Datei »%s« überein" #: access/transam/twophase.c:1419 access/transam/xlogrecovery.c:565 -#: postmaster/walsummarizer.c:936 replication/logical/logical.c:211 +#: postmaster/walsummarizer.c:936 replication/logical/logical.c:213 #: replication/walsender.c:840 #, c-format msgid "Failed while allocating a WAL reading processor." @@ -2282,7 +2288,7 @@ msgstr "konnte Zweiphasen-Statusdatei für Transaktion %u nicht wiederherstellen msgid "Two-phase state file has been found in WAL record %X/%X, but this transaction has already been restored from disk." msgstr "Zweiphasen-Statusdatei wurde in WAL-Eintrag %X/%X gefunden, aber diese Transaktion wurde schon von der Festplatte wiederhergestellt." -#: access/transam/twophase.c:2524 storage/file/fd.c:514 utils/fmgr/dfmgr.c:209 +#: access/transam/twophase.c:2524 storage/file/fd.c:511 utils/fmgr/dfmgr.c:209 #, c-format msgid "could not access file \"%s\": %m" msgstr "konnte nicht auf Datei »%s« zugreifen: %m" @@ -2446,464 +2452,459 @@ msgstr "während einer parallelen Operation kann nicht auf einen Sicherungspunkt msgid "cannot have more than 2^32-1 subtransactions in a transaction" msgstr "kann nicht mehr als 2^32-1 Subtransaktionen in einer Transaktion haben" -#: access/transam/xlog.c:1542 +#: access/transam/xlog.c:1543 #, c-format msgid "request to flush past end of generated WAL; request %X/%X, current position %X/%X" msgstr "Flush hinter das Ende des erzeugten WAL angefordert; Anforderung %X/%X, aktuelle Position %X/%X" -#: access/transam/xlog.c:1769 +#: access/transam/xlog.c:1770 #, c-format msgid "cannot read past end of generated WAL: requested %X/%X, current position %X/%X" msgstr "kann nicht hinter das Ende des erzeugten WAL lesen: Anforderung %X/%X, aktuelle Position %X/%X" -#: access/transam/xlog.c:2210 access/transam/xlog.c:4501 +#: access/transam/xlog.c:2211 access/transam/xlog.c:4483 #, c-format msgid "The WAL segment size must be a power of two between 1 MB and 1 GB." msgstr "Die WAL-Segmentgröße muss eine Zweierpotenz zwischen 1 MB und 1 GB sein." -#: access/transam/xlog.c:2228 -#, c-format -msgid "\"%s\" must be set to -1 during binary upgrade mode." -msgstr "»%s« muss im Binary-Upgrade-Modus auf -1 gesetzt sein." - -#: access/transam/xlog.c:2477 +#: access/transam/xlog.c:2459 #, c-format msgid "could not write to log file \"%s\" at offset %u, length %zu: %m" msgstr "konnte nicht in Logdatei »%s« bei Position %u, Länge %zu schreiben: %m" -#: access/transam/xlog.c:3739 access/transam/xlogutils.c:831 -#: replication/walsender.c:3075 +#: access/transam/xlog.c:3721 access/transam/xlogutils.c:831 +#: replication/walsender.c:3079 #, c-format msgid "requested WAL segment %s has already been removed" msgstr "das angeforderte WAL-Segment %s wurde schon entfernt" -#: access/transam/xlog.c:4061 +#: access/transam/xlog.c:4043 #, c-format msgid "could not rename file \"%s\": %m" msgstr "konnte Datei »%s« nicht umbenennen: %m" -#: access/transam/xlog.c:4104 access/transam/xlog.c:4115 -#: access/transam/xlog.c:4136 +#: access/transam/xlog.c:4086 access/transam/xlog.c:4097 +#: access/transam/xlog.c:4118 #, c-format msgid "required WAL directory \"%s\" does not exist" msgstr "benötigtes WAL-Verzeichnis »%s« existiert nicht" -#: access/transam/xlog.c:4121 access/transam/xlog.c:4142 +#: access/transam/xlog.c:4103 access/transam/xlog.c:4124 #, c-format msgid "creating missing WAL directory \"%s\"" msgstr "erzeuge fehlendes WAL-Verzeichnis »%s«" -#: access/transam/xlog.c:4125 access/transam/xlog.c:4145 +#: access/transam/xlog.c:4107 access/transam/xlog.c:4127 #: commands/dbcommands.c:3262 #, c-format msgid "could not create missing directory \"%s\": %m" msgstr "konnte fehlendes Verzeichnis »%s« nicht erzeugen: %m" -#: access/transam/xlog.c:4212 +#: access/transam/xlog.c:4194 #, c-format msgid "could not generate secret authorization token" msgstr "konnte geheimes Autorisierungstoken nicht erzeugen" -#: access/transam/xlog.c:4363 access/transam/xlog.c:4373 -#: access/transam/xlog.c:4399 access/transam/xlog.c:4407 -#: access/transam/xlog.c:4415 access/transam/xlog.c:4421 -#: access/transam/xlog.c:4429 access/transam/xlog.c:4437 -#: access/transam/xlog.c:4445 access/transam/xlog.c:4453 +#: access/transam/xlog.c:4345 access/transam/xlog.c:4355 +#: access/transam/xlog.c:4381 access/transam/xlog.c:4389 +#: access/transam/xlog.c:4397 access/transam/xlog.c:4403 +#: access/transam/xlog.c:4411 access/transam/xlog.c:4419 +#: access/transam/xlog.c:4427 access/transam/xlog.c:4435 +#: access/transam/xlog.c:4443 access/transam/xlog.c:4451 #: access/transam/xlog.c:4461 access/transam/xlog.c:4469 -#: access/transam/xlog.c:4479 access/transam/xlog.c:4487 #: utils/init/miscinit.c:1812 #, c-format msgid "database files are incompatible with server" msgstr "Datenbankdateien sind inkompatibel mit Server" -#: access/transam/xlog.c:4364 +#: access/transam/xlog.c:4346 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d (0x%08x), but the server was compiled with PG_CONTROL_VERSION %d (0x%08x)." msgstr "Der Datenbank-Cluster wurde mit PG_CONTROL_VERSION %d (0x%08x) initialisiert, aber der Server wurde mit PG_CONTROL_VERSION %d (0x%08x) kompiliert." -#: access/transam/xlog.c:4368 +#: access/transam/xlog.c:4350 #, c-format msgid "This could be a problem of mismatched byte ordering. It looks like you need to initdb." msgstr "Das Problem könnte eine falsche Byte-Reihenfolge sein. Es sieht so aus, dass Sie initdb ausführen müssen." -#: access/transam/xlog.c:4374 +#: access/transam/xlog.c:4356 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d, but the server was compiled with PG_CONTROL_VERSION %d." msgstr "Der Datenbank-Cluster wurde mit PG_CONTROL_VERSION %d initialisiert, aber der Server wurde mit PG_CONTROL_VERSION %d kompiliert." -#: access/transam/xlog.c:4377 access/transam/xlog.c:4403 -#: access/transam/xlog.c:4411 access/transam/xlog.c:4417 +#: access/transam/xlog.c:4359 access/transam/xlog.c:4385 +#: access/transam/xlog.c:4393 access/transam/xlog.c:4399 #, c-format msgid "It looks like you need to initdb." msgstr "Es sieht so aus, dass Sie initdb ausführen müssen." -#: access/transam/xlog.c:4389 +#: access/transam/xlog.c:4371 #, c-format msgid "incorrect checksum in control file" msgstr "falsche Prüfsumme in Kontrolldatei" -#: access/transam/xlog.c:4400 +#: access/transam/xlog.c:4382 #, c-format msgid "The database cluster was initialized with CATALOG_VERSION_NO %d, but the server was compiled with CATALOG_VERSION_NO %d." msgstr "Der Datenbank-Cluster wurde mit CATALOG_VERSION_NO %d initialisiert, aber der Server wurde mit CATALOG_VERSION_NO %d kompiliert." -#: access/transam/xlog.c:4408 +#: access/transam/xlog.c:4390 #, c-format msgid "The database cluster was initialized with MAXALIGN %d, but the server was compiled with MAXALIGN %d." msgstr "Der Datenbank-Cluster wurde mit MAXALIGN %d initialisiert, aber der Server wurde mit MAXALIGN %d kompiliert." -#: access/transam/xlog.c:4416 +#: access/transam/xlog.c:4398 #, c-format msgid "The database cluster appears to use a different floating-point number format than the server executable." msgstr "Der Datenbank-Cluster verwendet anscheinend ein anderes Fließkommazahlenformat als das Serverprogramm." -#: access/transam/xlog.c:4422 +#: access/transam/xlog.c:4404 #, c-format msgid "The database cluster was initialized with BLCKSZ %d, but the server was compiled with BLCKSZ %d." msgstr "Der Datenbank-Cluster wurde mit BLCKSZ %d initialisiert, aber der Server wurde mit BLCKSZ %d kompiliert." -#: access/transam/xlog.c:4425 access/transam/xlog.c:4433 -#: access/transam/xlog.c:4441 access/transam/xlog.c:4449 -#: access/transam/xlog.c:4457 access/transam/xlog.c:4465 -#: access/transam/xlog.c:4473 access/transam/xlog.c:4482 -#: access/transam/xlog.c:4490 +#: access/transam/xlog.c:4407 access/transam/xlog.c:4415 +#: access/transam/xlog.c:4423 access/transam/xlog.c:4431 +#: access/transam/xlog.c:4439 access/transam/xlog.c:4447 +#: access/transam/xlog.c:4455 access/transam/xlog.c:4464 +#: access/transam/xlog.c:4472 #, c-format msgid "It looks like you need to recompile or initdb." msgstr "Es sieht so aus, dass Sie neu kompilieren oder initdb ausführen müssen." -#: access/transam/xlog.c:4430 +#: access/transam/xlog.c:4412 #, c-format msgid "The database cluster was initialized with RELSEG_SIZE %d, but the server was compiled with RELSEG_SIZE %d." msgstr "Der Datenbank-Cluster wurde mit RELSEG_SIZE %d initialisiert, aber der Server wurde mit RELSEGSIZE %d kompiliert." -#: access/transam/xlog.c:4438 +#: access/transam/xlog.c:4420 #, c-format msgid "The database cluster was initialized with XLOG_BLCKSZ %d, but the server was compiled with XLOG_BLCKSZ %d." msgstr "Der Datenbank-Cluster wurde mit XLOG_BLCKSZ %d initialisiert, aber der Server wurde mit XLOG_BLCKSZ %d kompiliert." -#: access/transam/xlog.c:4446 +#: access/transam/xlog.c:4428 #, c-format msgid "The database cluster was initialized with NAMEDATALEN %d, but the server was compiled with NAMEDATALEN %d." msgstr "Der Datenbank-Cluster wurde mit NAMEDATALEN %d initialisiert, aber der Server wurde mit NAMEDATALEN %d kompiliert." -#: access/transam/xlog.c:4454 +#: access/transam/xlog.c:4436 #, c-format msgid "The database cluster was initialized with INDEX_MAX_KEYS %d, but the server was compiled with INDEX_MAX_KEYS %d." msgstr "Der Datenbank-Cluster wurde mit INDEX_MAX_KEYS %d initialisiert, aber der Server wurde mit INDEX_MAX_KEYS %d kompiliert." -#: access/transam/xlog.c:4462 +#: access/transam/xlog.c:4444 #, c-format msgid "The database cluster was initialized with TOAST_MAX_CHUNK_SIZE %d, but the server was compiled with TOAST_MAX_CHUNK_SIZE %d." msgstr "Der Datenbank-Cluster wurde mit TOAST_MAX_CHUNK_SIZE %d initialisiert, aber der Server wurde mit TOAST_MAX_CHUNK_SIZE %d kompiliert." -#: access/transam/xlog.c:4470 +#: access/transam/xlog.c:4452 #, c-format msgid "The database cluster was initialized with LOBLKSIZE %d, but the server was compiled with LOBLKSIZE %d." msgstr "Der Datenbank-Cluster wurde mit LOBLKSIZE %d initialisiert, aber der Server wurde mit LOBLKSIZE %d kompiliert." -#: access/transam/xlog.c:4480 +#: access/transam/xlog.c:4462 #, c-format msgid "The database cluster was initialized without USE_FLOAT8_BYVAL but the server was compiled with USE_FLOAT8_BYVAL." msgstr "Der Datenbank-Cluster wurde ohne USE_FLOAT8_BYVAL initialisiert, aber der Server wurde mit USE_FLOAT8_BYVAL kompiliert." -#: access/transam/xlog.c:4488 +#: access/transam/xlog.c:4470 #, c-format msgid "The database cluster was initialized with USE_FLOAT8_BYVAL but the server was compiled without USE_FLOAT8_BYVAL." msgstr "Der Datenbank-Cluster wurde mit USE_FLOAT8_BYVAL initialisiert, aber der Server wurde ohne USE_FLOAT8_BYVAL kompiliert." -#: access/transam/xlog.c:4497 +#: access/transam/xlog.c:4479 #, c-format msgid "invalid WAL segment size in control file (%d byte)" msgid_plural "invalid WAL segment size in control file (%d bytes)" msgstr[0] "ungültige WAL-Segmentgröße in Kontrolldatei (%d Byte)" msgstr[1] "ungültige WAL-Segmentgröße in Kontrolldatei (%d Bytes)" -#: access/transam/xlog.c:4510 +#: access/transam/xlog.c:4492 #, c-format msgid "\"min_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "»min_wal_size« muss mindestens zweimal so groß wie »wal_segment_size« sein" -#: access/transam/xlog.c:4514 +#: access/transam/xlog.c:4496 #, c-format msgid "\"max_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "»max_wal_size« muss mindestens zweimal so groß wie »wal_segment_size« sein" -#: access/transam/xlog.c:4662 catalog/namespace.c:4696 +#: access/transam/xlog.c:4644 catalog/namespace.c:4696 #: commands/tablespace.c:1210 commands/user.c:2529 commands/variable.c:72 -#: replication/slot.c:2446 tcop/postgres.c:3715 utils/error/elog.c:2247 +#: replication/slot.c:2442 tcop/postgres.c:3715 utils/error/elog.c:2247 #, c-format msgid "List syntax is invalid." msgstr "Die Listensyntax ist ungültig." -#: access/transam/xlog.c:4708 commands/user.c:2545 commands/variable.c:173 +#: access/transam/xlog.c:4690 commands/user.c:2545 commands/variable.c:173 #: tcop/postgres.c:3731 utils/error/elog.c:2273 #, c-format msgid "Unrecognized key word: \"%s\"." msgstr "Unbekanntes Schlüsselwort: »%s«." -#: access/transam/xlog.c:5129 +#: access/transam/xlog.c:5111 #, c-format msgid "could not write bootstrap write-ahead log file: %m" msgstr "konnte Bootstrap-Write-Ahead-Log-Datei nicht schreiben: %m" -#: access/transam/xlog.c:5137 +#: access/transam/xlog.c:5119 #, c-format msgid "could not fsync bootstrap write-ahead log file: %m" msgstr "konnte Bootstrap-Write-Ahead-Log-Datei nicht fsyncen: %m" -#: access/transam/xlog.c:5143 +#: access/transam/xlog.c:5125 #, c-format msgid "could not close bootstrap write-ahead log file: %m" msgstr "konnte Bootstrap-Write-Ahead-Log-Datei nicht schließen: %m" -#: access/transam/xlog.c:5368 +#: access/transam/xlog.c:5350 #, c-format msgid "WAL was generated with \"wal_level=minimal\", cannot continue recovering" msgstr "WAL wurde mit »wal_level=minimal« erzeugt, Wiederherstellung kann nicht fortgesetzt werden" -#: access/transam/xlog.c:5369 +#: access/transam/xlog.c:5351 #, c-format msgid "This happens if you temporarily set \"wal_level=minimal\" on the server." msgstr "Das passiert, wenn auf dem Server vorübergehend »wal_level=minimal« gesetzt wurde." -#: access/transam/xlog.c:5370 +#: access/transam/xlog.c:5352 #, c-format msgid "Use a backup taken after setting \"wal_level\" to higher than \"minimal\"." msgstr "Verwenden Sie ein Backup, das durchgeführt wurde, nachdem »wal_level« auf höher als »minimal« gesetzt wurde." -#: access/transam/xlog.c:5435 +#: access/transam/xlog.c:5417 #, c-format msgid "control file contains invalid checkpoint location" msgstr "Kontrolldatei enthält ungültige Checkpoint-Position" -#: access/transam/xlog.c:5446 +#: access/transam/xlog.c:5428 #, c-format msgid "database system was shut down at %s" msgstr "Datenbanksystem wurde am %s heruntergefahren" -#: access/transam/xlog.c:5452 +#: access/transam/xlog.c:5434 #, c-format msgid "database system was shut down in recovery at %s" msgstr "Datenbanksystem wurde während der Wiederherstellung am %s heruntergefahren" -#: access/transam/xlog.c:5458 +#: access/transam/xlog.c:5440 #, c-format msgid "database system shutdown was interrupted; last known up at %s" msgstr "Datenbanksystem wurde beim Herunterfahren unterbrochen; letzte bekannte Aktion am %s" -#: access/transam/xlog.c:5464 +#: access/transam/xlog.c:5446 #, c-format msgid "database system was interrupted while in recovery at %s" msgstr "Datenbanksystem wurde während der Wiederherstellung am %s unterbrochen" -#: access/transam/xlog.c:5466 +#: access/transam/xlog.c:5448 #, c-format msgid "This probably means that some data is corrupted and you will have to use the last backup for recovery." msgstr "Das bedeutet wahrscheinlich, dass einige Daten verfälscht sind und Sie die letzte Datensicherung zur Wiederherstellung verwenden müssen." -#: access/transam/xlog.c:5472 +#: access/transam/xlog.c:5454 #, c-format msgid "database system was interrupted while in recovery at log time %s" msgstr "Datenbanksystem wurde während der Wiederherstellung bei Logzeit %s unterbrochen" -#: access/transam/xlog.c:5474 +#: access/transam/xlog.c:5456 #, c-format msgid "If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target." msgstr "Wenn dies mehr als einmal vorgekommen ist, dann sind einige Daten möglicherweise verfälscht und Sie müssen ein früheres Wiederherstellungsziel wählen." -#: access/transam/xlog.c:5480 +#: access/transam/xlog.c:5462 #, c-format msgid "database system was interrupted; last known up at %s" msgstr "Datenbanksystem wurde unterbrochen; letzte bekannte Aktion am %s" -#: access/transam/xlog.c:5487 +#: access/transam/xlog.c:5469 #, c-format msgid "control file contains invalid database cluster state" msgstr "Kontrolldatei enthält ungültigen Datenbankclusterstatus" -#: access/transam/xlog.c:5875 +#: access/transam/xlog.c:5857 #, c-format msgid "WAL ends before end of online backup" msgstr "WAL endet vor dem Ende der Online-Sicherung" -#: access/transam/xlog.c:5876 +#: access/transam/xlog.c:5858 #, c-format msgid "All WAL generated while online backup was taken must be available at recovery." msgstr "Der komplette WAL, der während der Online-Sicherung erzeugt wurde, muss bei der Wiederherstellung verfügbar sein." -#: access/transam/xlog.c:5880 +#: access/transam/xlog.c:5862 #, c-format msgid "WAL ends before consistent recovery point" msgstr "WAL endet vor einem konsistenten Wiederherstellungspunkt" -#: access/transam/xlog.c:5926 +#: access/transam/xlog.c:5908 #, c-format msgid "selected new timeline ID: %u" msgstr "gewählte neue Zeitleisten-ID: %u" -#: access/transam/xlog.c:5959 +#: access/transam/xlog.c:5941 #, c-format msgid "archive recovery complete" msgstr "Wiederherstellung aus Archiv abgeschlossen" -#: access/transam/xlog.c:6612 +#: access/transam/xlog.c:6594 #, c-format msgid "shutting down" msgstr "fahre herunter" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6651 +#: access/transam/xlog.c:6633 #, c-format msgid "restartpoint starting:%s%s%s%s%s%s%s%s" msgstr "Restart-Punkt beginnt:%s%s%s%s%s%s%s%s" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6663 +#: access/transam/xlog.c:6645 #, c-format msgid "checkpoint starting:%s%s%s%s%s%s%s%s" msgstr "Checkpoint beginnt:%s%s%s%s%s%s%s%s" -#: access/transam/xlog.c:6728 +#: access/transam/xlog.c:6710 #, c-format msgid "restartpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "Restart-Punkt komplett: %d Puffer geschrieben (%.1f%%); %d WAL-Datei(en) hinzugefügt, %d entfernt, %d wiederverwendet; Schreiben=%ld,%03d s, Sync=%ld,%03d s, gesamt=%ld,%03d s; sync. Dateien=%d, längste=%ld,%03d s, Durchschnitt=%ld.%03d s; Entfernung=%d kB, Schätzung=%d kB; LSN=%X/%X, Redo-LSN=%X/%X" -#: access/transam/xlog.c:6751 +#: access/transam/xlog.c:6733 #, c-format msgid "checkpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "Checkpoint komplett: %d Puffer geschrieben (%.1f%%); %d WAL-Datei(en) hinzugefügt, %d entfernt, %d wiederverwendet; Schreiben=%ld,%03d s, Sync=%ld,%03d s, gesamt=%ld,%03d s; sync. Dateien=%d, längste=%ld,%03d s, Durchschnitt=%ld.%03d s; Entfernung=%d kB, Schätzung=%d kB; LSN=%X/%X, Redo-LSN=%X/%X" -#: access/transam/xlog.c:7233 +#: access/transam/xlog.c:7225 #, c-format msgid "concurrent write-ahead log activity while database system is shutting down" msgstr "gleichzeitige Write-Ahead-Log-Aktivität während das Datenbanksystem herunterfährt" -#: access/transam/xlog.c:7818 +#: access/transam/xlog.c:7840 #, c-format msgid "recovery restart point at %X/%X" msgstr "Recovery-Restart-Punkt bei %X/%X" -#: access/transam/xlog.c:7820 +#: access/transam/xlog.c:7842 #, c-format msgid "Last completed transaction was at log time %s." msgstr "Die letzte vollständige Transaktion war bei Logzeit %s." -#: access/transam/xlog.c:8082 +#: access/transam/xlog.c:8108 #, c-format msgid "restore point \"%s\" created at %X/%X" msgstr "Restore-Punkt »%s« erzeugt bei %X/%X" -#: access/transam/xlog.c:8289 +#: access/transam/xlog.c:8315 #, c-format msgid "online backup was canceled, recovery cannot continue" msgstr "Online-Sicherung wurde storniert, Wiederherstellung kann nicht fortgesetzt werden" -#: access/transam/xlog.c:8347 +#: access/transam/xlog.c:8373 #, c-format msgid "unexpected timeline ID %u (should be %u) in shutdown checkpoint record" msgstr "unerwartete Zeitleisten-ID %u (sollte %u sein) im Shutdown-Checkpoint-Datensatz" -#: access/transam/xlog.c:8405 +#: access/transam/xlog.c:8431 #, c-format msgid "unexpected timeline ID %u (should be %u) in online checkpoint record" msgstr "unerwartete Zeitleisten-ID %u (sollte %u sein) im Online-Checkpoint-Datensatz" -#: access/transam/xlog.c:8434 +#: access/transam/xlog.c:8460 #, c-format msgid "unexpected timeline ID %u (should be %u) in end-of-recovery record" msgstr "unerwartete Zeitleisten-ID %u (sollte %u sein) im End-of-Recovery-Datensatz" -#: access/transam/xlog.c:8705 +#: access/transam/xlog.c:8731 #, c-format msgid "could not fsync write-through file \"%s\": %m" msgstr "konnte Write-Through-Logdatei »%s« nicht fsyncen: %m" -#: access/transam/xlog.c:8710 +#: access/transam/xlog.c:8736 #, c-format msgid "could not fdatasync file \"%s\": %m" msgstr "konnte Datei »%s« nicht fdatasyncen: %m" -#: access/transam/xlog.c:8797 access/transam/xlog.c:9133 +#: access/transam/xlog.c:8823 access/transam/xlog.c:9159 #, c-format msgid "WAL level not sufficient for making an online backup" msgstr "WAL-Level nicht ausreichend, um Online-Sicherung durchzuführen" -#: access/transam/xlog.c:8798 access/transam/xlogfuncs.c:248 +#: access/transam/xlog.c:8824 access/transam/xlogfuncs.c:248 #, c-format msgid "\"wal_level\" must be set to \"replica\" or \"logical\" at server start." msgstr "»wal_level« muss beim Serverstart auf »replica« oder »logical« gesetzt werden." -#: access/transam/xlog.c:8803 +#: access/transam/xlog.c:8829 #, c-format msgid "backup label too long (max %d bytes)" msgstr "Backup-Label zu lang (maximal %d Bytes)" -#: access/transam/xlog.c:8924 +#: access/transam/xlog.c:8950 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed since last restartpoint" msgstr "mit »full_page_writes=off« erzeugtes WAL wurde seit dem letzten Restart-Punkt zurückgespielt" -#: access/transam/xlog.c:8926 access/transam/xlog.c:9222 +#: access/transam/xlog.c:8952 access/transam/xlog.c:9248 #, c-format msgid "This means that the backup being taken on the standby is corrupt and should not be used. Enable \"full_page_writes\" and run CHECKPOINT on the primary, and then try an online backup again." msgstr "Das bedeutet, dass die aktuelle Datensicherung auf dem Standby-Server verfälscht ist und nicht verwendet werden sollte. Schalten Sie auf dem Primärserver »full_page_writes« ein, führen Sie dort CHECKPOINT aus und versuchen Sie dann die Online-Sicherung erneut." -#: access/transam/xlog.c:9006 backup/basebackup.c:1417 utils/adt/misc.c:354 +#: access/transam/xlog.c:9032 backup/basebackup.c:1417 utils/adt/misc.c:354 #, c-format msgid "could not read symbolic link \"%s\": %m" msgstr "konnte symbolische Verknüpfung »%s« nicht lesen: %m" -#: access/transam/xlog.c:9013 backup/basebackup.c:1422 utils/adt/misc.c:359 +#: access/transam/xlog.c:9039 backup/basebackup.c:1422 utils/adt/misc.c:359 #, c-format msgid "symbolic link \"%s\" target is too long" msgstr "Ziel für symbolische Verknüpfung »%s« ist zu lang" -#: access/transam/xlog.c:9134 +#: access/transam/xlog.c:9160 #, c-format msgid "wal_level must be set to \"replica\" or \"logical\" at server start." msgstr "wal_level muss beim Serverstart auf »replica« oder »logical« gesetzt werden." -#: access/transam/xlog.c:9172 backup/basebackup.c:1281 +#: access/transam/xlog.c:9198 backup/basebackup.c:1281 #, c-format msgid "the standby was promoted during online backup" msgstr "der Standby-Server wurde während der Online-Sicherung zum Primärserver befördert" -#: access/transam/xlog.c:9173 backup/basebackup.c:1282 +#: access/transam/xlog.c:9199 backup/basebackup.c:1282 #, c-format msgid "This means that the backup being taken is corrupt and should not be used. Try taking another online backup." msgstr "Das bedeutet, dass die aktuelle Online-Sicherung verfälscht ist und nicht verwendet werden sollte. Versuchen Sie, eine neue Online-Sicherung durchzuführen." -#: access/transam/xlog.c:9220 +#: access/transam/xlog.c:9246 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed during online backup" msgstr "mit »full_page_writes=off« erzeugtes WAL wurde während der Online-Sicherung zurückgespielt" -#: access/transam/xlog.c:9336 +#: access/transam/xlog.c:9362 #, c-format msgid "base backup done, waiting for required WAL segments to be archived" msgstr "Basissicherung beendet, warte bis die benötigten WAL-Segmente archiviert sind" -#: access/transam/xlog.c:9350 +#: access/transam/xlog.c:9376 #, c-format msgid "still waiting for all required WAL segments to be archived (%d seconds elapsed)" msgstr "warte immer noch, bis alle benötigten WAL-Segmente archiviert sind (%d Sekunden abgelaufen)" -#: access/transam/xlog.c:9352 +#: access/transam/xlog.c:9378 #, c-format msgid "Check that your \"archive_command\" is executing properly. You can safely cancel this backup, but the database backup will not be usable without all the WAL segments." msgstr "Prüfen Sie, ob das »archive_command« korrekt ausgeführt wird. Dieser Sicherungsvorgang kann gefahrlos abgebrochen werden, aber die Datenbanksicherung wird ohne die fehlenden WAL-Segmente nicht benutzbar sein." -#: access/transam/xlog.c:9359 +#: access/transam/xlog.c:9385 #, c-format msgid "all required WAL segments have been archived" msgstr "alle benötigten WAL-Segmente wurden archiviert" -#: access/transam/xlog.c:9363 +#: access/transam/xlog.c:9389 #, c-format msgid "WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup" msgstr "WAL-Archivierung ist nicht eingeschaltet; Sie müssen dafür sorgen, dass alle benötigten WAL-Segmente auf andere Art kopiert werden, um die Sicherung abzuschließen" -#: access/transam/xlog.c:9402 +#: access/transam/xlog.c:9428 #, c-format msgid "aborting backup due to backend exiting before pg_backup_stop was called" msgstr "Backup wird abgebrochen, weil Backend-Prozess beendete, bevor pg_backup_stop aufgerufen wurde" @@ -3060,147 +3061,147 @@ msgstr "ungültiger Datensatz-Offset bei %X/%X: mindestens %u erwartet, %u erhal msgid "contrecord is requested by %X/%X" msgstr "Contrecord angefordert von %X/%X" -#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1134 +#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1144 #, c-format msgid "invalid record length at %X/%X: expected at least %u, got %u" msgstr "ungültige Datensatzlänge bei %X/%X: mindestens %u erwartet, %u erhalten" -#: access/transam/xlogreader.c:758 +#: access/transam/xlogreader.c:759 #, c-format msgid "there is no contrecord flag at %X/%X" msgstr "keine Contrecord-Flag bei %X/%X" -#: access/transam/xlogreader.c:771 +#: access/transam/xlogreader.c:772 #, c-format msgid "invalid contrecord length %u (expected %lld) at %X/%X" msgstr "ungültige Contrecord-Länge %u (erwartet %lld) bei %X/%X" -#: access/transam/xlogreader.c:1142 +#: access/transam/xlogreader.c:1152 #, c-format msgid "invalid resource manager ID %u at %X/%X" msgstr "ungültige Resource-Manager-ID %u bei %X/%X" -#: access/transam/xlogreader.c:1155 access/transam/xlogreader.c:1171 +#: access/transam/xlogreader.c:1165 access/transam/xlogreader.c:1181 #, c-format msgid "record with incorrect prev-link %X/%X at %X/%X" msgstr "Datensatz mit falschem Prev-Link %X/%X bei %X/%X" -#: access/transam/xlogreader.c:1209 +#: access/transam/xlogreader.c:1219 #, c-format msgid "incorrect resource manager data checksum in record at %X/%X" msgstr "ungültige Resource-Manager-Datenprüfsumme in Datensatz bei %X/%X" -#: access/transam/xlogreader.c:1243 +#: access/transam/xlogreader.c:1253 #, c-format msgid "invalid magic number %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "ungültige magische Zahl %04X in WAL-Segment %s, LSN %X/%X, Offset %u" -#: access/transam/xlogreader.c:1258 access/transam/xlogreader.c:1300 +#: access/transam/xlogreader.c:1268 access/transam/xlogreader.c:1310 #, c-format msgid "invalid info bits %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "ungültige Info-Bits %04X in WAL-Segment %s, LSN %X/%X, Offset %u" -#: access/transam/xlogreader.c:1274 +#: access/transam/xlogreader.c:1284 #, c-format msgid "WAL file is from different database system: WAL file database system identifier is %llu, pg_control database system identifier is %llu" msgstr "WAL-Datei ist von einem anderen Datenbanksystem: Datenbanksystemidentifikator in WAL-Datei ist %llu, Datenbanksystemidentifikator in pg_control ist %llu" -#: access/transam/xlogreader.c:1282 +#: access/transam/xlogreader.c:1292 #, c-format msgid "WAL file is from different database system: incorrect segment size in page header" msgstr "WAL-Datei ist von einem anderen Datenbanksystem: falsche Segmentgröße im Seitenkopf" -#: access/transam/xlogreader.c:1288 +#: access/transam/xlogreader.c:1298 #, c-format msgid "WAL file is from different database system: incorrect XLOG_BLCKSZ in page header" msgstr "WAL-Datei ist von einem anderen Datenbanksystem: falsche XLOG_BLCKSZ im Seitenkopf" -#: access/transam/xlogreader.c:1320 +#: access/transam/xlogreader.c:1330 #, c-format msgid "unexpected pageaddr %X/%X in WAL segment %s, LSN %X/%X, offset %u" msgstr "unerwartete Pageaddr %X/%X in WAL-Segment %s, LSN %X/%X, Offset %u" -#: access/transam/xlogreader.c:1346 +#: access/transam/xlogreader.c:1356 #, c-format msgid "out-of-sequence timeline ID %u (after %u) in WAL segment %s, LSN %X/%X, offset %u" msgstr "Zeitleisten-ID %u außer der Reihe (nach %u) in WAL-Segment %s, LSN %X/%X, Offset %u" -#: access/transam/xlogreader.c:1749 +#: access/transam/xlogreader.c:1759 #, c-format msgid "out-of-order block_id %u at %X/%X" msgstr "block_id %u außer der Reihe bei %X/%X" -#: access/transam/xlogreader.c:1773 +#: access/transam/xlogreader.c:1783 #, c-format msgid "BKPBLOCK_HAS_DATA set, but no data included at %X/%X" msgstr "BKPBLOCK_HAS_DATA gesetzt, aber keine Daten enthalten bei %X/%X" -#: access/transam/xlogreader.c:1780 +#: access/transam/xlogreader.c:1790 #, c-format msgid "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X" msgstr "BKPBLOCK_HAS_DATA nicht gesetzt, aber Datenlänge ist %u bei %X/%X" -#: access/transam/xlogreader.c:1816 +#: access/transam/xlogreader.c:1826 #, c-format msgid "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at %X/%X" msgstr "BKPIMAGE_HAS_HOLE gesetzt, aber Loch Offset %u Länge %u Block-Abbild-Länge %u bei %X/%X" -#: access/transam/xlogreader.c:1832 +#: access/transam/xlogreader.c:1842 #, c-format msgid "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X" msgstr "BKPIMAGE_HAS_HOLE nicht gesetzt, aber Loch Offset %u Länge %u bei %X/%X" -#: access/transam/xlogreader.c:1846 +#: access/transam/xlogreader.c:1856 #, c-format msgid "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X" msgstr "BKPIMAGE_COMPRESSED gesetzt, aber Block-Abbild-Länge %u bei %X/%X" -#: access/transam/xlogreader.c:1861 +#: access/transam/xlogreader.c:1871 #, c-format msgid "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image length is %u at %X/%X" msgstr "weder BKPIMAGE_HAS_HOLE noch BKPIMAGE_COMPRESSED gesetzt, aber Block-Abbild-Länge ist %u bei %X/%X" -#: access/transam/xlogreader.c:1877 +#: access/transam/xlogreader.c:1887 #, c-format msgid "BKPBLOCK_SAME_REL set but no previous rel at %X/%X" msgstr "BKPBLOCK_SAME_REL gesetzt, aber keine vorangehende Relation bei %X/%X" -#: access/transam/xlogreader.c:1889 +#: access/transam/xlogreader.c:1899 #, c-format msgid "invalid block_id %u at %X/%X" msgstr "ungültige block_id %u bei %X/%X" -#: access/transam/xlogreader.c:1956 +#: access/transam/xlogreader.c:1966 #, c-format msgid "record with invalid length at %X/%X" msgstr "Datensatz mit ungültiger Länge bei %X/%X" -#: access/transam/xlogreader.c:1982 +#: access/transam/xlogreader.c:1992 #, c-format msgid "could not locate backup block with ID %d in WAL record" msgstr "konnte Backup-Block mit ID %d nicht im WAL-Eintrag finden" -#: access/transam/xlogreader.c:2066 +#: access/transam/xlogreader.c:2076 #, c-format msgid "could not restore image at %X/%X with invalid block %d specified" msgstr "konnte Abbild bei %X/%X mit ungültigem angegebenen Block %d nicht wiederherstellen" -#: access/transam/xlogreader.c:2073 +#: access/transam/xlogreader.c:2083 #, c-format msgid "could not restore image at %X/%X with invalid state, block %d" msgstr "konnte Abbild mit ungültigem Zustand bei %X/%X nicht wiederherstellen, Block %d" -#: access/transam/xlogreader.c:2100 access/transam/xlogreader.c:2117 +#: access/transam/xlogreader.c:2110 access/transam/xlogreader.c:2127 #, c-format msgid "could not restore image at %X/%X compressed with %s not supported by build, block %d" msgstr "konnte Abbild bei %X/%X nicht wiederherstellen, komprimiert mit %s, nicht unterstützt von dieser Installation, Block %d" -#: access/transam/xlogreader.c:2126 +#: access/transam/xlogreader.c:2136 #, c-format msgid "could not restore image at %X/%X compressed with unknown method, block %d" msgstr "konnte Abbild bei %X/%X nicht wiederherstellen, komprimiert mit unbekannter Methode, Block %d" -#: access/transam/xlogreader.c:2134 +#: access/transam/xlogreader.c:2144 #, c-format msgid "could not decompress image at %X/%X, block %d" msgstr "konnte Abbild bei %X/%X nicht dekomprimieren, Block %d" @@ -3977,7 +3978,7 @@ msgstr "relativer Pfad nicht erlaubt für auf dem Server abgelegtes Backup" #: backup/basebackup_server.c:102 commands/dbcommands.c:477 #: commands/tablespace.c:157 commands/tablespace.c:173 -#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1986 +#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1982 #: storage/file/copydir.c:47 #, c-format msgid "could not create directory \"%s\": %m" @@ -4218,12 +4219,12 @@ msgstr "Klausel IN SCHEMA kann nicht verwendet werden, wenn GRANT/REVOKE ON SCHE #: commands/tablecmds.c:8376 commands/tablecmds.c:8503 #: commands/tablecmds.c:8533 commands/tablecmds.c:8675 #: commands/tablecmds.c:8768 commands/tablecmds.c:8902 -#: commands/tablecmds.c:9014 commands/tablecmds.c:12851 -#: commands/tablecmds.c:13043 commands/tablecmds.c:13204 -#: commands/tablecmds.c:14393 commands/tablecmds.c:17020 commands/trigger.c:942 +#: commands/tablecmds.c:9014 commands/tablecmds.c:12838 +#: commands/tablecmds.c:13030 commands/tablecmds.c:13191 +#: commands/tablecmds.c:14403 commands/tablecmds.c:17030 commands/trigger.c:943 #: parser/analyze.c:2530 parser/parse_relation.c:737 parser/parse_target.c:1067 #: parser/parse_type.c:144 parser/parse_utilcmd.c:3409 -#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2923 +#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2940 #: utils/adt/ruleutils.c:2812 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist" @@ -4234,15 +4235,15 @@ msgstr "Spalte »%s« von Relation »%s« existiert nicht" msgid "\"%s\" is an index" msgstr "»%s« ist ein Index" -#: catalog/aclchk.c:1869 commands/tablecmds.c:14550 commands/tablecmds.c:17936 +#: catalog/aclchk.c:1869 commands/tablecmds.c:14560 commands/tablecmds.c:17946 #, c-format msgid "\"%s\" is a composite type" msgstr "»%s« ist ein zusammengesetzter Typ" #: catalog/aclchk.c:1877 catalog/objectaddress.c:1363 commands/tablecmds.c:263 -#: commands/tablecmds.c:17900 utils/adt/acl.c:2107 utils/adt/acl.c:2137 -#: utils/adt/acl.c:2170 utils/adt/acl.c:2206 utils/adt/acl.c:2237 -#: utils/adt/acl.c:2268 +#: commands/tablecmds.c:17910 utils/adt/acl.c:2124 utils/adt/acl.c:2154 +#: utils/adt/acl.c:2187 utils/adt/acl.c:2223 utils/adt/acl.c:2254 +#: utils/adt/acl.c:2285 #, c-format msgid "\"%s\" is not a sequence" msgstr "»%s« ist keine Sequenz" @@ -4767,14 +4768,14 @@ msgstr "kann %s nicht löschen, weil andere Objekte davon abhängen" #: catalog/dependency.c:1153 catalog/dependency.c:1160 #: catalog/dependency.c:1171 commands/tablecmds.c:1459 -#: commands/tablecmds.c:15142 commands/tablespace.c:460 commands/user.c:1302 -#: commands/vacuum.c:211 commands/view.c:441 executor/execExprInterp.c:4655 -#: executor/execExprInterp.c:4663 libpq/auth.c:324 +#: commands/tablecmds.c:15152 commands/tablespace.c:460 commands/user.c:1302 +#: commands/vacuum.c:212 commands/view.c:441 executor/execExprInterp.c:4655 +#: executor/execExprInterp.c:4663 libpq/auth.c:332 #: replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1078 #: storage/lmgr/deadlock.c:1134 storage/lmgr/proc.c:1432 utils/misc/guc.c:3169 -#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6825 -#: utils/misc/guc.c:6859 utils/misc/guc.c:6893 utils/misc/guc.c:6936 -#: utils/misc/guc.c:6978 +#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6836 +#: utils/misc/guc.c:6870 utils/misc/guc.c:6904 utils/misc/guc.c:6947 +#: utils/misc/guc.c:6989 #, c-format msgid "%s" msgstr "%s" @@ -4932,7 +4933,7 @@ msgstr "Constraint »%s« wird mit geerbter Definition zusammengeführt" #: catalog/heap.c:2683 catalog/pg_constraint.c:854 commands/tablecmds.c:3074 #: commands/tablecmds.c:3377 commands/tablecmds.c:7089 -#: commands/tablecmds.c:15961 commands/tablecmds.c:16092 +#: commands/tablecmds.c:15971 commands/tablecmds.c:16102 #, c-format msgid "too many inheritance parents" msgstr "zu viele Elterntabellen" @@ -5075,12 +5076,12 @@ msgstr "DROP INDEX CONCURRENTLY muss die erste Aktion in einer Transaktion sein" msgid "cannot reindex temporary tables of other sessions" msgstr "kann temporäre Tabellen anderer Sitzungen nicht reindizieren" -#: catalog/index.c:3679 commands/indexcmds.c:3626 +#: catalog/index.c:3679 commands/indexcmds.c:3660 #, c-format msgid "cannot reindex invalid index on TOAST table" msgstr "ungültiger Index einer TOAST-Tabelle kann nicht reindiziert werden" -#: catalog/index.c:3695 commands/indexcmds.c:3504 commands/indexcmds.c:3650 +#: catalog/index.c:3695 commands/indexcmds.c:3538 commands/indexcmds.c:3684 #: commands/tablecmds.c:3581 #, c-format msgid "cannot move system relation \"%s\"" @@ -5097,7 +5098,7 @@ msgid "cannot reindex invalid index \"%s.%s\" on TOAST table, skipping" msgstr "ungültiger Index »%s.%s« einer TOAST-Tabelle kann nicht reindizert werden, wird übersprungen" #: catalog/namespace.c:462 catalog/namespace.c:666 catalog/namespace.c:758 -#: commands/trigger.c:5729 +#: commands/trigger.c:5815 #, c-format msgid "cross-database references are not implemented: \"%s.%s.%s\"" msgstr "Verweise auf andere Datenbanken sind nicht implementiert: »%s.%s.%s«" @@ -5180,7 +5181,7 @@ msgstr "Textsuchekonfiguration »%s« existiert nicht" msgid "cross-database references are not implemented: %s" msgstr "Verweise auf andere Datenbanken sind nicht implementiert: %s" -#: catalog/namespace.c:3335 gram.y:19181 gram.y:19221 parser/parse_expr.c:875 +#: catalog/namespace.c:3335 gram.y:19188 gram.y:19228 parser/parse_expr.c:875 #: parser/parse_target.c:1266 #, c-format msgid "improper qualified name (too many dotted names): %s" @@ -5234,25 +5235,25 @@ msgstr "während einer parallelen Operation können keine temporären Tabellen e #: catalog/objectaddress.c:1371 commands/policy.c:93 commands/policy.c:373 #: commands/tablecmds.c:257 commands/tablecmds.c:299 commands/tablecmds.c:2327 -#: commands/tablecmds.c:12979 +#: commands/tablecmds.c:12966 #, c-format msgid "\"%s\" is not a table" msgstr "»%s« ist keine Tabelle" #: catalog/objectaddress.c:1378 commands/tablecmds.c:269 -#: commands/tablecmds.c:17905 commands/view.c:114 +#: commands/tablecmds.c:17915 commands/view.c:114 #, c-format msgid "\"%s\" is not a view" msgstr "»%s« ist keine Sicht" #: catalog/objectaddress.c:1385 commands/matview.c:199 commands/tablecmds.c:275 -#: commands/tablecmds.c:17910 +#: commands/tablecmds.c:17920 #, c-format msgid "\"%s\" is not a materialized view" msgstr "»%s« ist keine materialisierte Sicht" #: catalog/objectaddress.c:1392 commands/tablecmds.c:293 -#: commands/tablecmds.c:17915 +#: commands/tablecmds.c:17925 #, c-format msgid "\"%s\" is not a foreign table" msgstr "»%s« ist keine Fremdtabelle" @@ -5275,7 +5276,7 @@ msgstr "Vorgabewert für Spalte »%s« von Relation »%s« existiert nicht" #: catalog/objectaddress.c:1618 commands/functioncmds.c:132 #: commands/tablecmds.c:285 commands/typecmds.c:278 commands/typecmds.c:3843 #: parser/parse_type.c:243 parser/parse_type.c:272 parser/parse_type.c:801 -#: utils/adt/acl.c:4560 +#: utils/adt/acl.c:4577 #, c-format msgid "type \"%s\" does not exist" msgstr "Typ »%s« existiert nicht" @@ -5295,8 +5296,9 @@ msgstr "Funktion %d (%s, %s) von %s existiert nicht" msgid "user mapping for user \"%s\" on server \"%s\" does not exist" msgstr "Benutzerabbildung für Benutzer »%s« auf Server »%s« existiert nicht" -#: catalog/objectaddress.c:1834 commands/foreigncmds.c:430 -#: commands/foreigncmds.c:993 commands/foreigncmds.c:1356 foreign/foreign.c:713 +#: catalog/objectaddress.c:1834 commands/foreigncmds.c:441 +#: commands/foreigncmds.c:1004 commands/foreigncmds.c:1367 +#: foreign/foreign.c:713 #, c-format msgid "server \"%s\" does not exist" msgstr "Server »%s« existiert nicht" @@ -6029,7 +6031,7 @@ msgid "The partition is being detached concurrently or has an unfinished detach. msgstr "Die Partition wird nebenläufig abgetrennt oder hat eine unfertige Abtrennoperation." #: catalog/pg_inherits.c:595 commands/tablecmds.c:4800 -#: commands/tablecmds.c:16207 +#: commands/tablecmds.c:16217 #, c-format msgid "Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation." msgstr "Verwendet Sie ALTER TABLE ... DETACH PARTITION ... FINALIZE, um die unerledigte Abtrennoperation abzuschließen." @@ -6360,12 +6362,12 @@ msgstr "kann Objekte, die %s gehören, nicht löschen, weil sie vom Datenbanksys msgid "cannot reassign ownership of objects owned by %s because they are required by the database system" msgstr "kann den Eigentümer von den Objekten, die %s gehören, nicht ändern, weil die Objekte vom Datenbanksystem benötigt werden" -#: catalog/pg_subscription.c:438 +#: catalog/pg_subscription.c:463 #, c-format msgid "could not drop relation mapping for subscription \"%s\"" msgstr "konnte Relation-Mapping für Subskription »%s« nicht löschen" -#: catalog/pg_subscription.c:440 +#: catalog/pg_subscription.c:465 #, c-format msgid "Table synchronization for relation \"%s\" is in progress and is in state \"%c\"." msgstr "Tabellensynchronisierung für Relation »%s« ist im Gang und hat Status »%c«." @@ -6373,7 +6375,7 @@ msgstr "Tabellensynchronisierung für Relation »%s« ist im Gang und hat Status #. translator: first %s is a SQL ALTER command and second %s is a #. SQL DROP command #. -#: catalog/pg_subscription.c:447 +#: catalog/pg_subscription.c:472 #, c-format msgid "Use %s to enable subscription if not already enabled or use %s to drop the subscription." msgstr "Verwenden Sie %s um die Subskription zu aktivieren, falls noch nicht aktiviert, oder %s um die Subskription zu löschen." @@ -6519,12 +6521,12 @@ msgstr "Parameter »%s« muss READ_ONLY, SHAREABLE oder READ_WRITE sein" msgid "event trigger \"%s\" already exists" msgstr "Ereignistrigger »%s« existiert bereits" -#: commands/alter.c:86 commands/foreigncmds.c:593 +#: commands/alter.c:86 commands/foreigncmds.c:604 #, c-format msgid "foreign-data wrapper \"%s\" already exists" msgstr "Fremddaten-Wrapper »%s« existiert bereits" -#: commands/alter.c:89 commands/foreigncmds.c:884 +#: commands/alter.c:89 commands/foreigncmds.c:895 #, c-format msgid "server \"%s\" already exists" msgstr "Server »%s« existiert bereits" @@ -6625,7 +6627,7 @@ msgid "handler function is not specified" msgstr "keine Handler-Funktion angegeben" #: commands/amcmds.c:264 commands/event_trigger.c:200 -#: commands/foreigncmds.c:489 commands/proclang.c:78 commands/trigger.c:702 +#: commands/foreigncmds.c:500 commands/proclang.c:78 commands/trigger.c:703 #: parser/parse_clause.c:943 #, c-format msgid "function %s must return type %s" @@ -6731,7 +6733,7 @@ msgstr "kann temporäre Tabellen anderer Sitzungen nicht clustern" msgid "there is no previously clustered index for table \"%s\"" msgstr "es gibt keinen bereits geclusterten Index für Tabelle »%s«" -#: commands/cluster.c:191 commands/tablecmds.c:14851 commands/tablecmds.c:16783 +#: commands/cluster.c:191 commands/tablecmds.c:14861 commands/tablecmds.c:16793 #, c-format msgid "index \"%s\" for table \"%s\" does not exist" msgstr "Index »%s« für Tabelle »%s« existiert nicht" @@ -6746,7 +6748,7 @@ msgstr "globaler Katalog kann nicht geclustert werden" msgid "cannot vacuum temporary tables of other sessions" msgstr "temporäre Tabellen anderer Sitzungen können nicht gevacuumt werden" -#: commands/cluster.c:513 commands/tablecmds.c:16793 +#: commands/cluster.c:513 commands/tablecmds.c:16803 #, c-format msgid "\"%s\" is not an index for table \"%s\"" msgstr "»%s« ist kein Index für Tabelle »%s«" @@ -6887,8 +6889,8 @@ msgstr "Version der Standardsortierfolge kann nicht aufgefrischt werden" #. translator: %s is an SQL ALTER command #: commands/collationcmds.c:447 commands/subscriptioncmds.c:1376 #: commands/tablecmds.c:7938 commands/tablecmds.c:7948 -#: commands/tablecmds.c:7950 commands/tablecmds.c:14553 -#: commands/tablecmds.c:17938 commands/tablecmds.c:17959 +#: commands/tablecmds.c:7950 commands/tablecmds.c:14563 +#: commands/tablecmds.c:17948 commands/tablecmds.c:17969 #: commands/typecmds.c:3787 commands/typecmds.c:3872 commands/typecmds.c:4226 #, c-format msgid "Use %s instead." @@ -7165,7 +7167,7 @@ msgstr "Generierte Spalten können nicht in COPY verwendet werden." msgid "column \"%s\" does not exist" msgstr "Spalte »%s« existiert nicht" -#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:951 +#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:952 #: parser/parse_target.c:1083 parser/parse_target.c:1094 #, c-format msgid "column \"%s\" specified more than once" @@ -7997,7 +7999,7 @@ msgstr "Verwenden Sie DROP AGGREGATE, um Aggregatfunktionen zu löschen." #: commands/dropcmds.c:153 commands/sequence.c:462 commands/tablecmds.c:3892 #: commands/tablecmds.c:4050 commands/tablecmds.c:4102 -#: commands/tablecmds.c:17215 tcop/utility.c:1325 +#: commands/tablecmds.c:17225 tcop/utility.c:1325 #, c-format msgid "relation \"%s\" does not exist, skipping" msgstr "Relation »%s« existiert nicht, wird übersprungen" @@ -8122,7 +8124,7 @@ msgstr "Regel »%s« für Relation »%s« existiert nicht, wird übersprungen" msgid "foreign-data wrapper \"%s\" does not exist, skipping" msgstr "Fremddaten-Wrapper »%s« existiert nicht, wird übersprungen" -#: commands/dropcmds.c:448 commands/foreigncmds.c:1360 +#: commands/dropcmds.c:448 commands/foreigncmds.c:1371 #, c-format msgid "server \"%s\" does not exist, skipping" msgstr "Server »%s« existiert nicht, wird übersprungen" @@ -8532,112 +8534,112 @@ msgstr "konnte Multirange-Typ für Datentyp %s nicht finden" msgid "file \"%s\" is too large" msgstr "Datei »%s« ist zu groß" -#: commands/foreigncmds.c:148 commands/foreigncmds.c:157 +#: commands/foreigncmds.c:159 commands/foreigncmds.c:168 #, c-format msgid "option \"%s\" not found" msgstr "Option »%s« nicht gefunden" -#: commands/foreigncmds.c:167 +#: commands/foreigncmds.c:178 #, c-format msgid "option \"%s\" provided more than once" msgstr "Option »%s« mehrmals angegeben" -#: commands/foreigncmds.c:221 commands/foreigncmds.c:229 +#: commands/foreigncmds.c:232 commands/foreigncmds.c:240 #, c-format msgid "permission denied to change owner of foreign-data wrapper \"%s\"" msgstr "keine Berechtigung, um Eigentümer des Fremddaten-Wrappers »%s« zu ändern" -#: commands/foreigncmds.c:223 +#: commands/foreigncmds.c:234 #, c-format msgid "Must be superuser to change owner of a foreign-data wrapper." msgstr "Nur Superuser können den Eigentümer eines Fremddaten-Wrappers ändern." -#: commands/foreigncmds.c:231 +#: commands/foreigncmds.c:242 #, c-format msgid "The owner of a foreign-data wrapper must be a superuser." msgstr "Der Eigentümer eines Fremddaten-Wrappers muss ein Superuser sein." -#: commands/foreigncmds.c:291 commands/foreigncmds.c:707 foreign/foreign.c:691 +#: commands/foreigncmds.c:302 commands/foreigncmds.c:718 foreign/foreign.c:691 #, c-format msgid "foreign-data wrapper \"%s\" does not exist" msgstr "Fremddaten-Wrapper »%s« existiert nicht" -#: commands/foreigncmds.c:325 +#: commands/foreigncmds.c:336 #, c-format msgid "foreign-data wrapper with OID %u does not exist" msgstr "Fremddaten-Wrapper mit OID %u existiert nicht" -#: commands/foreigncmds.c:462 +#: commands/foreigncmds.c:473 #, c-format msgid "foreign server with OID %u does not exist" msgstr "Fremdserver mit OID %u existiert nicht" -#: commands/foreigncmds.c:580 +#: commands/foreigncmds.c:591 #, c-format msgid "permission denied to create foreign-data wrapper \"%s\"" msgstr "keine Berechtigung, um Fremddaten-Wrapper »%s« zu erzeugen" -#: commands/foreigncmds.c:582 +#: commands/foreigncmds.c:593 #, c-format msgid "Must be superuser to create a foreign-data wrapper." msgstr "Nur Superuser können Fremddaten-Wrapper anlegen." -#: commands/foreigncmds.c:697 +#: commands/foreigncmds.c:708 #, c-format msgid "permission denied to alter foreign-data wrapper \"%s\"" msgstr "keine Berechtigung, um Fremddaten-Wrapper »%s« zu ändern" -#: commands/foreigncmds.c:699 +#: commands/foreigncmds.c:710 #, c-format msgid "Must be superuser to alter a foreign-data wrapper." msgstr "Nur Superuser können Fremddaten-Wrapper ändern." -#: commands/foreigncmds.c:730 +#: commands/foreigncmds.c:741 #, c-format msgid "changing the foreign-data wrapper handler can change behavior of existing foreign tables" msgstr "das Ändern des Handlers des Fremddaten-Wrappers kann das Verhalten von bestehenden Fremdtabellen verändern" -#: commands/foreigncmds.c:745 +#: commands/foreigncmds.c:756 #, c-format msgid "changing the foreign-data wrapper validator can cause the options for dependent objects to become invalid" msgstr "durch Ändern des Validators des Fremddaten-Wrappers können die Optionen von abhängigen Objekten ungültig werden" -#: commands/foreigncmds.c:876 +#: commands/foreigncmds.c:887 #, c-format msgid "server \"%s\" already exists, skipping" msgstr "Server »%s« existiert bereits, wird übersprungen" -#: commands/foreigncmds.c:1144 +#: commands/foreigncmds.c:1155 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\", skipping" msgstr "Benutzerabbildung für »%s« existiert bereits für Server »%s«, wird übersprungen" -#: commands/foreigncmds.c:1154 +#: commands/foreigncmds.c:1165 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\"" msgstr "Benutzerabbildung für »%s« existiert bereits für Server »%s«" -#: commands/foreigncmds.c:1254 commands/foreigncmds.c:1374 +#: commands/foreigncmds.c:1265 commands/foreigncmds.c:1385 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\"" msgstr "Benutzerabbildung für »%s« existiert nicht für Server »%s«" -#: commands/foreigncmds.c:1379 +#: commands/foreigncmds.c:1390 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\", skipping" msgstr "Benutzerabbildung für »%s« existiert nicht für Server »%s«, wird übersprungen" -#: commands/foreigncmds.c:1507 foreign/foreign.c:404 +#: commands/foreigncmds.c:1518 foreign/foreign.c:404 #, c-format msgid "foreign-data wrapper \"%s\" has no handler" msgstr "Fremddaten-Wrapper »%s« hat keinen Handler" -#: commands/foreigncmds.c:1513 +#: commands/foreigncmds.c:1524 #, c-format msgid "foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA" msgstr "Fremddaten-Wrapper »%s« unterstützt IMPORT FOREIGN SCHEMA nicht" -#: commands/foreigncmds.c:1615 +#: commands/foreigncmds.c:1626 #, c-format msgid "importing foreign table \"%s\"" msgstr "importiere Fremdtabelle »%s«" @@ -9161,7 +9163,7 @@ msgstr "inkludierte Spalte unterstützt die Optionen NULLS FIRST/LAST nicht" msgid "could not determine which collation to use for index expression" msgstr "konnte die für den Indexausdruck zu verwendende Sortierfolge nicht bestimmen" -#: commands/indexcmds.c:2028 commands/tablecmds.c:18239 commands/typecmds.c:811 +#: commands/indexcmds.c:2028 commands/tablecmds.c:18249 commands/typecmds.c:811 #: parser/parse_expr.c:2793 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 #: utils/adt/misc.c:630 #, c-format @@ -9198,8 +9200,8 @@ msgstr "Zugriffsmethode »%s« unterstützt die Optionen ASC/DESC nicht" msgid "access method \"%s\" does not support NULLS FIRST/LAST options" msgstr "Zugriffsmethode »%s« unterstützt die Optionen NULLS FIRST/LAST nicht" -#: commands/indexcmds.c:2210 commands/tablecmds.c:18264 -#: commands/tablecmds.c:18270 commands/typecmds.c:2311 +#: commands/indexcmds.c:2210 commands/tablecmds.c:18274 +#: commands/tablecmds.c:18280 commands/typecmds.c:2311 #, c-format msgid "data type %s has no default operator class for access method \"%s\"" msgstr "Datentyp %s hat keine Standardoperatorklasse für Zugriffsmethode »%s«" @@ -9225,88 +9227,88 @@ msgstr "Operatorklasse »%s« akzeptiert Datentyp %s nicht" msgid "there are multiple default operator classes for data type %s" msgstr "es gibt mehrere Standardoperatorklassen für Datentyp %s" -#: commands/indexcmds.c:2681 +#: commands/indexcmds.c:2715 #, c-format msgid "unrecognized REINDEX option \"%s\"" msgstr "unbekannte REINDEX-Option »%s«" -#: commands/indexcmds.c:2913 +#: commands/indexcmds.c:2947 #, c-format msgid "table \"%s\" has no indexes that can be reindexed concurrently" msgstr "Tabelle »%s« hat keine Indexe, die nebenläufig reindiziert werden können" -#: commands/indexcmds.c:2927 +#: commands/indexcmds.c:2961 #, c-format msgid "table \"%s\" has no indexes to reindex" msgstr "Tabelle »%s« hat keine zu reindizierenden Indexe" -#: commands/indexcmds.c:2974 commands/indexcmds.c:3485 -#: commands/indexcmds.c:3615 +#: commands/indexcmds.c:3008 commands/indexcmds.c:3519 +#: commands/indexcmds.c:3649 #, c-format msgid "cannot reindex system catalogs concurrently" msgstr "Systemkataloge können nicht nebenläufig reindiziert werden" -#: commands/indexcmds.c:2998 +#: commands/indexcmds.c:3032 #, c-format msgid "can only reindex the currently open database" msgstr "nur die aktuell geöffnete Datenbank kann reindiziert werden" -#: commands/indexcmds.c:3090 +#: commands/indexcmds.c:3124 #, c-format msgid "cannot reindex system catalogs concurrently, skipping all" msgstr "Systemkataloge können nicht nebenläufig reindiziert werden, werden alle übersprungen" -#: commands/indexcmds.c:3123 +#: commands/indexcmds.c:3157 #, c-format msgid "cannot move system relations, skipping all" msgstr "Systemrelationen können nicht verschoben werden, werden alle übersprungen" -#: commands/indexcmds.c:3169 +#: commands/indexcmds.c:3203 #, c-format msgid "while reindexing partitioned table \"%s.%s\"" msgstr "beim Reindizieren der partitionierten Tabelle »%s.%s«" -#: commands/indexcmds.c:3172 +#: commands/indexcmds.c:3206 #, c-format msgid "while reindexing partitioned index \"%s.%s\"" msgstr "beim Reindizieren des partitionierten Index »%s.%s«" -#: commands/indexcmds.c:3365 commands/indexcmds.c:4241 +#: commands/indexcmds.c:3399 commands/indexcmds.c:4283 #, c-format msgid "table \"%s.%s\" was reindexed" msgstr "Tabelle »%s.%s« wurde neu indiziert" -#: commands/indexcmds.c:3517 commands/indexcmds.c:3570 +#: commands/indexcmds.c:3551 commands/indexcmds.c:3604 #, c-format msgid "skipping reindex of invalid index \"%s.%s\"" msgstr "Reindizieren des ungültigen Index »%s.%s« wird übersprungen" -#: commands/indexcmds.c:3520 commands/indexcmds.c:3573 +#: commands/indexcmds.c:3554 commands/indexcmds.c:3607 #, c-format msgid "Use DROP INDEX or REINDEX INDEX." msgstr "Verwenden Sie DROP INDEX oder REINDEX INDEX." -#: commands/indexcmds.c:3524 +#: commands/indexcmds.c:3558 #, c-format msgid "cannot reindex exclusion constraint index \"%s.%s\" concurrently, skipping" msgstr "Exclusion-Constraint-Index »%s.%s« kann nicht nebenläufig reindizert werden, wird übersprungen" -#: commands/indexcmds.c:3680 +#: commands/indexcmds.c:3714 #, c-format msgid "cannot reindex this type of relation concurrently" msgstr "diese Art Relation kann nicht nebenläufig reindiziert werden" -#: commands/indexcmds.c:3698 +#: commands/indexcmds.c:3732 #, c-format msgid "cannot move non-shared relation to tablespace \"%s\"" msgstr "nicht geteilte Relation kann nicht nach Tablespace »%s« verschoben werden" -#: commands/indexcmds.c:4222 commands/indexcmds.c:4234 +#: commands/indexcmds.c:4264 commands/indexcmds.c:4276 #, c-format msgid "index \"%s.%s\" was reindexed" msgstr "Index »%s.%s« wurde neu indiziert" -#: commands/indexcmds.c:4224 commands/indexcmds.c:4243 +#: commands/indexcmds.c:4266 commands/indexcmds.c:4285 #, c-format msgid "%s." msgstr "%s." @@ -9321,7 +9323,7 @@ msgstr "kann Relation »%s« nicht sperren" msgid "CONCURRENTLY cannot be used when the materialized view is not populated" msgstr "CONCURRENTLY kann nicht verwendet werden, wenn die materialisierte Sicht nicht befüllt ist" -#: commands/matview.c:212 gram.y:18918 +#: commands/matview.c:212 gram.y:18925 #, c-format msgid "%s and %s options cannot be used together" msgstr "Optionen %s und %s können nicht zusammen verwendet werden" @@ -9638,9 +9640,9 @@ msgstr "Operator-Attribut »%s« kann nicht geändert werden, wenn es schon gese #: commands/policy.c:86 commands/policy.c:379 commands/statscmds.c:146 #: commands/tablecmds.c:1740 commands/tablecmds.c:2340 #: commands/tablecmds.c:3702 commands/tablecmds.c:6605 -#: commands/tablecmds.c:9670 commands/tablecmds.c:17826 -#: commands/tablecmds.c:17861 commands/trigger.c:316 commands/trigger.c:1332 -#: commands/trigger.c:1442 rewrite/rewriteDefine.c:268 +#: commands/tablecmds.c:9670 commands/tablecmds.c:17836 +#: commands/tablecmds.c:17871 commands/trigger.c:317 commands/trigger.c:1333 +#: commands/trigger.c:1443 rewrite/rewriteDefine.c:268 #: rewrite/rewriteDefine.c:779 rewrite/rewriteRemove.c:74 #, c-format msgid "permission denied: \"%s\" is a system catalog" @@ -9692,7 +9694,7 @@ msgid "cannot create a cursor WITH HOLD within security-restricted operation" msgstr "kann WITH-HOLD-Cursor nicht in einer sicherheitsbeschränkten Operation erzeugen" #: commands/portalcmds.c:189 commands/portalcmds.c:242 -#: executor/execCurrent.c:70 utils/adt/xml.c:2936 utils/adt/xml.c:3106 +#: executor/execCurrent.c:70 utils/adt/xml.c:2938 utils/adt/xml.c:3108 #, c-format msgid "cursor \"%s\" does not exist" msgstr "Cursor »%s« existiert nicht" @@ -10089,8 +10091,8 @@ msgstr "Sequenz muss im selben Schema wie die verknüpfte Tabelle sein" msgid "cannot change ownership of identity sequence" msgstr "kann Eigentümer einer Identitätssequenz nicht ändern" -#: commands/sequence.c:1671 commands/tablecmds.c:14540 -#: commands/tablecmds.c:17235 +#: commands/sequence.c:1671 commands/tablecmds.c:14550 +#: commands/tablecmds.c:17245 #, c-format msgid "Sequence \"%s\" is linked to table \"%s\"." msgstr "Sequenz »%s« ist mit Tabelle »%s« verknüpft." @@ -10227,7 +10229,7 @@ msgid "Only roles with privileges of the \"%s\" role may create subscriptions." msgstr "Nur Rollen mit den Privilegien der Rolle »%s« können Subskriptionen erzeugen." #: commands/subscriptioncmds.c:758 commands/subscriptioncmds.c:891 -#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1345 +#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1371 #: replication/logical/worker.c:4524 #, c-format msgid "could not connect to the publisher: %s" @@ -10357,7 +10359,7 @@ msgstr[1] "Die zu erzeugende Subskription hat Publikationen (%s) abonniert, die msgid "Verify that initial data copied from the publisher tables did not come from other origins." msgstr "Überprüfen Sie, dass die von den publizierten Tabellen kopierten initialen Daten nicht von anderen Origins kamen." -#: commands/subscriptioncmds.c:2226 replication/logical/tablesync.c:906 +#: commands/subscriptioncmds.c:2226 replication/logical/tablesync.c:932 #: replication/pgoutput/pgoutput.c:1143 #, c-format msgid "cannot use different column lists for table \"%s.%s\" in different publications" @@ -10455,7 +10457,7 @@ msgstr "materialisierte Sicht »%s« existiert nicht, wird übersprungen" msgid "Use DROP MATERIALIZED VIEW to remove a materialized view." msgstr "Verwenden Sie DROP MATERIALIZED VIEW, um eine materialisierte Sicht zu löschen." -#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19859 +#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19877 #: parser/parse_utilcmd.c:2251 #, c-format msgid "index \"%s\" does not exist" @@ -10479,8 +10481,8 @@ msgstr "»%s« ist kein Typ" msgid "Use DROP TYPE to remove a type." msgstr "Verwenden Sie DROP TYPE, um einen Typen zu löschen." -#: commands/tablecmds.c:291 commands/tablecmds.c:14379 -#: commands/tablecmds.c:16940 +#: commands/tablecmds.c:291 commands/tablecmds.c:14389 +#: commands/tablecmds.c:16950 #, c-format msgid "foreign table \"%s\" does not exist" msgstr "Fremdtabelle »%s« existiert nicht" @@ -10504,7 +10506,7 @@ msgstr "ON COMMIT kann nur mit temporären Tabellen verwendet werden" msgid "cannot create temporary table within security-restricted operation" msgstr "kann temporäre Tabelle nicht in einer sicherheitsbeschränkten Operation erzeugen" -#: commands/tablecmds.c:801 commands/tablecmds.c:15799 +#: commands/tablecmds.c:801 commands/tablecmds.c:15809 #, c-format msgid "relation \"%s\" would be inherited from more than once" msgstr "von der Relation »%s« würde mehrmals geerbt werden" @@ -10529,7 +10531,7 @@ msgstr "kann keine Fremdpartition der partitionierten Tabelle »%s« erzeugen" msgid "Table \"%s\" contains indexes that are unique." msgstr "Tabelle »%s« enthält Unique Indexe." -#: commands/tablecmds.c:1338 commands/tablecmds.c:13395 +#: commands/tablecmds.c:1338 commands/tablecmds.c:13382 #, c-format msgid "too many array dimensions" msgstr "zu viele Array-Dimensionen" @@ -10580,7 +10582,7 @@ msgstr "kann Fremdtabelle »%s« nicht leeren" msgid "cannot truncate temporary tables of other sessions" msgstr "kann temporäre Tabellen anderer Sitzungen nicht leeren" -#: commands/tablecmds.c:2606 commands/tablecmds.c:15696 +#: commands/tablecmds.c:2606 commands/tablecmds.c:15706 #, c-format msgid "cannot inherit from partitioned table \"%s\"" msgstr "von partitionierter Tabelle »%s« kann nicht geerbt werden" @@ -10601,18 +10603,18 @@ msgstr "geerbte Relation »%s« ist keine Tabelle oder Fremdtabelle" msgid "cannot create a temporary relation as partition of permanent relation \"%s\"" msgstr "eine temporäre Relation kann nicht als Partition der permanenten Relation »%s« erzeugt werden" -#: commands/tablecmds.c:2640 commands/tablecmds.c:15675 +#: commands/tablecmds.c:2640 commands/tablecmds.c:15685 #, c-format msgid "cannot inherit from temporary relation \"%s\"" msgstr "von temporärer Relation »%s« kann nicht geerbt werden" -#: commands/tablecmds.c:2650 commands/tablecmds.c:15683 +#: commands/tablecmds.c:2650 commands/tablecmds.c:15693 #, c-format msgid "cannot inherit from temporary relation of another session" msgstr "von temporärer Relation einer anderen Sitzung kann nicht geerbt werden" #: commands/tablecmds.c:2791 commands/tablecmds.c:2845 -#: commands/tablecmds.c:13078 parser/parse_utilcmd.c:1265 +#: commands/tablecmds.c:13065 parser/parse_utilcmd.c:1265 #: parser/parse_utilcmd.c:1308 parser/parse_utilcmd.c:1735 #: parser/parse_utilcmd.c:1843 #, c-format @@ -10906,12 +10908,12 @@ msgstr "zu einer getypten Tabelle kann keine Spalte hinzugefügt werden" msgid "cannot add column to a partition" msgstr "zu einer Partition kann keine Spalte hinzugefügt werden" -#: commands/tablecmds.c:7072 commands/tablecmds.c:15914 +#: commands/tablecmds.c:7072 commands/tablecmds.c:15924 #, c-format msgid "child table \"%s\" has different type for column \"%s\"" msgstr "abgeleitete Tabelle »%s« hat unterschiedlichen Typ für Spalte »%s«" -#: commands/tablecmds.c:7078 commands/tablecmds.c:15920 +#: commands/tablecmds.c:7078 commands/tablecmds.c:15930 #, c-format msgid "child table \"%s\" has different collation for column \"%s\"" msgstr "abgeleitete Tabelle »%s« hat unterschiedliche Sortierfolge für Spalte »%s«" @@ -10941,7 +10943,7 @@ msgstr "Spalte »%s« von Relation »%s« existiert bereits, wird übersprungen" msgid "column \"%s\" of relation \"%s\" already exists" msgstr "Spalte »%s« von Relation »%s« existiert bereits" -#: commands/tablecmds.c:7546 commands/tablecmds.c:12706 +#: commands/tablecmds.c:7546 commands/tablecmds.c:12693 #, c-format msgid "cannot remove constraint from only the partitioned table when partitions exist" msgstr "Constraint kann nicht nur von der partitionierten Tabelle entfernt werden, wenn Partitionen existieren" @@ -10949,7 +10951,7 @@ msgstr "Constraint kann nicht nur von der partitionierten Tabelle entfernt werde #: commands/tablecmds.c:7547 commands/tablecmds.c:7861 #: commands/tablecmds.c:8039 commands/tablecmds.c:8146 #: commands/tablecmds.c:8263 commands/tablecmds.c:9082 -#: commands/tablecmds.c:12707 +#: commands/tablecmds.c:12694 #, c-format msgid "Do not specify the ONLY keyword." msgstr "Lassen Sie das Schlüsselwort ONLY weg." @@ -10959,8 +10961,8 @@ msgstr "Lassen Sie das Schlüsselwort ONLY weg." #: commands/tablecmds.c:8190 commands/tablecmds.c:8284 #: commands/tablecmds.c:8385 commands/tablecmds.c:8542 #: commands/tablecmds.c:8695 commands/tablecmds.c:8776 -#: commands/tablecmds.c:8910 commands/tablecmds.c:12860 -#: commands/tablecmds.c:14402 commands/tablecmds.c:17029 +#: commands/tablecmds.c:8910 commands/tablecmds.c:12847 +#: commands/tablecmds.c:14412 commands/tablecmds.c:17039 #, c-format msgid "cannot alter system column \"%s\"" msgstr "Systemspalte »%s« kann nicht geändert werden" @@ -11210,696 +11212,696 @@ msgstr "Schlüsselspalten »%s« und »%s« haben inkompatible Typen: %s und %s. msgid "column \"%s\" referenced in ON DELETE SET action must be part of foreign key" msgstr "Spalte »%s«, auf die in der ON-DELETE-SET-Aktion verwiesen wird, muss Teil des Fremdschlüssels sein" -#: commands/tablecmds.c:10446 commands/tablecmds.c:10886 +#: commands/tablecmds.c:10446 commands/tablecmds.c:10873 #: parser/parse_utilcmd.c:822 parser/parse_utilcmd.c:945 #, c-format msgid "foreign key constraints are not supported on foreign tables" msgstr "Fremdschlüssel-Constraints auf Fremdtabellen werden nicht unterstützt" -#: commands/tablecmds.c:10869 +#: commands/tablecmds.c:10856 #, c-format msgid "cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"" msgstr "kann Tabelle »%s« nicht als Partition anfügen, weil auf sie von Fremdschlüssel »%s« verwiesen wird" -#: commands/tablecmds.c:11470 commands/tablecmds.c:11751 -#: commands/tablecmds.c:12663 commands/tablecmds.c:12737 +#: commands/tablecmds.c:11457 commands/tablecmds.c:11738 +#: commands/tablecmds.c:12650 commands/tablecmds.c:12724 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist" msgstr "Constraint »%s« von Relation »%s« existiert nicht" -#: commands/tablecmds.c:11477 +#: commands/tablecmds.c:11464 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key constraint" msgstr "Constraint »%s« von Relation »%s« ist kein Fremdschlüssel-Constraint" -#: commands/tablecmds.c:11515 +#: commands/tablecmds.c:11502 #, c-format msgid "cannot alter constraint \"%s\" on relation \"%s\"" msgstr "Constraint »%s« von Relation »%s« kann nicht geändert werden" -#: commands/tablecmds.c:11518 +#: commands/tablecmds.c:11505 #, c-format msgid "Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\"." msgstr "Constraint »%s« ist von Constraint »%s« von Relation »%s« abgeleitet." -#: commands/tablecmds.c:11520 +#: commands/tablecmds.c:11507 #, c-format msgid "You may alter the constraint it derives from instead." msgstr "Sie können stattdessen den Constraint, von dem er abgeleitet ist, ändern." -#: commands/tablecmds.c:11759 +#: commands/tablecmds.c:11746 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint" msgstr "Constraint »%s« von Relation »%s« ist kein Fremdschlüssel- oder Check-Constraint" -#: commands/tablecmds.c:11836 +#: commands/tablecmds.c:11823 #, c-format msgid "constraint must be validated on child tables too" msgstr "Constraint muss ebenso in den abgeleiteten Tabellen validiert werden" -#: commands/tablecmds.c:11923 +#: commands/tablecmds.c:11910 #, c-format msgid "column \"%s\" referenced in foreign key constraint does not exist" msgstr "Spalte »%s«, die im Fremdschlüssel verwendet wird, existiert nicht" -#: commands/tablecmds.c:11929 +#: commands/tablecmds.c:11916 #, c-format msgid "system columns cannot be used in foreign keys" msgstr "Systemspalten können nicht in Fremdschlüsseln verwendet werden" -#: commands/tablecmds.c:11933 +#: commands/tablecmds.c:11920 #, c-format msgid "cannot have more than %d keys in a foreign key" msgstr "Fremdschlüssel kann nicht mehr als %d Schlüssel haben" -#: commands/tablecmds.c:11998 +#: commands/tablecmds.c:11985 #, c-format msgid "cannot use a deferrable primary key for referenced table \"%s\"" msgstr "aufschiebbarer Primärschlüssel kann nicht für Tabelle »%s«, auf die verwiesen wird, verwendet werden" -#: commands/tablecmds.c:12015 +#: commands/tablecmds.c:12002 #, c-format msgid "there is no primary key for referenced table \"%s\"" msgstr "in Tabelle »%s«, auf die verwiesen wird, gibt es keinen Primärschlüssel" -#: commands/tablecmds.c:12083 +#: commands/tablecmds.c:12070 #, c-format msgid "foreign key referenced-columns list must not contain duplicates" msgstr "die Liste der Spalten, auf die ein Fremdschlüssel verweist, darf keine doppelten Einträge enthalten" -#: commands/tablecmds.c:12175 +#: commands/tablecmds.c:12162 #, c-format msgid "cannot use a deferrable unique constraint for referenced table \"%s\"" msgstr "aufschiebbarer Unique-Constraint kann nicht für Tabelle »%s«, auf die verwiesen wird, verwendet werden" -#: commands/tablecmds.c:12180 +#: commands/tablecmds.c:12167 #, c-format msgid "there is no unique constraint matching given keys for referenced table \"%s\"" msgstr "in Tabelle »%s«, auf die verwiesen wird, gibt es keinen Unique-Constraint, der auf die angegebenen Schlüssel passt" -#: commands/tablecmds.c:12619 +#: commands/tablecmds.c:12606 #, c-format msgid "cannot drop inherited constraint \"%s\" of relation \"%s\"" msgstr "geerbter Constraint »%s« von Relation »%s« kann nicht gelöscht werden" -#: commands/tablecmds.c:12669 +#: commands/tablecmds.c:12656 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist, skipping" msgstr "Constraint »%s« von Relation »%s« existiert nicht, wird übersprungen" -#: commands/tablecmds.c:12844 +#: commands/tablecmds.c:12831 #, c-format msgid "cannot alter column type of typed table" msgstr "Spaltentyp einer getypten Tabelle kann nicht geändert werden" -#: commands/tablecmds.c:12870 +#: commands/tablecmds.c:12857 #, c-format msgid "cannot specify USING when altering type of generated column" msgstr "USING kann nicht angegeben werden, wenn der Typ einer generierten Spalte geändert wird" -#: commands/tablecmds.c:12871 commands/tablecmds.c:18082 -#: commands/tablecmds.c:18172 commands/trigger.c:656 +#: commands/tablecmds.c:12858 commands/tablecmds.c:18092 +#: commands/tablecmds.c:18182 commands/trigger.c:657 #: rewrite/rewriteHandler.c:941 rewrite/rewriteHandler.c:976 #, c-format msgid "Column \"%s\" is a generated column." msgstr "Spalte »%s« ist eine generierte Spalte." -#: commands/tablecmds.c:12881 +#: commands/tablecmds.c:12868 #, c-format msgid "cannot alter inherited column \"%s\"" msgstr "kann vererbte Spalte »%s« nicht ändern" -#: commands/tablecmds.c:12890 +#: commands/tablecmds.c:12877 #, c-format msgid "cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"" msgstr "Spalte »%s« kann nicht geändert werden, weil sie Teil des Partitionierungsschlüssels von Relation »%s« ist" -#: commands/tablecmds.c:12940 +#: commands/tablecmds.c:12927 #, c-format msgid "result of USING clause for column \"%s\" cannot be cast automatically to type %s" msgstr "Ergebnis der USING-Klausel für Spalte »%s« kann nicht automatisch in Typ %s umgewandelt werden" -#: commands/tablecmds.c:12943 +#: commands/tablecmds.c:12930 #, c-format msgid "You might need to add an explicit cast." msgstr "Sie müssen möglicherweise eine ausdrückliche Typumwandlung hinzufügen." -#: commands/tablecmds.c:12947 +#: commands/tablecmds.c:12934 #, c-format msgid "column \"%s\" cannot be cast automatically to type %s" msgstr "Spalte »%s« kann nicht automatisch in Typ %s umgewandelt werden" #. translator: USING is SQL, don't translate it -#: commands/tablecmds.c:12951 +#: commands/tablecmds.c:12938 #, c-format msgid "You might need to specify \"USING %s::%s\"." msgstr "Sie müssen möglicherweise »USING %s::%s« angeben." -#: commands/tablecmds.c:13050 +#: commands/tablecmds.c:13037 #, c-format msgid "cannot alter inherited column \"%s\" of relation \"%s\"" msgstr "geerbte Spalte »%s« von Relation »%s« kann nicht geändert werden" -#: commands/tablecmds.c:13079 +#: commands/tablecmds.c:13066 #, c-format msgid "USING expression contains a whole-row table reference." msgstr "USING-Ausdruck enthält einen Verweis auf die ganze Zeile der Tabelle." -#: commands/tablecmds.c:13090 +#: commands/tablecmds.c:13077 #, c-format msgid "type of inherited column \"%s\" must be changed in child tables too" msgstr "Typ der vererbten Spalte »%s« muss ebenso in den abgeleiteten Tabellen geändert werden" -#: commands/tablecmds.c:13215 +#: commands/tablecmds.c:13202 #, c-format msgid "cannot alter type of column \"%s\" twice" msgstr "Typ der Spalte »%s« kann nicht zweimal geändert werden" -#: commands/tablecmds.c:13253 +#: commands/tablecmds.c:13240 #, c-format msgid "generation expression for column \"%s\" cannot be cast automatically to type %s" msgstr "Generierungsausdruck der Spalte »%s« kann nicht automatisch in Typ %s umgewandelt werden" -#: commands/tablecmds.c:13258 +#: commands/tablecmds.c:13245 #, c-format msgid "default for column \"%s\" cannot be cast automatically to type %s" msgstr "Vorgabewert der Spalte »%s« kann nicht automatisch in Typ %s umgewandelt werden" -#: commands/tablecmds.c:13562 +#: commands/tablecmds.c:13549 #, c-format msgid "cannot alter type of a column used by a function or procedure" msgstr "Typ einer Spalte, die von einer Funktion oder Prozedur verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:13563 commands/tablecmds.c:13578 -#: commands/tablecmds.c:13598 commands/tablecmds.c:13617 -#: commands/tablecmds.c:13676 +#: commands/tablecmds.c:13550 commands/tablecmds.c:13565 +#: commands/tablecmds.c:13585 commands/tablecmds.c:13604 +#: commands/tablecmds.c:13663 #, c-format msgid "%s depends on column \"%s\"" msgstr "%s hängt von Spalte »%s« ab" -#: commands/tablecmds.c:13577 +#: commands/tablecmds.c:13564 #, c-format msgid "cannot alter type of a column used by a view or rule" msgstr "Typ einer Spalte, die von einer Sicht oder Regel verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:13597 +#: commands/tablecmds.c:13584 #, c-format msgid "cannot alter type of a column used in a trigger definition" msgstr "Typ einer Spalte, die in einer Trigger-Definition verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:13616 +#: commands/tablecmds.c:13603 #, c-format msgid "cannot alter type of a column used in a policy definition" msgstr "Typ einer Spalte, die in einer Policy-Definition verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:13647 +#: commands/tablecmds.c:13634 #, c-format msgid "cannot alter type of a column used by a generated column" msgstr "Typ einer Spalte, die von einer generierten Spalte verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:13648 +#: commands/tablecmds.c:13635 #, c-format msgid "Column \"%s\" is used by generated column \"%s\"." msgstr "Spalte »%s« wird von generierter Spalte »%s« verwendet." -#: commands/tablecmds.c:13675 +#: commands/tablecmds.c:13662 #, c-format msgid "cannot alter type of a column used by a publication WHERE clause" msgstr "Typ einer Spalte, die in der WHERE-Klausel einer Publikation verwendet wird, kann nicht geändert werden" -#: commands/tablecmds.c:14510 commands/tablecmds.c:14522 +#: commands/tablecmds.c:14520 commands/tablecmds.c:14532 #, c-format msgid "cannot change owner of index \"%s\"" msgstr "kann Eigentümer des Index »%s« nicht ändern" -#: commands/tablecmds.c:14512 commands/tablecmds.c:14524 +#: commands/tablecmds.c:14522 commands/tablecmds.c:14534 #, c-format msgid "Change the ownership of the index's table instead." msgstr "Ändern Sie stattdessen den Eigentümer der Tabelle des Index." -#: commands/tablecmds.c:14538 +#: commands/tablecmds.c:14548 #, c-format msgid "cannot change owner of sequence \"%s\"" msgstr "kann Eigentümer der Sequenz »%s« nicht ändern" -#: commands/tablecmds.c:14563 +#: commands/tablecmds.c:14573 #, c-format msgid "cannot change owner of relation \"%s\"" msgstr "kann Eigentümer der Relation »%s« nicht ändern" -#: commands/tablecmds.c:15030 +#: commands/tablecmds.c:15040 #, c-format msgid "cannot have multiple SET TABLESPACE subcommands" msgstr "mehrere SET TABLESPACE Unterbefehle sind ungültig" -#: commands/tablecmds.c:15107 +#: commands/tablecmds.c:15117 #, c-format msgid "cannot set options for relation \"%s\"" msgstr "für Relation »%s« können keine Optionen gesetzt werden" -#: commands/tablecmds.c:15141 commands/view.c:440 +#: commands/tablecmds.c:15151 commands/view.c:440 #, c-format msgid "WITH CHECK OPTION is supported only on automatically updatable views" msgstr "WITH CHECK OPTION wird nur für automatisch aktualisierbare Sichten unterstützt" -#: commands/tablecmds.c:15392 +#: commands/tablecmds.c:15402 #, c-format msgid "only tables, indexes, and materialized views exist in tablespaces" msgstr "nur Tabellen, Indexe und materialisierte Sichten existieren in Tablespaces" -#: commands/tablecmds.c:15404 +#: commands/tablecmds.c:15414 #, c-format msgid "cannot move relations in to or out of pg_global tablespace" msgstr "Relationen können nicht in den oder aus dem Tablespace »pg_global« verschoben werden" -#: commands/tablecmds.c:15496 +#: commands/tablecmds.c:15506 #, c-format msgid "aborting because lock on relation \"%s.%s\" is not available" msgstr "Abbruch weil Sperre für Relation »%s.%s« nicht verfügbar ist" -#: commands/tablecmds.c:15512 +#: commands/tablecmds.c:15522 #, c-format msgid "no matching relations in tablespace \"%s\" found" msgstr "keine passenden Relationen in Tablespace »%s« gefunden" -#: commands/tablecmds.c:15634 +#: commands/tablecmds.c:15644 #, c-format msgid "cannot change inheritance of typed table" msgstr "Vererbung einer getypten Tabelle kann nicht geändert werden" -#: commands/tablecmds.c:15639 commands/tablecmds.c:16139 +#: commands/tablecmds.c:15649 commands/tablecmds.c:16149 #, c-format msgid "cannot change inheritance of a partition" msgstr "Vererbung einer Partition kann nicht geändert werden" -#: commands/tablecmds.c:15644 +#: commands/tablecmds.c:15654 #, c-format msgid "cannot change inheritance of partitioned table" msgstr "Vererbung einer partitionierten Tabelle kann nicht geändert werden" -#: commands/tablecmds.c:15690 +#: commands/tablecmds.c:15700 #, c-format msgid "cannot inherit to temporary relation of another session" msgstr "an temporäre Relation einer anderen Sitzung kann nicht vererbt werden" -#: commands/tablecmds.c:15703 +#: commands/tablecmds.c:15713 #, c-format msgid "cannot inherit from a partition" msgstr "von einer Partition kann nicht geerbt werden" -#: commands/tablecmds.c:15725 commands/tablecmds.c:18583 +#: commands/tablecmds.c:15735 commands/tablecmds.c:18593 #, c-format msgid "circular inheritance not allowed" msgstr "zirkuläre Vererbung ist nicht erlaubt" -#: commands/tablecmds.c:15726 commands/tablecmds.c:18584 +#: commands/tablecmds.c:15736 commands/tablecmds.c:18594 #, c-format msgid "\"%s\" is already a child of \"%s\"." msgstr "»%s« ist schon von »%s« abgeleitet." -#: commands/tablecmds.c:15739 +#: commands/tablecmds.c:15749 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming an inheritance child" msgstr "Trigger »%s« verhindert, dass Tabelle »%s« ein Vererbungskind werden kann" -#: commands/tablecmds.c:15741 +#: commands/tablecmds.c:15751 #, c-format msgid "ROW triggers with transition tables are not supported in inheritance hierarchies." msgstr "ROW-Trigger mit Übergangstabellen werden in Vererbungshierarchien nicht unterstützt." -#: commands/tablecmds.c:15930 +#: commands/tablecmds.c:15940 #, c-format msgid "column \"%s\" in child table must be marked NOT NULL" msgstr "Spalte »%s« in abgeleiteter Tabelle muss als NOT NULL markiert sein" -#: commands/tablecmds.c:15939 +#: commands/tablecmds.c:15949 #, c-format msgid "column \"%s\" in child table must be a generated column" msgstr "Spalte »%s« in abgeleiteter Tabelle muss eine generierte Spalte sein" -#: commands/tablecmds.c:15943 +#: commands/tablecmds.c:15953 #, c-format msgid "column \"%s\" in child table must not be a generated column" msgstr "Spalte »%s« in abgeleiteter Tabelle darf keine generierte Spalte sein" -#: commands/tablecmds.c:15981 +#: commands/tablecmds.c:15991 #, c-format msgid "child table is missing column \"%s\"" msgstr "Spalte »%s« fehlt in abgeleiteter Tabelle" -#: commands/tablecmds.c:16062 +#: commands/tablecmds.c:16072 #, c-format msgid "child table \"%s\" has different definition for check constraint \"%s\"" msgstr "abgeleitete Tabelle »%s« hat unterschiedliche Definition für Check-Constraint »%s«" -#: commands/tablecmds.c:16069 +#: commands/tablecmds.c:16079 #, c-format msgid "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"" msgstr "Constraint »%s« kollidiert mit nicht vererbtem Constraint für abgeleitete Tabelle »%s«" -#: commands/tablecmds.c:16079 +#: commands/tablecmds.c:16089 #, c-format msgid "constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"" msgstr "Constraint »%s« kollidiert mit NOT-VALID-Constraint für abgeleitete Tabelle »%s«" -#: commands/tablecmds.c:16117 +#: commands/tablecmds.c:16127 #, c-format msgid "child table is missing constraint \"%s\"" msgstr "Constraint »%s« fehlt in abgeleiteter Tabelle" -#: commands/tablecmds.c:16203 +#: commands/tablecmds.c:16213 #, c-format msgid "partition \"%s\" already pending detach in partitioned table \"%s.%s\"" msgstr "Partition »%s« hat schon eine unerledigte Abtrennoperation in der partitionierten Tabelle »%s.%s«" -#: commands/tablecmds.c:16232 commands/tablecmds.c:16278 +#: commands/tablecmds.c:16242 commands/tablecmds.c:16288 #, c-format msgid "relation \"%s\" is not a partition of relation \"%s\"" msgstr "Relation »%s« ist keine Partition von Relation »%s«" -#: commands/tablecmds.c:16284 +#: commands/tablecmds.c:16294 #, c-format msgid "relation \"%s\" is not a parent of relation \"%s\"" msgstr "Relation »%s« ist keine Basisrelation von Relation »%s«" -#: commands/tablecmds.c:16511 +#: commands/tablecmds.c:16521 #, c-format msgid "typed tables cannot inherit" msgstr "getypte Tabellen können nicht erben" -#: commands/tablecmds.c:16541 +#: commands/tablecmds.c:16551 #, c-format msgid "table is missing column \"%s\"" msgstr "Spalte »%s« fehlt in Tabelle" -#: commands/tablecmds.c:16552 +#: commands/tablecmds.c:16562 #, c-format msgid "table has column \"%s\" where type requires \"%s\"" msgstr "Tabelle hat Spalte »%s«, aber Typ benötigt »%s«" -#: commands/tablecmds.c:16561 +#: commands/tablecmds.c:16571 #, c-format msgid "table \"%s\" has different type for column \"%s\"" msgstr "Tabelle »%s« hat unterschiedlichen Typ für Spalte »%s«" -#: commands/tablecmds.c:16575 +#: commands/tablecmds.c:16585 #, c-format msgid "table has extra column \"%s\"" msgstr "Tabelle hat zusätzliche Spalte »%s«" -#: commands/tablecmds.c:16627 +#: commands/tablecmds.c:16637 #, c-format msgid "\"%s\" is not a typed table" msgstr "»%s« ist keine getypte Tabelle" -#: commands/tablecmds.c:16801 +#: commands/tablecmds.c:16811 #, c-format msgid "cannot use non-unique index \"%s\" as replica identity" msgstr "nicht eindeutiger Index »%s« kann nicht als Replik-Identität verwendet werden" -#: commands/tablecmds.c:16807 +#: commands/tablecmds.c:16817 #, c-format msgid "cannot use non-immediate index \"%s\" as replica identity" msgstr "Index »%s« kann nicht als Replik-Identität verwendet werden, weil er nicht IMMEDIATE ist" -#: commands/tablecmds.c:16813 +#: commands/tablecmds.c:16823 #, c-format msgid "cannot use expression index \"%s\" as replica identity" msgstr "Ausdrucksindex »%s« kann nicht als Replik-Identität verwendet werden" -#: commands/tablecmds.c:16819 +#: commands/tablecmds.c:16829 #, c-format msgid "cannot use partial index \"%s\" as replica identity" msgstr "partieller Index »%s« kann nicht als Replik-Identität verwendet werden" -#: commands/tablecmds.c:16836 +#: commands/tablecmds.c:16846 #, c-format msgid "index \"%s\" cannot be used as replica identity because column %d is a system column" msgstr "Index »%s« kann nicht als Replik-Identität verwendet werden, weil Spalte %d eine Systemspalte ist" -#: commands/tablecmds.c:16843 +#: commands/tablecmds.c:16853 #, c-format msgid "index \"%s\" cannot be used as replica identity because column \"%s\" is nullable" msgstr "Index »%s« kann nicht als Replik-Identität verwendet werden, weil Spalte »%s« NULL-Werte akzeptiert" -#: commands/tablecmds.c:17095 +#: commands/tablecmds.c:17105 #, c-format msgid "cannot change logged status of table \"%s\" because it is temporary" msgstr "kann den geloggten Status der Tabelle »%s« nicht ändern, weil sie temporär ist" -#: commands/tablecmds.c:17119 +#: commands/tablecmds.c:17129 #, c-format msgid "cannot change table \"%s\" to unlogged because it is part of a publication" msgstr "kann Tabelle »%s« nicht in ungeloggt ändern, weil sie Teil einer Publikation ist" -#: commands/tablecmds.c:17121 +#: commands/tablecmds.c:17131 #, c-format msgid "Unlogged relations cannot be replicated." msgstr "Ungeloggte Relationen können nicht repliziert werden." -#: commands/tablecmds.c:17166 +#: commands/tablecmds.c:17176 #, c-format msgid "could not change table \"%s\" to logged because it references unlogged table \"%s\"" msgstr "konnte Tabelle »%s« nicht in geloggt ändern, weil sie auf die ungeloggte Tabelle »%s« verweist" -#: commands/tablecmds.c:17176 +#: commands/tablecmds.c:17186 #, c-format msgid "could not change table \"%s\" to unlogged because it references logged table \"%s\"" msgstr "konnte Tabelle »%s« nicht in ungeloggt ändern, weil sie auf die geloggte Tabelle »%s« verweist" -#: commands/tablecmds.c:17234 +#: commands/tablecmds.c:17244 #, c-format msgid "cannot move an owned sequence into another schema" msgstr "einer Tabelle zugeordnete Sequenz kann nicht in ein anderes Schema verschoben werden" -#: commands/tablecmds.c:17342 +#: commands/tablecmds.c:17352 #, c-format msgid "relation \"%s\" already exists in schema \"%s\"" msgstr "Relation »%s« existiert bereits in Schema »%s«" -#: commands/tablecmds.c:17767 +#: commands/tablecmds.c:17777 #, c-format msgid "\"%s\" is not a table or materialized view" msgstr "»%s« ist keine Tabelle oder materialisierte Sicht" -#: commands/tablecmds.c:17920 +#: commands/tablecmds.c:17930 #, c-format msgid "\"%s\" is not a composite type" msgstr "»%s« ist kein zusammengesetzter Typ" -#: commands/tablecmds.c:17950 +#: commands/tablecmds.c:17960 #, c-format msgid "cannot change schema of index \"%s\"" msgstr "kann Schema des Index »%s« nicht ändern" -#: commands/tablecmds.c:17952 commands/tablecmds.c:17966 +#: commands/tablecmds.c:17962 commands/tablecmds.c:17976 #, c-format msgid "Change the schema of the table instead." msgstr "Ändern Sie stattdessen das Schema der Tabelle." -#: commands/tablecmds.c:17956 +#: commands/tablecmds.c:17966 #, c-format msgid "cannot change schema of composite type \"%s\"" msgstr "kann Schema des zusammengesetzten Typs »%s« nicht ändern" -#: commands/tablecmds.c:17964 +#: commands/tablecmds.c:17974 #, c-format msgid "cannot change schema of TOAST table \"%s\"" msgstr "kann Schema der TOAST-Tabelle »%s« nicht ändern" -#: commands/tablecmds.c:17996 +#: commands/tablecmds.c:18006 #, c-format msgid "cannot use \"list\" partition strategy with more than one column" msgstr "Partitionierungsstrategie »list« kann nicht mit mehr als einer Spalte verwendet werden" -#: commands/tablecmds.c:18062 +#: commands/tablecmds.c:18072 #, c-format msgid "column \"%s\" named in partition key does not exist" msgstr "Spalte »%s«, die im Partitionierungsschlüssel verwendet wird, existiert nicht" -#: commands/tablecmds.c:18070 +#: commands/tablecmds.c:18080 #, c-format msgid "cannot use system column \"%s\" in partition key" msgstr "Systemspalte »%s« kann nicht im Partitionierungsschlüssel verwendet werden" -#: commands/tablecmds.c:18081 commands/tablecmds.c:18171 +#: commands/tablecmds.c:18091 commands/tablecmds.c:18181 #, c-format msgid "cannot use generated column in partition key" msgstr "generierte Spalte kann nicht im Partitionierungsschlüssel verwendet werden" -#: commands/tablecmds.c:18154 +#: commands/tablecmds.c:18164 #, c-format msgid "partition key expressions cannot contain system column references" msgstr "Partitionierungsschlüsselausdruck kann nicht auf Systemspalten verweisen" -#: commands/tablecmds.c:18201 +#: commands/tablecmds.c:18211 #, c-format msgid "functions in partition key expression must be marked IMMUTABLE" msgstr "Funktionen im Partitionierungsschlüsselausdruck müssen als IMMUTABLE markiert sein" -#: commands/tablecmds.c:18210 +#: commands/tablecmds.c:18220 #, c-format msgid "cannot use constant expression as partition key" msgstr "Partitionierungsschlüssel kann kein konstanter Ausdruck sein" -#: commands/tablecmds.c:18231 +#: commands/tablecmds.c:18241 #, c-format msgid "could not determine which collation to use for partition expression" msgstr "konnte die für den Partitionierungsausdruck zu verwendende Sortierfolge nicht bestimmen" -#: commands/tablecmds.c:18266 +#: commands/tablecmds.c:18276 #, c-format msgid "You must specify a hash operator class or define a default hash operator class for the data type." msgstr "Sie müssen eine hash-Operatorklasse angeben oder eine hash-Standardoperatorklasse für den Datentyp definieren." -#: commands/tablecmds.c:18272 +#: commands/tablecmds.c:18282 #, c-format msgid "You must specify a btree operator class or define a default btree operator class for the data type." msgstr "Sie müssen eine btree-Operatorklasse angeben oder eine btree-Standardoperatorklasse für den Datentyp definieren." -#: commands/tablecmds.c:18523 +#: commands/tablecmds.c:18533 #, c-format msgid "\"%s\" is already a partition" msgstr "»%s« ist bereits eine Partition" -#: commands/tablecmds.c:18529 +#: commands/tablecmds.c:18539 #, c-format msgid "cannot attach a typed table as partition" msgstr "eine getypte Tabelle kann nicht als Partition angefügt werden" -#: commands/tablecmds.c:18545 +#: commands/tablecmds.c:18555 #, c-format msgid "cannot attach inheritance child as partition" msgstr "ein Vererbungskind kann nicht als Partition angefügt werden" -#: commands/tablecmds.c:18559 +#: commands/tablecmds.c:18569 #, c-format msgid "cannot attach inheritance parent as partition" msgstr "eine Tabelle mit abgeleiteten Tabellen kann nicht als Partition angefügt werden" -#: commands/tablecmds.c:18593 +#: commands/tablecmds.c:18603 #, c-format msgid "cannot attach a temporary relation as partition of permanent relation \"%s\"" msgstr "eine temporäre Relation kann nicht als Partition an permanente Relation »%s« angefügt werden" -#: commands/tablecmds.c:18601 +#: commands/tablecmds.c:18611 #, c-format msgid "cannot attach a permanent relation as partition of temporary relation \"%s\"" msgstr "eine permanente Relation kann nicht als Partition an temporäre Relation »%s« angefügt werden" -#: commands/tablecmds.c:18609 +#: commands/tablecmds.c:18619 #, c-format msgid "cannot attach as partition of temporary relation of another session" msgstr "kann nicht als Partition an temporäre Relation einer anderen Sitzung anfügen" -#: commands/tablecmds.c:18616 +#: commands/tablecmds.c:18626 #, c-format msgid "cannot attach temporary relation of another session as partition" msgstr "temporäre Relation einer anderen Sitzung kann nicht als Partition angefügt werden" -#: commands/tablecmds.c:18636 +#: commands/tablecmds.c:18646 #, c-format msgid "table \"%s\" being attached contains an identity column \"%s\"" msgstr "anzufügende Tabelle »%s« enthält eine Identitätsspalte »%s«" -#: commands/tablecmds.c:18638 +#: commands/tablecmds.c:18648 #, c-format msgid "The new partition may not contain an identity column." msgstr "Die neue Partition darf keine Identitätsspalte enthalten." -#: commands/tablecmds.c:18646 +#: commands/tablecmds.c:18656 #, c-format msgid "table \"%s\" contains column \"%s\" not found in parent \"%s\"" msgstr "Tabelle »%s« enthält Spalte »%s«, die nicht in der Elterntabelle »%s« gefunden wurde" -#: commands/tablecmds.c:18649 +#: commands/tablecmds.c:18659 #, c-format msgid "The new partition may contain only the columns present in parent." msgstr "Die neue Partition darf nur Spalten enthalten, die auch die Elterntabelle hat." -#: commands/tablecmds.c:18661 +#: commands/tablecmds.c:18671 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming a partition" msgstr "Trigger »%s« verhindert, dass Tabelle »%s« eine Partition werden kann" -#: commands/tablecmds.c:18663 +#: commands/tablecmds.c:18673 #, c-format msgid "ROW triggers with transition tables are not supported on partitions." msgstr "ROW-Trigger mit Übergangstabellen werden für Partitionen nicht unterstützt." -#: commands/tablecmds.c:18839 +#: commands/tablecmds.c:18849 #, c-format msgid "cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"" msgstr "kann Fremdtabelle »%s« nicht als Partition an partitionierte Tabelle »%s« anfügen" -#: commands/tablecmds.c:18842 +#: commands/tablecmds.c:18852 #, c-format msgid "Partitioned table \"%s\" contains unique indexes." msgstr "Partitionierte Tabelle »%s« enthält Unique-Indexe." -#: commands/tablecmds.c:19164 +#: commands/tablecmds.c:19174 #, c-format msgid "cannot detach partitions concurrently when a default partition exists" msgstr "nebenläufiges Abtrennen einer Partition ist nicht möglich, wenn eine Standardpartition existiert" -#: commands/tablecmds.c:19273 +#: commands/tablecmds.c:19283 #, c-format msgid "partitioned table \"%s\" was removed concurrently" msgstr "partitionierte Tabelle »%s« wurde nebenläufig entfernt" -#: commands/tablecmds.c:19279 +#: commands/tablecmds.c:19289 #, c-format msgid "partition \"%s\" was removed concurrently" msgstr "Partition »%s« wurde nebenläufig entfernt" -#: commands/tablecmds.c:19893 commands/tablecmds.c:19913 -#: commands/tablecmds.c:19934 commands/tablecmds.c:19953 -#: commands/tablecmds.c:19995 +#: commands/tablecmds.c:19911 commands/tablecmds.c:19931 +#: commands/tablecmds.c:19952 commands/tablecmds.c:19971 +#: commands/tablecmds.c:20013 #, c-format msgid "cannot attach index \"%s\" as a partition of index \"%s\"" msgstr "kann Index »%s« nicht als Partition an Index »%s« anfügen" -#: commands/tablecmds.c:19896 +#: commands/tablecmds.c:19914 #, c-format msgid "Index \"%s\" is already attached to another index." msgstr "Index »%s« ist bereits an einen anderen Index angefügt." -#: commands/tablecmds.c:19916 +#: commands/tablecmds.c:19934 #, c-format msgid "Index \"%s\" is not an index on any partition of table \"%s\"." msgstr "Index »%s« ist kein Index irgendeiner Partition von Tabelle »%s«." -#: commands/tablecmds.c:19937 +#: commands/tablecmds.c:19955 #, c-format msgid "The index definitions do not match." msgstr "Die Indexdefinitionen stimmen nicht überein." -#: commands/tablecmds.c:19956 +#: commands/tablecmds.c:19974 #, c-format msgid "The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\"." msgstr "Der Index »%s« gehört zu einem Constraint in Tabelle »%s«, aber kein Constraint existiert für Index »%s«." -#: commands/tablecmds.c:19998 +#: commands/tablecmds.c:20016 #, c-format msgid "Another index is already attached for partition \"%s\"." msgstr "Ein anderer Index ist bereits für Partition »%s« angefügt." -#: commands/tablecmds.c:20234 +#: commands/tablecmds.c:20252 #, c-format msgid "column data type %s does not support compression" msgstr "Spaltendatentyp %s unterstützt keine Komprimierung" -#: commands/tablecmds.c:20241 +#: commands/tablecmds.c:20259 #, c-format msgid "invalid compression method \"%s\"" msgstr "ungültige Komprimierungsmethode »%s«" -#: commands/tablecmds.c:20267 +#: commands/tablecmds.c:20285 #, c-format msgid "invalid storage type \"%s\"" msgstr "ungültiger Storage-Typ »%s«" -#: commands/tablecmds.c:20277 +#: commands/tablecmds.c:20295 #, c-format msgid "column data type %s can only have storage PLAIN" msgstr "Spaltendatentyp %s kann nur Storage-Typ PLAIN" @@ -12026,281 +12028,286 @@ msgstr "Verzeichnisse für Tablespace %u konnten nicht entfernt werden" msgid "You can remove the directories manually if necessary." msgstr "Sie können die Verzeichnisse falls nötig manuell entfernen." -#: commands/trigger.c:225 commands/trigger.c:236 +#: commands/trigger.c:226 commands/trigger.c:237 #, c-format msgid "\"%s\" is a table" msgstr "»%s« ist eine Tabelle" -#: commands/trigger.c:227 commands/trigger.c:238 +#: commands/trigger.c:228 commands/trigger.c:239 #, c-format msgid "Tables cannot have INSTEAD OF triggers." msgstr "Tabellen können keine INSTEAD OF-Trigger haben." -#: commands/trigger.c:259 +#: commands/trigger.c:260 #, c-format msgid "\"%s\" is a partitioned table" msgstr "»%s« ist eine partitionierte Tabelle" -#: commands/trigger.c:261 +#: commands/trigger.c:262 #, c-format msgid "ROW triggers with transition tables are not supported on partitioned tables." msgstr "ROW-Trigger mit Übergangstabellen werden für partitionierte Tabellen nicht unterstützt." -#: commands/trigger.c:273 commands/trigger.c:280 commands/trigger.c:444 +#: commands/trigger.c:274 commands/trigger.c:281 commands/trigger.c:445 #, c-format msgid "\"%s\" is a view" msgstr "»%s« ist eine Sicht" -#: commands/trigger.c:275 +#: commands/trigger.c:276 #, c-format msgid "Views cannot have row-level BEFORE or AFTER triggers." msgstr "Sichten können keine BEFORE- oder AFTER-Trigger auf Zeilenebene haben." -#: commands/trigger.c:282 +#: commands/trigger.c:283 #, c-format msgid "Views cannot have TRUNCATE triggers." msgstr "Sichten können keine TRUNCATE-Trigger haben." -#: commands/trigger.c:290 commands/trigger.c:302 commands/trigger.c:437 +#: commands/trigger.c:291 commands/trigger.c:303 commands/trigger.c:438 #, c-format msgid "\"%s\" is a foreign table" msgstr "»%s« ist eine Fremdtabelle" -#: commands/trigger.c:292 +#: commands/trigger.c:293 #, c-format msgid "Foreign tables cannot have INSTEAD OF triggers." msgstr "Fremdtabellen können keine INSTEAD OF-Trigger haben." -#: commands/trigger.c:304 +#: commands/trigger.c:305 #, c-format msgid "Foreign tables cannot have constraint triggers." msgstr "Fremdtabellen können keine Constraint-Trigger haben." -#: commands/trigger.c:309 commands/trigger.c:1325 commands/trigger.c:1432 +#: commands/trigger.c:310 commands/trigger.c:1326 commands/trigger.c:1433 #, c-format msgid "relation \"%s\" cannot have triggers" msgstr "Relation »%s« kann keine Trigger haben" -#: commands/trigger.c:380 +#: commands/trigger.c:381 #, c-format msgid "TRUNCATE FOR EACH ROW triggers are not supported" msgstr "TRUNCATE FOR EACH ROW-Trigger werden nicht unterstützt" -#: commands/trigger.c:388 +#: commands/trigger.c:389 #, c-format msgid "INSTEAD OF triggers must be FOR EACH ROW" msgstr "INSTEAD OF-Trigger müssen FOR EACH ROW sein" -#: commands/trigger.c:392 +#: commands/trigger.c:393 #, c-format msgid "INSTEAD OF triggers cannot have WHEN conditions" msgstr "INSTEAD OF-Trigger können keine WHEN-Bedingungen haben" -#: commands/trigger.c:396 +#: commands/trigger.c:397 #, c-format msgid "INSTEAD OF triggers cannot have column lists" msgstr "INSTEAD OF-Trigger können keine Spaltenlisten haben" -#: commands/trigger.c:425 +#: commands/trigger.c:426 #, c-format msgid "ROW variable naming in the REFERENCING clause is not supported" msgstr "Benennung von ROW-Variablen in der REFERENCING-Klausel wird nicht unterstützt" -#: commands/trigger.c:426 +#: commands/trigger.c:427 #, c-format msgid "Use OLD TABLE or NEW TABLE for naming transition tables." msgstr "Verwenden Sie OLD TABLE und NEW TABLE, um Übergangstabellen zu benennen." -#: commands/trigger.c:439 +#: commands/trigger.c:440 #, c-format msgid "Triggers on foreign tables cannot have transition tables." msgstr "Trigger für Fremdtabellen können keine Übergangstabellen haben." -#: commands/trigger.c:446 +#: commands/trigger.c:447 #, c-format msgid "Triggers on views cannot have transition tables." msgstr "Trigger für Sichten können keine Übergangstabellen haben." -#: commands/trigger.c:462 +#: commands/trigger.c:463 #, c-format msgid "ROW triggers with transition tables are not supported on partitions" msgstr "ROW-Trigger mit Übergangstabellen werden für Partitionen nicht unterstützt" -#: commands/trigger.c:466 +#: commands/trigger.c:467 #, c-format msgid "ROW triggers with transition tables are not supported on inheritance children" msgstr "ROW-Trigger mit Übergangstabellen werden für Vererbungskinder nicht unterstützt" -#: commands/trigger.c:472 +#: commands/trigger.c:473 #, c-format msgid "transition table name can only be specified for an AFTER trigger" msgstr "Übergangstabellenname kann nur für einen AFTER-Trigger angegeben werden" -#: commands/trigger.c:477 +#: commands/trigger.c:478 #, c-format msgid "TRUNCATE triggers with transition tables are not supported" msgstr "TRUNCATE-Trigger mit Übergangstabellen werden nicht unterstützt" -#: commands/trigger.c:494 +#: commands/trigger.c:495 #, c-format msgid "transition tables cannot be specified for triggers with more than one event" msgstr "Übergangstabellen können nicht für Trigger mit mehr als einem Ereignis angegeben werden" -#: commands/trigger.c:505 +#: commands/trigger.c:506 #, c-format msgid "transition tables cannot be specified for triggers with column lists" msgstr "Übergangstabellen können nicht für Trigger mit Spaltenlisten angegeben werden" -#: commands/trigger.c:522 +#: commands/trigger.c:523 #, c-format msgid "NEW TABLE can only be specified for an INSERT or UPDATE trigger" msgstr "NEW TABLE kann nur für INSERT- oder UPDATE-Trigger angegeben werden" -#: commands/trigger.c:527 +#: commands/trigger.c:528 #, c-format msgid "NEW TABLE cannot be specified multiple times" msgstr "NEW TABLE kann nicht mehrmals angegeben werden" -#: commands/trigger.c:537 +#: commands/trigger.c:538 #, c-format msgid "OLD TABLE can only be specified for a DELETE or UPDATE trigger" msgstr "OLD TABLE kann nur für DELETE- oder UPDATE-Trigger angegeben werden" -#: commands/trigger.c:542 +#: commands/trigger.c:543 #, c-format msgid "OLD TABLE cannot be specified multiple times" msgstr "OLD TABLE kann nicht mehrmals angegeben werden" -#: commands/trigger.c:552 +#: commands/trigger.c:553 #, c-format msgid "OLD TABLE name and NEW TABLE name cannot be the same" msgstr "Name für OLD TABLE und NEW TABLE kann nicht gleich sein" -#: commands/trigger.c:616 commands/trigger.c:629 +#: commands/trigger.c:617 commands/trigger.c:630 #, c-format msgid "statement trigger's WHEN condition cannot reference column values" msgstr "WHEN-Bedingung eines Statement-Triggers kann keine Verweise auf Spaltenwerte enthalten" -#: commands/trigger.c:621 +#: commands/trigger.c:622 #, c-format msgid "INSERT trigger's WHEN condition cannot reference OLD values" msgstr "WHEN-Bedingung eines INSERT-Triggers kann keine Verweise auf OLD-Werte enthalten" -#: commands/trigger.c:634 +#: commands/trigger.c:635 #, c-format msgid "DELETE trigger's WHEN condition cannot reference NEW values" msgstr "WHEN-Bedingung eines DELETE-Triggers kann keine Verweise auf NEW-Werte enthalten" -#: commands/trigger.c:639 +#: commands/trigger.c:640 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW system columns" msgstr "WHEN-Bedingung eines BEFORE-Triggers kann keine Verweise auf Systemspalten in NEW enthalten" -#: commands/trigger.c:647 commands/trigger.c:655 +#: commands/trigger.c:648 commands/trigger.c:656 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW generated columns" msgstr "WHEN-Bedingung eines BEFORE-Triggers kann keine Verweise auf generierte Spalten in NEW enthalten" -#: commands/trigger.c:648 +#: commands/trigger.c:649 #, c-format msgid "A whole-row reference is used and the table contains generated columns." msgstr "Ein Verweis auf die ganze Zeile der Tabelle wird verwendet und die Tabelle enthält generierte Spalten." -#: commands/trigger.c:763 commands/trigger.c:1607 +#: commands/trigger.c:764 commands/trigger.c:1608 #, c-format msgid "trigger \"%s\" for relation \"%s\" already exists" msgstr "Trigger »%s« für Relation »%s« existiert bereits" -#: commands/trigger.c:776 +#: commands/trigger.c:777 #, c-format msgid "trigger \"%s\" for relation \"%s\" is an internal or a child trigger" msgstr "Trigger »%s« für Relation »%s« ist ein interner oder abgeleiteter Trigger" -#: commands/trigger.c:795 +#: commands/trigger.c:796 #, c-format msgid "trigger \"%s\" for relation \"%s\" is a constraint trigger" msgstr "Trigger »%s« für Relation »%s« ist ein Constraint-Trigger" -#: commands/trigger.c:1397 commands/trigger.c:1550 commands/trigger.c:1831 +#: commands/trigger.c:1398 commands/trigger.c:1551 commands/trigger.c:1832 #, c-format msgid "trigger \"%s\" for table \"%s\" does not exist" msgstr "Trigger »%s« für Tabelle »%s« existiert nicht" -#: commands/trigger.c:1522 +#: commands/trigger.c:1523 #, c-format msgid "cannot rename trigger \"%s\" on table \"%s\"" msgstr "Trigger »%s« für Tabelle »%s« kann nicht umbenannt werden" -#: commands/trigger.c:1524 +#: commands/trigger.c:1525 #, c-format msgid "Rename the trigger on the partitioned table \"%s\" instead." msgstr "Benennen Sie stattdessen den Trigger für die partitionierte Tabelle »%s« um." -#: commands/trigger.c:1624 +#: commands/trigger.c:1625 #, c-format msgid "renamed trigger \"%s\" on relation \"%s\"" msgstr "Trigger »%s« für Tabelle »%s« wurde umbenannt" -#: commands/trigger.c:1770 +#: commands/trigger.c:1771 #, c-format msgid "permission denied: \"%s\" is a system trigger" msgstr "keine Berechtigung: »%s« ist ein Systemtrigger" -#: commands/trigger.c:2379 +#: commands/trigger.c:2380 #, c-format msgid "trigger function %u returned null value" msgstr "Triggerfunktion %u gab NULL-Wert zurück" -#: commands/trigger.c:2439 commands/trigger.c:2657 commands/trigger.c:2910 -#: commands/trigger.c:3263 +#: commands/trigger.c:2440 commands/trigger.c:2667 commands/trigger.c:2957 +#: commands/trigger.c:3347 #, c-format msgid "BEFORE STATEMENT trigger cannot return a value" msgstr "Trigger für BEFORE STATEMENT kann keinen Wert zurückgeben" -#: commands/trigger.c:2515 +#: commands/trigger.c:2516 #, c-format msgid "moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported" msgstr "Verschieben einer Zeile in eine andere Partition durch einen BEFORE-FOR-EACH-ROW-Trigger wird nicht unterstützt" -#: commands/trigger.c:2516 +#: commands/trigger.c:2517 #, c-format msgid "Before executing trigger \"%s\", the row was to be in partition \"%s.%s\"." msgstr "Vor der Ausführung von Trigger »%s« gehörte die Zeile in Partition »%s.%s«." -#: commands/trigger.c:3341 executor/nodeModifyTable.c:1561 -#: executor/nodeModifyTable.c:1635 executor/nodeModifyTable.c:2397 -#: executor/nodeModifyTable.c:2488 executor/nodeModifyTable.c:3152 -#: executor/nodeModifyTable.c:3322 +#: commands/trigger.c:2546 commands/trigger.c:2825 commands/trigger.c:3188 +#, c-format +msgid "cannot collect transition tuples from child foreign tables" +msgstr "aus abgeleiteten Fremdtabellen können keine Übergangstupel gesammelt werden" + +#: commands/trigger.c:3426 executor/nodeModifyTable.c:1563 +#: executor/nodeModifyTable.c:1637 executor/nodeModifyTable.c:2400 +#: executor/nodeModifyTable.c:2491 executor/nodeModifyTable.c:3155 +#: executor/nodeModifyTable.c:3325 #, c-format msgid "Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows." msgstr "Verwenden Sie einen AFTER-Trigger anstelle eines BEFORE-Triggers, um Änderungen an andere Zeilen zu propagieren." -#: commands/trigger.c:3382 executor/nodeLockRows.c:228 -#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:334 -#: executor/nodeModifyTable.c:1577 executor/nodeModifyTable.c:2414 -#: executor/nodeModifyTable.c:2638 +#: commands/trigger.c:3468 executor/nodeLockRows.c:228 +#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:335 +#: executor/nodeModifyTable.c:1579 executor/nodeModifyTable.c:2417 +#: executor/nodeModifyTable.c:2641 #, c-format msgid "could not serialize access due to concurrent update" msgstr "konnte Zugriff nicht serialisieren wegen gleichzeitiger Aktualisierung" -#: commands/trigger.c:3390 executor/nodeModifyTable.c:1667 -#: executor/nodeModifyTable.c:2505 executor/nodeModifyTable.c:2662 -#: executor/nodeModifyTable.c:3170 +#: commands/trigger.c:3476 executor/nodeModifyTable.c:1669 +#: executor/nodeModifyTable.c:2508 executor/nodeModifyTable.c:2665 +#: executor/nodeModifyTable.c:3173 #, c-format msgid "could not serialize access due to concurrent delete" msgstr "konnte Zugriff nicht serialisieren wegen gleichzeitigem Löschen" -#: commands/trigger.c:4599 +#: commands/trigger.c:4685 #, c-format msgid "cannot fire deferred trigger within security-restricted operation" msgstr "aufgeschobener Trigger kann nicht in einer sicherheitsbeschränkten Operation ausgelöst werden" -#: commands/trigger.c:5780 +#: commands/trigger.c:5866 #, c-format msgid "constraint \"%s\" is not deferrable" msgstr "Constraint »%s« ist nicht aufschiebbar" -#: commands/trigger.c:5803 +#: commands/trigger.c:5889 #, c-format msgid "constraint \"%s\" does not exist" msgstr "Constraint »%s« existiert nicht" @@ -12768,8 +12775,8 @@ msgstr "Nur Rollen mit dem %s-Attribut können Rollen erzeugen." msgid "Only roles with the %s attribute may create roles with the %s attribute." msgstr "Nur Rollen mit dem %s-Attribut können Rollen mit dem %s-Attribut erzeugen." -#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17310 -#: gram.y:17356 utils/adt/acl.c:5574 utils/adt/acl.c:5580 +#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17317 +#: gram.y:17363 utils/adt/acl.c:5591 utils/adt/acl.c:5597 #, c-format msgid "role name \"%s\" is reserved" msgstr "Rollenname »%s« ist reserviert" @@ -12864,8 +12871,8 @@ msgstr "in DROP ROLE kann kein Rollenplatzhalter verwendet werden" #: commands/user.c:1135 commands/user.c:1357 commands/variable.c:851 #: commands/variable.c:854 commands/variable.c:971 commands/variable.c:974 -#: utils/adt/acl.c:365 utils/adt/acl.c:385 utils/adt/acl.c:5429 -#: utils/adt/acl.c:5477 utils/adt/acl.c:5505 utils/adt/acl.c:5524 +#: utils/adt/acl.c:382 utils/adt/acl.c:402 utils/adt/acl.c:5446 +#: utils/adt/acl.c:5494 utils/adt/acl.c:5522 utils/adt/acl.c:5541 #: utils/adt/regproc.c:1571 utils/init/miscinit.c:799 #, c-format msgid "role \"%s\" does not exist" @@ -13056,122 +13063,122 @@ msgstr "keine Berechtigung, um von Rolle »%s« gewährte Privilegien zu entzieh msgid "Only roles with privileges of role \"%s\" may revoke privileges granted by this role." msgstr "Nur Rollen mit den Privilegien der Rolle »%s« können von dieser Rolle gewährte Privilegien entziehen." -#: commands/user.c:2491 utils/adt/acl.c:1324 +#: commands/user.c:2491 utils/adt/acl.c:1341 #, c-format msgid "dependent privileges exist" msgstr "abhängige Privilegien existieren" -#: commands/user.c:2492 utils/adt/acl.c:1325 +#: commands/user.c:2492 utils/adt/acl.c:1342 #, c-format msgid "Use CASCADE to revoke them too." msgstr "Verwenden Sie CASCADE, um diese auch zu entziehen." -#: commands/vacuum.c:134 +#: commands/vacuum.c:135 #, c-format msgid "\"vacuum_buffer_usage_limit\" must be 0 or between %d kB and %d kB" msgstr "»vacuum_buffer_usage_limit« muss 0 sein oder zwischen %d kB und %d kB liegen" -#: commands/vacuum.c:209 +#: commands/vacuum.c:210 #, c-format msgid "BUFFER_USAGE_LIMIT option must be 0 or between %d kB and %d kB" msgstr "Option BUFFER_USAGE_LIMIT muss 0 sein oder zwischen %d kB und %d kB liegen" -#: commands/vacuum.c:219 +#: commands/vacuum.c:220 #, c-format msgid "unrecognized ANALYZE option \"%s\"" msgstr "unbekannte ANALYZE-Option »%s«" -#: commands/vacuum.c:259 +#: commands/vacuum.c:260 #, c-format msgid "parallel option requires a value between 0 and %d" msgstr "Option PARALLEL benötigt einen Wert zwischen 0 und %d" -#: commands/vacuum.c:271 +#: commands/vacuum.c:272 #, c-format msgid "parallel workers for vacuum must be between 0 and %d" msgstr "parallele Arbeitsprozesse für Vacuum müssen zwischen 0 und %d sein" -#: commands/vacuum.c:292 +#: commands/vacuum.c:293 #, c-format msgid "unrecognized VACUUM option \"%s\"" msgstr "unbekannte VACUUM-Option »%s«" -#: commands/vacuum.c:318 +#: commands/vacuum.c:319 #, c-format msgid "VACUUM FULL cannot be performed in parallel" msgstr "VACUUM FULL kann nicht parallel ausgeführt werden" -#: commands/vacuum.c:329 +#: commands/vacuum.c:330 #, c-format msgid "BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL" msgstr "BUFFER_USAGE_LIMIT kann nicht für VACUUM FULL angegeben werden" -#: commands/vacuum.c:343 +#: commands/vacuum.c:344 #, c-format msgid "ANALYZE option must be specified when a column list is provided" msgstr "Option ANALYZE muss angegeben werden, wenn eine Spaltenliste angegeben ist" -#: commands/vacuum.c:355 +#: commands/vacuum.c:356 #, c-format msgid "VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL" msgstr "VACUUM-Option DISABLE_PAGE_SKIPPING kann nicht zusammen mit FULL verwendet werden" -#: commands/vacuum.c:362 +#: commands/vacuum.c:363 #, c-format msgid "PROCESS_TOAST required with VACUUM FULL" msgstr "PROCESS_TOAST benötigt VACUUM FULL" -#: commands/vacuum.c:371 +#: commands/vacuum.c:372 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with a list of tables" msgstr "ONLY_DATABASE_STATS kann nicht mit einer Tabellenliste angegeben werden" -#: commands/vacuum.c:380 +#: commands/vacuum.c:381 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with other VACUUM options" msgstr "ONLY_DATABASE_STATS kann nicht mit anderen VACUUM-Optionen angegeben werden" -#: commands/vacuum.c:515 +#: commands/vacuum.c:516 #, c-format msgid "%s cannot be executed from VACUUM or ANALYZE" msgstr "%s kann nicht aus VACUUM oder ANALYZE ausgeführt werden" -#: commands/vacuum.c:732 +#: commands/vacuum.c:741 #, c-format msgid "permission denied to vacuum \"%s\", skipping it" msgstr "keine Berechtigung für Vacuum von »%s«, wird übersprungen" -#: commands/vacuum.c:745 +#: commands/vacuum.c:754 #, c-format msgid "permission denied to analyze \"%s\", skipping it" msgstr "keine Berechtigung für Analyze von »%s«, wird übersprungen" -#: commands/vacuum.c:823 commands/vacuum.c:920 +#: commands/vacuum.c:832 commands/vacuum.c:929 #, c-format msgid "skipping vacuum of \"%s\" --- lock not available" msgstr "überspringe Vacuum von »%s« --- Sperre nicht verfügbar" -#: commands/vacuum.c:828 +#: commands/vacuum.c:837 #, c-format msgid "skipping vacuum of \"%s\" --- relation no longer exists" msgstr "überspringe Vacuum von »%s« --- Relation existiert nicht mehr" -#: commands/vacuum.c:844 commands/vacuum.c:925 +#: commands/vacuum.c:853 commands/vacuum.c:934 #, c-format msgid "skipping analyze of \"%s\" --- lock not available" msgstr "überspringe Analyze von »%s« --- Sperre nicht verfügbar" -#: commands/vacuum.c:849 +#: commands/vacuum.c:858 #, c-format msgid "skipping analyze of \"%s\" --- relation no longer exists" msgstr "überspringe Analyze von »%s« --- Relation existiert nicht mehr" -#: commands/vacuum.c:1141 +#: commands/vacuum.c:1150 #, c-format msgid "cutoff for removing and freezing tuples is far in the past" msgstr "Obergrenze für das Entfernen und Einfrieren von Tuples ist weit in der Vergangenheit" -#: commands/vacuum.c:1142 commands/vacuum.c:1147 +#: commands/vacuum.c:1151 commands/vacuum.c:1156 #, c-format msgid "" "Close open transactions soon to avoid wraparound problems.\n" @@ -13180,37 +13187,37 @@ msgstr "" "Schließen Sie bald alle offenen Transaktionen, um Überlaufprobleme zu vermeiden.\n" "Eventuell müssen Sie auch alte vorbereitete Transaktionen committen oder zurückrollen oder unbenutzte Replikations-Slots löschen." -#: commands/vacuum.c:1146 +#: commands/vacuum.c:1155 #, c-format msgid "cutoff for freezing multixacts is far in the past" msgstr "Obergrenze für das Einfrieren von Multixacts ist weit in der Vergangenheit" -#: commands/vacuum.c:1902 +#: commands/vacuum.c:1911 #, c-format msgid "some databases have not been vacuumed in over 2 billion transactions" msgstr "einige Datenbanken sind seit über 2 Milliarden Transaktionen nicht gevacuumt worden" -#: commands/vacuum.c:1903 +#: commands/vacuum.c:1912 #, c-format msgid "You might have already suffered transaction-wraparound data loss." msgstr "Sie haben möglicherweise bereits Daten wegen Transaktionsnummernüberlauf verloren." -#: commands/vacuum.c:2082 +#: commands/vacuum.c:2098 #, c-format msgid "skipping \"%s\" --- cannot vacuum non-tables or special system tables" msgstr "überspringe »%s« --- kann Nicht-Tabellen oder besondere Systemtabellen nicht vacuumen" -#: commands/vacuum.c:2514 +#: commands/vacuum.c:2545 #, c-format msgid "scanned index \"%s\" to remove %lld row versions" msgstr "Index »%s« gelesen und %lld Zeilenversionen entfernt" -#: commands/vacuum.c:2533 +#: commands/vacuum.c:2564 #, c-format msgid "index \"%s\" now contains %.0f row versions in %u pages" msgstr "Index »%s« enthält %.0f Zeilenversionen in %u Seiten" -#: commands/vacuum.c:2537 +#: commands/vacuum.c:2568 #, c-format msgid "" "%.0f index row versions were removed.\n" @@ -13462,26 +13469,26 @@ msgid "no value found for parameter %d" msgstr "kein Wert für Parameter %d gefunden" #: executor/execExpr.c:642 executor/execExpr.c:649 executor/execExpr.c:655 -#: executor/execExprInterp.c:4852 executor/execExprInterp.c:4869 -#: executor/execExprInterp.c:4968 executor/nodeModifyTable.c:203 -#: executor/nodeModifyTable.c:222 executor/nodeModifyTable.c:239 -#: executor/nodeModifyTable.c:249 executor/nodeModifyTable.c:259 +#: executor/execExprInterp.c:4851 executor/execExprInterp.c:4868 +#: executor/execExprInterp.c:4967 executor/nodeModifyTable.c:204 +#: executor/nodeModifyTable.c:223 executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:250 executor/nodeModifyTable.c:260 #, c-format msgid "table row type and query-specified row type do not match" msgstr "Zeilentyp der Tabelle und der von der Anfrage angegebene Zeilentyp stimmen nicht überein" -#: executor/execExpr.c:643 executor/nodeModifyTable.c:204 +#: executor/execExpr.c:643 executor/nodeModifyTable.c:205 #, c-format msgid "Query has too many columns." msgstr "Anfrage hat zu viele Spalten." -#: executor/execExpr.c:650 executor/nodeModifyTable.c:223 +#: executor/execExpr.c:650 executor/nodeModifyTable.c:224 #, c-format msgid "Query provides a value for a dropped column at ordinal position %d." msgstr "Anfrage liefert einen Wert für eine gelöschte Spalte auf Position %d." -#: executor/execExpr.c:656 executor/execExprInterp.c:4870 -#: executor/nodeModifyTable.c:250 +#: executor/execExpr.c:656 executor/execExprInterp.c:4869 +#: executor/nodeModifyTable.c:251 #, c-format msgid "Table has type %s at ordinal position %d, but query expects %s." msgstr "Tabelle hat Typ %s auf Position %d, aber Anfrage erwartet %s." @@ -13623,14 +13630,14 @@ msgstr "kein SQL/JSON-Item für angegebenen Pfad gefunden" msgid "could not coerce %s expression (%s) to the RETURNING type" msgstr "konnte %s-Ausdruck (%s) nicht in RETURNING-Typ umwandeln" -#: executor/execExprInterp.c:4853 +#: executor/execExprInterp.c:4852 #, c-format msgid "Table row contains %d attribute, but query expects %d." msgid_plural "Table row contains %d attributes, but query expects %d." msgstr[0] "Tabellenzeile enthält %d Attribut, aber Anfrage erwartet %d." msgstr[1] "Tabellenzeile enthält %d Attribute, aber Anfrage erwartet %d." -#: executor/execExprInterp.c:4969 executor/execSRF.c:977 +#: executor/execExprInterp.c:4968 executor/execSRF.c:977 #, c-format msgid "Physical storage mismatch on dropped attribute at ordinal position %d." msgstr "Physischer Speicher stimmt nicht überein mit gelöschtem Attribut auf Position %d." @@ -14078,69 +14085,69 @@ msgstr "RIGHT JOIN wird nur für Merge-Verbund-fähige Verbundbedingungen unters msgid "FULL JOIN is only supported with merge-joinable join conditions" msgstr "FULL JOIN wird nur für Merge-Verbund-fähige Verbundbedingungen unterstützt" -#: executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:241 #, c-format msgid "Query provides a value for a generated column at ordinal position %d." msgstr "Anfrage liefert einen Wert für eine generierte Spalte auf Position %d." -#: executor/nodeModifyTable.c:260 +#: executor/nodeModifyTable.c:261 #, c-format msgid "Query has too few columns." msgstr "Anfrage hat zu wenige Spalten." -#: executor/nodeModifyTable.c:1560 executor/nodeModifyTable.c:1634 +#: executor/nodeModifyTable.c:1562 executor/nodeModifyTable.c:1636 #, c-format msgid "tuple to be deleted was already modified by an operation triggered by the current command" msgstr "das zu löschende Tupel wurde schon durch eine vom aktuellen Befehl ausgelöste Operation verändert" -#: executor/nodeModifyTable.c:1789 +#: executor/nodeModifyTable.c:1791 #, c-format msgid "invalid ON UPDATE specification" msgstr "ungültige ON-UPDATE-Angabe" -#: executor/nodeModifyTable.c:1790 +#: executor/nodeModifyTable.c:1792 #, c-format msgid "The result tuple would appear in a different partition than the original tuple." msgstr "Das Ergebnistupel würde in einer anderen Partition erscheinen als das ursprüngliche Tupel." -#: executor/nodeModifyTable.c:2246 +#: executor/nodeModifyTable.c:2249 #, c-format msgid "cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key" msgstr "Tupel kann nicht zwischen Partitionen bewegt werden, wenn ein Fremdschlüssel direkt auf einen Vorgänger (außer der Wurzel) der Quellpartition verweist" -#: executor/nodeModifyTable.c:2247 +#: executor/nodeModifyTable.c:2250 #, c-format msgid "A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\"." msgstr "Ein Fremdschlüssel verweist auf den Vorgänger »%s«, aber nicht auf den Wurzelvorgänger »%s«." -#: executor/nodeModifyTable.c:2250 +#: executor/nodeModifyTable.c:2253 #, c-format msgid "Consider defining the foreign key on table \"%s\"." msgstr "Definieren Sie den Fremdschlüssel eventuell für Tabelle »%s«." #. translator: %s is a SQL command name -#: executor/nodeModifyTable.c:2616 executor/nodeModifyTable.c:3158 -#: executor/nodeModifyTable.c:3328 +#: executor/nodeModifyTable.c:2619 executor/nodeModifyTable.c:3161 +#: executor/nodeModifyTable.c:3331 #, c-format msgid "%s command cannot affect row a second time" msgstr "Befehl in %s kann eine Zeile nicht ein zweites Mal ändern" -#: executor/nodeModifyTable.c:2618 +#: executor/nodeModifyTable.c:2621 #, c-format msgid "Ensure that no rows proposed for insertion within the same command have duplicate constrained values." msgstr "Stellen Sie sicher, dass keine im selben Befehl fürs Einfügen vorgesehene Zeilen doppelte Werte haben, die einen Constraint verletzen würden." -#: executor/nodeModifyTable.c:3151 executor/nodeModifyTable.c:3321 +#: executor/nodeModifyTable.c:3154 executor/nodeModifyTable.c:3324 #, c-format msgid "tuple to be updated or deleted was already modified by an operation triggered by the current command" msgstr "das zu aktualisierende oder zu löschende Tupel wurde schon durch eine vom aktuellen Befehl ausgelöste Operation verändert" -#: executor/nodeModifyTable.c:3160 executor/nodeModifyTable.c:3330 +#: executor/nodeModifyTable.c:3163 executor/nodeModifyTable.c:3333 #, c-format msgid "Ensure that not more than one source row matches any one target row." msgstr "Stellen Sie sicher, dass nicht mehr als eine Quellzeile auf jede Zielzeile passt." -#: executor/nodeModifyTable.c:3229 +#: executor/nodeModifyTable.c:3232 #, c-format msgid "tuple to be merged was already moved to another partition due to concurrent update" msgstr "das zu mergende Tupel wurde schon durch ein gleichzeitiges Update in eine andere Partition verschoben" @@ -14303,7 +14310,7 @@ msgstr "konnte Tupel nicht an Shared-Memory-Queue senden" msgid "user mapping not found for user \"%s\", server \"%s\"" msgstr "Benutzerabbildung für Benutzer »%s«, Server »%s« nicht gefunden" -#: foreign/foreign.c:336 optimizer/plan/createplan.c:7153 +#: foreign/foreign.c:336 optimizer/plan/createplan.c:7155 #: optimizer/util/plancat.c:540 #, c-format msgid "access to non-system foreign table is restricted" @@ -14518,200 +14525,205 @@ msgstr "widersprüchliche oder überflüssige NULL/NOT NULL-Deklarationen für S msgid "unrecognized column option \"%s\"" msgstr "unbekannte Spaltenoption »%s«" -#: gram.y:14147 +#: gram.y:14098 +#, c-format +msgid "option name \"%s\" cannot be used in XMLTABLE" +msgstr "Optionsname »%s« kann nicht in XMLTABLE verwendet werden" + +#: gram.y:14154 #, c-format msgid "only string constants are supported in JSON_TABLE path specification" msgstr "nur Zeichenkettenkonstanten werden in Pfadangaben in JSON_TABLE unterstützt" -#: gram.y:14469 +#: gram.y:14476 #, c-format msgid "precision for type float must be at least 1 bit" msgstr "Präzision von Typ float muss mindestens 1 Bit sein" -#: gram.y:14478 +#: gram.y:14485 #, c-format msgid "precision for type float must be less than 54 bits" msgstr "Präzision von Typ float muss weniger als 54 Bits sein" -#: gram.y:14995 +#: gram.y:15002 #, c-format msgid "wrong number of parameters on left side of OVERLAPS expression" msgstr "falsche Anzahl Parameter auf linker Seite von OVERLAPS-Ausdruck" -#: gram.y:15000 +#: gram.y:15007 #, c-format msgid "wrong number of parameters on right side of OVERLAPS expression" msgstr "falsche Anzahl Parameter auf rechter Seite von OVERLAPS-Ausdruck" -#: gram.y:15177 +#: gram.y:15184 #, c-format msgid "UNIQUE predicate is not yet implemented" msgstr "UNIQUE-Prädikat ist noch nicht implementiert" -#: gram.y:15591 +#: gram.y:15598 #, c-format msgid "cannot use multiple ORDER BY clauses with WITHIN GROUP" msgstr "in WITHIN GROUP können nicht mehrere ORDER-BY-Klauseln verwendet werden" -#: gram.y:15596 +#: gram.y:15603 #, c-format msgid "cannot use DISTINCT with WITHIN GROUP" msgstr "DISTINCT kann nicht mit WITHIN GROUP verwendet werden" -#: gram.y:15601 +#: gram.y:15608 #, c-format msgid "cannot use VARIADIC with WITHIN GROUP" msgstr "VARIADIC kann nicht mit WITHIN GROUP verwendet werden" -#: gram.y:16328 gram.y:16352 +#: gram.y:16335 gram.y:16359 #, c-format msgid "frame start cannot be UNBOUNDED FOLLOWING" msgstr "Frame-Beginn kann nicht UNBOUNDED FOLLOWING sein" -#: gram.y:16333 +#: gram.y:16340 #, c-format msgid "frame starting from following row cannot end with current row" msgstr "Frame der in der folgenden Zeile beginnt kann nicht in der aktuellen Zeile enden" -#: gram.y:16357 +#: gram.y:16364 #, c-format msgid "frame end cannot be UNBOUNDED PRECEDING" msgstr "Frame-Ende kann nicht UNBOUNDED PRECEDING sein" -#: gram.y:16363 +#: gram.y:16370 #, c-format msgid "frame starting from current row cannot have preceding rows" msgstr "Frame der in der aktuellen Zeile beginnt kann keine vorhergehenden Zeilen haben" -#: gram.y:16370 +#: gram.y:16377 #, c-format msgid "frame starting from following row cannot have preceding rows" msgstr "Frame der in der folgenden Zeile beginnt kann keine vorhergehenden Zeilen haben" -#: gram.y:16919 +#: gram.y:16926 #, c-format msgid "unrecognized JSON encoding: %s" msgstr "unbekannte JSON-Kodierung: %s" -#: gram.y:17243 +#: gram.y:17250 #, c-format msgid "type modifier cannot have parameter name" msgstr "Typmodifikator kann keinen Parameternamen haben" -#: gram.y:17249 +#: gram.y:17256 #, c-format msgid "type modifier cannot have ORDER BY" msgstr "Typmodifikator kann kein ORDER BY haben" -#: gram.y:17317 gram.y:17324 gram.y:17331 +#: gram.y:17324 gram.y:17331 gram.y:17338 #, c-format msgid "%s cannot be used as a role name here" msgstr "%s kann hier nicht als Rollenname verwendet werden" -#: gram.y:17421 gram.y:18906 +#: gram.y:17428 gram.y:18913 #, c-format msgid "WITH TIES cannot be specified without ORDER BY clause" msgstr "WITH TIES kann nicht ohne ORDER-BY-Klausel angegeben werden" -#: gram.y:18597 gram.y:18772 +#: gram.y:18604 gram.y:18779 msgid "improper use of \"*\"" msgstr "unzulässige Verwendung von »*«" -#: gram.y:18735 gram.y:18752 tsearch/spell.c:964 tsearch/spell.c:981 +#: gram.y:18742 gram.y:18759 tsearch/spell.c:964 tsearch/spell.c:981 #: tsearch/spell.c:998 tsearch/spell.c:1015 tsearch/spell.c:1081 #, c-format msgid "syntax error" msgstr "Syntaxfehler" -#: gram.y:18836 +#: gram.y:18843 #, c-format msgid "an ordered-set aggregate with a VARIADIC direct argument must have one VARIADIC aggregated argument of the same data type" msgstr "eine Ordered-Set-Aggregatfunktion mit einem direkten VARIADIC-Argument muss ein aggregiertes VARIADIC-Argument des selben Datentyps haben" -#: gram.y:18873 +#: gram.y:18880 #, c-format msgid "multiple ORDER BY clauses not allowed" msgstr "mehrere ORDER-BY-Klauseln sind nicht erlaubt" -#: gram.y:18884 +#: gram.y:18891 #, c-format msgid "multiple OFFSET clauses not allowed" msgstr "mehrere OFFSET-Klauseln sind nicht erlaubt" -#: gram.y:18893 +#: gram.y:18900 #, c-format msgid "multiple LIMIT clauses not allowed" msgstr "mehrere LIMIT-Klauseln sind nicht erlaubt" -#: gram.y:18902 +#: gram.y:18909 #, c-format msgid "multiple limit options not allowed" msgstr "mehrere Limit-Optionen sind nicht erlaubt" -#: gram.y:18929 +#: gram.y:18936 #, c-format msgid "multiple WITH clauses not allowed" msgstr "mehrere WITH-Klauseln sind nicht erlaubt" -#: gram.y:19122 +#: gram.y:19129 #, c-format msgid "OUT and INOUT arguments aren't allowed in TABLE functions" msgstr "OUT- und INOUT-Argumente sind in TABLE-Funktionen nicht erlaubt" -#: gram.y:19255 +#: gram.y:19262 #, c-format msgid "multiple COLLATE clauses not allowed" msgstr "mehrere COLLATE-Klauseln sind nicht erlaubt" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19293 gram.y:19306 +#: gram.y:19300 gram.y:19313 #, c-format msgid "%s constraints cannot be marked DEFERRABLE" msgstr "%s-Constraints können nicht als DEFERRABLE markiert werden" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19319 +#: gram.y:19326 #, c-format msgid "%s constraints cannot be marked NOT VALID" msgstr "%s-Constraints können nicht als NOT VALID markiert werden" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19332 +#: gram.y:19339 #, c-format msgid "%s constraints cannot be marked NO INHERIT" msgstr "%s-Constraints können nicht als NO INHERIT markiert werden" -#: gram.y:19354 +#: gram.y:19361 #, c-format msgid "unrecognized partitioning strategy \"%s\"" msgstr "unbekannte Partitionierungsstrategie »%s«" -#: gram.y:19378 +#: gram.y:19385 #, c-format msgid "invalid publication object list" msgstr "ungültige Publikationsobjektliste" -#: gram.y:19379 +#: gram.y:19386 #, c-format msgid "One of TABLE or TABLES IN SCHEMA must be specified before a standalone table or schema name." msgstr "Entweder TABLE oder TABLES IN SCHEMA muss vor einem alleinstehenden Tabellen- oder Schemanamen angegeben werden." -#: gram.y:19395 +#: gram.y:19402 #, c-format msgid "invalid table name" msgstr "ungültiger Tabellenname" -#: gram.y:19416 +#: gram.y:19423 #, c-format msgid "WHERE clause not allowed for schema" msgstr "für Schemas ist keine WHERE-Klausel erlaubt" -#: gram.y:19423 +#: gram.y:19430 #, c-format msgid "column specification not allowed for schema" msgstr "für Schemas ist keine Spaltenangabe erlaubt" -#: gram.y:19437 +#: gram.y:19444 #, c-format msgid "invalid schema name" msgstr "ungültiger Schemaname" @@ -15009,545 +15021,545 @@ msgstr "Fehlerhafter Proof in »client-final-message«." msgid "Garbage found at the end of client-final-message." msgstr "Müll am Ende der »client-final-message« gefunden." -#: libpq/auth.c:269 +#: libpq/auth.c:277 #, c-format msgid "authentication failed for user \"%s\": host rejected" msgstr "Authentifizierung für Benutzer »%s« fehlgeschlagen: Host abgelehnt" -#: libpq/auth.c:272 +#: libpq/auth.c:280 #, c-format msgid "\"trust\" authentication failed for user \"%s\"" msgstr "»trust«-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:275 +#: libpq/auth.c:283 #, c-format msgid "Ident authentication failed for user \"%s\"" msgstr "Ident-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:278 +#: libpq/auth.c:286 #, c-format msgid "Peer authentication failed for user \"%s\"" msgstr "Peer-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:283 +#: libpq/auth.c:291 #, c-format msgid "password authentication failed for user \"%s\"" msgstr "Passwort-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:288 +#: libpq/auth.c:296 #, c-format msgid "GSSAPI authentication failed for user \"%s\"" msgstr "GSSAPI-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:291 +#: libpq/auth.c:299 #, c-format msgid "SSPI authentication failed for user \"%s\"" msgstr "SSPI-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:294 +#: libpq/auth.c:302 #, c-format msgid "PAM authentication failed for user \"%s\"" msgstr "PAM-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:297 +#: libpq/auth.c:305 #, c-format msgid "BSD authentication failed for user \"%s\"" msgstr "BSD-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:300 +#: libpq/auth.c:308 #, c-format msgid "LDAP authentication failed for user \"%s\"" msgstr "LDAP-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:303 +#: libpq/auth.c:311 #, c-format msgid "certificate authentication failed for user \"%s\"" msgstr "Zertifikatauthentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:306 +#: libpq/auth.c:314 #, c-format msgid "RADIUS authentication failed for user \"%s\"" msgstr "RADIUS-Authentifizierung für Benutzer »%s« fehlgeschlagen" -#: libpq/auth.c:309 +#: libpq/auth.c:317 #, c-format msgid "authentication failed for user \"%s\": invalid authentication method" msgstr "Authentifizierung für Benutzer »%s« fehlgeschlagen: ungültige Authentifizierungsmethode" -#: libpq/auth.c:313 +#: libpq/auth.c:321 #, c-format msgid "Connection matched file \"%s\" line %d: \"%s\"" msgstr "Verbindung stimmte mit Datei »%s« Zeile %d überein: »%s«" -#: libpq/auth.c:357 +#: libpq/auth.c:365 #, c-format msgid "authentication identifier set more than once" msgstr "Authentifizierungsbezeichner mehrmals gesetzt" -#: libpq/auth.c:358 +#: libpq/auth.c:366 #, c-format msgid "previous identifier: \"%s\"; new identifier: \"%s\"" msgstr "vorheriger Bezeichner: »%s«; neuer Bezeichner: »%s«" -#: libpq/auth.c:368 +#: libpq/auth.c:376 #, c-format msgid "connection authenticated: identity=\"%s\" method=%s (%s:%d)" msgstr "Verbindung authentifiziert: Identität=»%s« Methode=%s (%s:%d)" -#: libpq/auth.c:408 +#: libpq/auth.c:416 #, c-format msgid "client certificates can only be checked if a root certificate store is available" msgstr "Client-Zertifikate können nur überprüft werden, wenn Wurzelzertifikat verfügbar ist" -#: libpq/auth.c:419 +#: libpq/auth.c:427 #, c-format msgid "connection requires a valid client certificate" msgstr "Verbindung erfordert ein gültiges Client-Zertifikat" -#: libpq/auth.c:450 libpq/auth.c:496 +#: libpq/auth.c:458 libpq/auth.c:504 msgid "GSS encryption" msgstr "GSS-Verschlüsselung" -#: libpq/auth.c:453 libpq/auth.c:499 +#: libpq/auth.c:461 libpq/auth.c:507 msgid "SSL encryption" msgstr "SSL-Verschlüsselung" -#: libpq/auth.c:455 libpq/auth.c:501 +#: libpq/auth.c:463 libpq/auth.c:509 msgid "no encryption" msgstr "keine Verschlüsselung" #. translator: last %s describes encryption state -#: libpq/auth.c:461 +#: libpq/auth.c:469 #, c-format msgid "pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s" msgstr "pg_hba.conf lehnt Replikationsverbindung ab für Host »%s«, Benutzer »%s«, %s" #. translator: last %s describes encryption state -#: libpq/auth.c:468 +#: libpq/auth.c:476 #, c-format msgid "pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s" msgstr "pg_hba.conf lehnt Verbindung ab für Host »%s«, Benutzer »%s«, Datenbank »%s«, %s" -#: libpq/auth.c:506 +#: libpq/auth.c:514 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup matches." msgstr "Auflösung der Client-IP-Adresse ergab »%s«, Vorwärtsauflösung stimmt überein." -#: libpq/auth.c:509 +#: libpq/auth.c:517 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup not checked." msgstr "Auflösung der Client-IP-Adresse ergab »%s«, Vorwärtsauflösung nicht geprüft." -#: libpq/auth.c:512 +#: libpq/auth.c:520 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup does not match." msgstr "Auflösung der Client-IP-Adresse ergab »%s«, Vorwärtsauflösung stimmt nicht überein." -#: libpq/auth.c:515 +#: libpq/auth.c:523 #, c-format msgid "Could not translate client host name \"%s\" to IP address: %s." msgstr "Konnte Client-Hostnamen »%s« nicht in IP-Adresse übersetzen: %s." -#: libpq/auth.c:520 +#: libpq/auth.c:528 #, c-format msgid "Could not resolve client IP address to a host name: %s." msgstr "Konnte Client-IP-Adresse nicht in einen Hostnamen auflösen: %s." #. translator: last %s describes encryption state -#: libpq/auth.c:528 +#: libpq/auth.c:536 #, c-format msgid "no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s" msgstr "kein pg_hba.conf-Eintrag für Replikationsverbindung von Host »%s«, Benutzer »%s«, %s" #. translator: last %s describes encryption state -#: libpq/auth.c:536 +#: libpq/auth.c:544 #, c-format msgid "no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s" msgstr "kein pg_hba.conf-Eintrag für Host »%s«, Benutzer »%s«, Datenbank »%s«, %s" -#: libpq/auth.c:656 +#: libpq/auth.c:664 #, c-format msgid "connection authenticated: user=\"%s\" method=%s (%s:%d)" msgstr "Verbindung authentifiziert: Benutzer=»%s« Methode=%s (%s:%d)" -#: libpq/auth.c:725 +#: libpq/auth.c:733 #, c-format msgid "expected password response, got message type %d" msgstr "Passwort-Antwort erwartet, Message-Typ %d empfangen" -#: libpq/auth.c:746 +#: libpq/auth.c:754 #, c-format msgid "invalid password packet size" msgstr "ungültige Größe des Passwortpakets" -#: libpq/auth.c:764 +#: libpq/auth.c:772 #, c-format msgid "empty password returned by client" msgstr "Client gab leeres Passwort zurück" -#: libpq/auth.c:892 +#: libpq/auth.c:900 #, c-format msgid "could not generate random MD5 salt" msgstr "konnte zufälliges MD5-Salt nicht erzeugen" -#: libpq/auth.c:943 libpq/be-secure-gssapi.c:540 +#: libpq/auth.c:951 libpq/be-secure-gssapi.c:550 #, c-format msgid "could not set environment: %m" msgstr "konnte Umgebung nicht setzen: %m" -#: libpq/auth.c:982 +#: libpq/auth.c:990 #, c-format msgid "expected GSS response, got message type %d" msgstr "GSS-Antwort erwartet, Message-Typ %d empfangen" -#: libpq/auth.c:1048 +#: libpq/auth.c:1056 msgid "accepting GSS security context failed" msgstr "Annahme des GSS-Sicherheitskontexts fehlgeschlagen" -#: libpq/auth.c:1089 +#: libpq/auth.c:1097 msgid "retrieving GSS user name failed" msgstr "Abfrage des GSS-Benutzernamens fehlgeschlagen" -#: libpq/auth.c:1235 +#: libpq/auth.c:1243 msgid "could not acquire SSPI credentials" msgstr "konnte SSPI-Credentials nicht erhalten" -#: libpq/auth.c:1260 +#: libpq/auth.c:1268 #, c-format msgid "expected SSPI response, got message type %d" msgstr "SSPI-Antwort erwartet, Message-Typ %d empfangen" -#: libpq/auth.c:1338 +#: libpq/auth.c:1346 msgid "could not accept SSPI security context" msgstr "konnte SSPI-Sicherheitskontext nicht akzeptieren" -#: libpq/auth.c:1379 +#: libpq/auth.c:1387 msgid "could not get token from SSPI security context" msgstr "konnte kein Token vom SSPI-Sicherheitskontext erhalten" -#: libpq/auth.c:1515 libpq/auth.c:1534 +#: libpq/auth.c:1523 libpq/auth.c:1542 #, c-format msgid "could not translate name" msgstr "konnte Namen nicht umwandeln" -#: libpq/auth.c:1547 +#: libpq/auth.c:1555 #, c-format msgid "realm name too long" msgstr "Realm-Name zu lang" -#: libpq/auth.c:1562 +#: libpq/auth.c:1570 #, c-format msgid "translated account name too long" msgstr "umgewandelter Account-Name zu lang" -#: libpq/auth.c:1741 +#: libpq/auth.c:1749 #, c-format msgid "could not create socket for Ident connection: %m" msgstr "konnte Socket für Ident-Verbindung nicht erzeugen: %m" -#: libpq/auth.c:1756 +#: libpq/auth.c:1764 #, c-format msgid "could not bind to local address \"%s\": %m" msgstr "konnte nicht mit lokaler Adresse »%s« verbinden: %m" -#: libpq/auth.c:1768 +#: libpq/auth.c:1776 #, c-format msgid "could not connect to Ident server at address \"%s\", port %s: %m" msgstr "konnte nicht mit Ident-Server auf Adresse »%s«, Port %s verbinden: %m" -#: libpq/auth.c:1790 +#: libpq/auth.c:1798 #, c-format msgid "could not send query to Ident server at address \"%s\", port %s: %m" msgstr "konnte Anfrage an Ident-Server auf Adresse »%s«, Port %s nicht senden: %m" -#: libpq/auth.c:1807 +#: libpq/auth.c:1815 #, c-format msgid "could not receive response from Ident server at address \"%s\", port %s: %m" msgstr "konnte Antwort von Ident-Server auf Adresse »%s«, Port %s nicht empfangen: %m" -#: libpq/auth.c:1817 +#: libpq/auth.c:1825 #, c-format msgid "invalidly formatted response from Ident server: \"%s\"" msgstr "ungültig formatierte Antwort vom Ident-Server: »%s«" -#: libpq/auth.c:1870 +#: libpq/auth.c:1878 #, c-format msgid "peer authentication is not supported on this platform" msgstr "Peer-Authentifizierung wird auf dieser Plattform nicht unterstützt" -#: libpq/auth.c:1874 +#: libpq/auth.c:1882 #, c-format msgid "could not get peer credentials: %m" msgstr "konnte Credentials von Gegenstelle nicht ermitteln: %m" -#: libpq/auth.c:1886 +#: libpq/auth.c:1894 #, c-format msgid "could not look up local user ID %ld: %s" msgstr "konnte lokale Benutzer-ID %ld nicht nachschlagen: %s" -#: libpq/auth.c:1988 +#: libpq/auth.c:1996 #, c-format msgid "error from underlying PAM layer: %s" msgstr "Fehler von der unteren PAM-Ebene: %s" -#: libpq/auth.c:1999 +#: libpq/auth.c:2007 #, c-format msgid "unsupported PAM conversation %d/\"%s\"" msgstr "nicht unterstützte PAM-Conversation: %d/»%s«" -#: libpq/auth.c:2056 +#: libpq/auth.c:2064 #, c-format msgid "could not create PAM authenticator: %s" msgstr "konnte PAM-Authenticator nicht erzeugen: %s" -#: libpq/auth.c:2067 +#: libpq/auth.c:2075 #, c-format msgid "pam_set_item(PAM_USER) failed: %s" msgstr "pam_set_item(PAM_USER) fehlgeschlagen: %s" -#: libpq/auth.c:2099 +#: libpq/auth.c:2107 #, c-format msgid "pam_set_item(PAM_RHOST) failed: %s" msgstr "pam_set_item(PAM_RHOST) fehlgeschlagen: %s" -#: libpq/auth.c:2111 +#: libpq/auth.c:2119 #, c-format msgid "pam_set_item(PAM_CONV) failed: %s" msgstr "pam_set_item(PAM_CONV) fehlgeschlagen: %s" -#: libpq/auth.c:2124 +#: libpq/auth.c:2132 #, c-format msgid "pam_authenticate failed: %s" msgstr "pam_authenticate fehlgeschlagen: %s" -#: libpq/auth.c:2137 +#: libpq/auth.c:2145 #, c-format msgid "pam_acct_mgmt failed: %s" msgstr "pam_acct_mgmt fehlgeschlagen: %s" -#: libpq/auth.c:2148 +#: libpq/auth.c:2156 #, c-format msgid "could not release PAM authenticator: %s" msgstr "konnte PAM-Authenticator nicht freigeben: %s" -#: libpq/auth.c:2228 +#: libpq/auth.c:2236 #, c-format msgid "could not initialize LDAP: error code %d" msgstr "konnte LDAP nicht initialisieren: Fehlercode %d" -#: libpq/auth.c:2265 +#: libpq/auth.c:2273 #, c-format msgid "could not extract domain name from ldapbasedn" msgstr "konnte keinen Domain-Namen aus ldapbasedn herauslesen" -#: libpq/auth.c:2273 +#: libpq/auth.c:2281 #, c-format msgid "LDAP authentication could not find DNS SRV records for \"%s\"" msgstr "LDAP-Authentifizierung konnte keine DNS-SRV-Einträge für »%s« finden" -#: libpq/auth.c:2275 +#: libpq/auth.c:2283 #, c-format msgid "Set an LDAP server name explicitly." msgstr "Geben Sie einen LDAP-Servernamen explizit an." -#: libpq/auth.c:2327 +#: libpq/auth.c:2335 #, c-format msgid "could not initialize LDAP: %s" msgstr "konnte LDAP nicht initialisieren: %s" -#: libpq/auth.c:2337 +#: libpq/auth.c:2345 #, c-format msgid "ldaps not supported with this LDAP library" msgstr "ldaps wird mit dieser LDAP-Bibliothek nicht unterstützt" -#: libpq/auth.c:2345 +#: libpq/auth.c:2353 #, c-format msgid "could not initialize LDAP: %m" msgstr "konnte LDAP nicht initialisieren: %m" -#: libpq/auth.c:2355 +#: libpq/auth.c:2363 #, c-format msgid "could not set LDAP protocol version: %s" msgstr "konnte LDAP-Protokollversion nicht setzen: %s" -#: libpq/auth.c:2371 +#: libpq/auth.c:2379 #, c-format msgid "could not start LDAP TLS session: %s" msgstr "konnte LDAP-TLS-Sitzung nicht starten: %s" -#: libpq/auth.c:2448 +#: libpq/auth.c:2456 #, c-format msgid "LDAP server not specified, and no ldapbasedn" msgstr "LDAP-Server nicht angegeben, und kein ldapbasedn" -#: libpq/auth.c:2455 +#: libpq/auth.c:2463 #, c-format msgid "LDAP server not specified" msgstr "LDAP-Server nicht angegeben" -#: libpq/auth.c:2517 +#: libpq/auth.c:2525 #, c-format msgid "invalid character in user name for LDAP authentication" msgstr "ungültiges Zeichen im Benutzernamen für LDAP-Authentifizierung" -#: libpq/auth.c:2534 +#: libpq/auth.c:2542 #, c-format msgid "could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s" msgstr "erstes LDAP-Binden für ldapbinddn »%s« auf Server »%s« fehlgeschlagen: %s" -#: libpq/auth.c:2564 +#: libpq/auth.c:2572 #, c-format msgid "could not search LDAP for filter \"%s\" on server \"%s\": %s" msgstr "konnte LDAP nicht mit Filter »%s« auf Server »%s« durchsuchen: %s" -#: libpq/auth.c:2580 +#: libpq/auth.c:2588 #, c-format msgid "LDAP user \"%s\" does not exist" msgstr "LDAP-Benutzer »%s« existiert nicht" -#: libpq/auth.c:2581 +#: libpq/auth.c:2589 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned no entries." msgstr "LDAP-Suche nach Filter »%s« auf Server »%s« gab keine Einträge zurück." -#: libpq/auth.c:2585 +#: libpq/auth.c:2593 #, c-format msgid "LDAP user \"%s\" is not unique" msgstr "LDAP-Benutzer »%s« ist nicht eindeutig" -#: libpq/auth.c:2586 +#: libpq/auth.c:2594 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned %d entry." msgid_plural "LDAP search for filter \"%s\" on server \"%s\" returned %d entries." msgstr[0] "LDAP-Suche nach Filter »%s« auf Server »%s« gab %d Eintrag zurück." msgstr[1] "LDAP-Suche nach Filter »%s« auf Server »%s« gab %d Einträge zurück." -#: libpq/auth.c:2606 +#: libpq/auth.c:2614 #, c-format msgid "could not get dn for the first entry matching \"%s\" on server \"%s\": %s" msgstr "konnte DN fũr den ersten Treffer für »%s« auf Server »%s« nicht lesen: %s" -#: libpq/auth.c:2633 +#: libpq/auth.c:2641 #, c-format msgid "LDAP login failed for user \"%s\" on server \"%s\": %s" msgstr "LDAP-Login fehlgeschlagen für Benutzer »%s« auf Server »%s«: %s" -#: libpq/auth.c:2665 +#: libpq/auth.c:2673 #, c-format msgid "LDAP diagnostics: %s" msgstr "LDAP-Diagnostik: %s" -#: libpq/auth.c:2703 +#: libpq/auth.c:2711 #, c-format msgid "certificate authentication failed for user \"%s\": client certificate contains no user name" msgstr "Zertifikatauthentifizierung für Benutzer »%s« fehlgeschlagen: Client-Zertifikat enthält keinen Benutzernamen" -#: libpq/auth.c:2724 +#: libpq/auth.c:2732 #, c-format msgid "certificate authentication failed for user \"%s\": unable to retrieve subject DN" msgstr "Zertifikatauthentifizierung für Benutzer »%s« fehlgeschlagen: konnte Subject-DN nicht abfragen" -#: libpq/auth.c:2747 +#: libpq/auth.c:2755 #, c-format msgid "certificate validation (clientcert=verify-full) failed for user \"%s\": DN mismatch" msgstr "Zertifikatüberprüfung (clientcert=verify=full) für Benutzer »%s« fehlgeschlagen: DN stimmt nicht überein" -#: libpq/auth.c:2752 +#: libpq/auth.c:2760 #, c-format msgid "certificate validation (clientcert=verify-full) failed for user \"%s\": CN mismatch" msgstr "Zertifikatüberprüfung (clientcert=verify=full) für Benutzer »%s« fehlgeschlagen: CN stimmt nicht überein" -#: libpq/auth.c:2854 +#: libpq/auth.c:2862 #, c-format msgid "RADIUS server not specified" msgstr "RADIUS-Server nicht angegeben" -#: libpq/auth.c:2861 +#: libpq/auth.c:2869 #, c-format msgid "RADIUS secret not specified" msgstr "RADIUS-Geheimnis nicht angegeben" -#: libpq/auth.c:2875 +#: libpq/auth.c:2883 #, c-format msgid "RADIUS authentication does not support passwords longer than %d characters" msgstr "RADIUS-Authentifizierung unterstützt keine Passwörter länger als %d Zeichen" -#: libpq/auth.c:2977 libpq/hba.c:2352 +#: libpq/auth.c:2985 libpq/hba.c:2352 #, c-format msgid "could not translate RADIUS server name \"%s\" to address: %s" msgstr "konnte RADIUS-Servername »%s« nicht in Adresse übersetzen: %s" -#: libpq/auth.c:2991 +#: libpq/auth.c:2999 #, c-format msgid "could not generate random encryption vector" msgstr "konnte zufälligen Verschlüsselungsvektor nicht erzeugen" -#: libpq/auth.c:3028 +#: libpq/auth.c:3036 #, c-format msgid "could not perform MD5 encryption of password: %s" msgstr "konnte MD5-Verschlüsselung des Passworts nicht durchführen: %s" -#: libpq/auth.c:3055 +#: libpq/auth.c:3063 #, c-format msgid "could not create RADIUS socket: %m" msgstr "konnte RADIUS-Socket nicht erstellen: %m" -#: libpq/auth.c:3071 +#: libpq/auth.c:3079 #, c-format msgid "could not bind local RADIUS socket: %m" msgstr "konnte lokales RADIUS-Socket nicht binden: %m" -#: libpq/auth.c:3081 +#: libpq/auth.c:3089 #, c-format msgid "could not send RADIUS packet: %m" msgstr "konnte RADIUS-Paket nicht senden: %m" -#: libpq/auth.c:3115 libpq/auth.c:3141 +#: libpq/auth.c:3123 libpq/auth.c:3149 #, c-format msgid "timeout waiting for RADIUS response from %s" msgstr "Zeitüberschreitung beim Warten auf RADIUS-Antwort von %s" -#: libpq/auth.c:3134 +#: libpq/auth.c:3142 #, c-format msgid "could not check status on RADIUS socket: %m" msgstr "konnte Status des RADIUS-Sockets nicht prüfen: %m" -#: libpq/auth.c:3164 +#: libpq/auth.c:3172 #, c-format msgid "could not read RADIUS response: %m" msgstr "konnte RADIUS-Antwort nicht lesen: %m" -#: libpq/auth.c:3172 +#: libpq/auth.c:3180 #, c-format msgid "RADIUS response from %s was sent from incorrect port: %d" msgstr "RADIUS-Antwort von %s wurde von falschem Port gesendet: %d" -#: libpq/auth.c:3180 +#: libpq/auth.c:3188 #, c-format msgid "RADIUS response from %s too short: %d" msgstr "RADIUS-Antwort von %s zu kurz: %d" -#: libpq/auth.c:3187 +#: libpq/auth.c:3195 #, c-format msgid "RADIUS response from %s has corrupt length: %d (actual length %d)" msgstr "RADIUS-Antwort von %s hat verfälschte Länge: %d (tatsächliche Länge %d)" -#: libpq/auth.c:3195 +#: libpq/auth.c:3203 #, c-format msgid "RADIUS response from %s is to a different request: %d (should be %d)" msgstr "RADIUS-Antwort von %s unterscheidet sich von Anfrage: %d (sollte %d sein)" -#: libpq/auth.c:3220 +#: libpq/auth.c:3228 #, c-format msgid "could not perform MD5 encryption of received packet: %s" msgstr "konnte MD5-Verschlüsselung des empfangenen Pakets nicht durchführen: %s" -#: libpq/auth.c:3230 +#: libpq/auth.c:3238 #, c-format msgid "RADIUS response from %s has incorrect MD5 signature" msgstr "RADIUS-Antwort von %s hat falsche MD5-Signatur" -#: libpq/auth.c:3248 +#: libpq/auth.c:3256 #, c-format msgid "RADIUS response from %s has invalid code (%d) for user \"%s\"" msgstr "RADIUS-Antwort von %s hat ungültigen Code (%d) für Benutzer »%s«" @@ -15639,44 +15651,39 @@ msgstr "private Schlüsseldatei »%s« erlaubt Zugriff von Gruppe oder Welt" msgid "File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root." msgstr "Dateirechte müssen u=rw (0600) oder weniger sein, wenn der Eigentümer der Datenbankbenutzer ist, oder u=rw,g=r (0640) oder weniger, wenn der Eigentümer »root« ist." -#: libpq/be-secure-gssapi.c:201 +#: libpq/be-secure-gssapi.c:208 msgid "GSSAPI wrap error" msgstr "GSSAPI-Wrap-Fehler" -#: libpq/be-secure-gssapi.c:208 +#: libpq/be-secure-gssapi.c:215 #, c-format msgid "outgoing GSSAPI message would not use confidentiality" msgstr "ausgehende GSSAPI-Nachricht würde keine Vertraulichkeit verwenden" -#: libpq/be-secure-gssapi.c:215 libpq/be-secure-gssapi.c:634 +#: libpq/be-secure-gssapi.c:222 libpq/be-secure-gssapi.c:644 #, c-format msgid "server tried to send oversize GSSAPI packet (%zu > %zu)" msgstr "Server versuchte übergroßes GSSAPI-Paket zu senden (%zu > %zu)" -#: libpq/be-secure-gssapi.c:351 +#: libpq/be-secure-gssapi.c:358 libpq/be-secure-gssapi.c:585 #, c-format msgid "oversize GSSAPI packet sent by the client (%zu > %zu)" msgstr "übergroßes GSSAPI-Paket vom Client gesendet (%zu > %zu)" -#: libpq/be-secure-gssapi.c:389 +#: libpq/be-secure-gssapi.c:396 msgid "GSSAPI unwrap error" msgstr "GSSAPI-Unwrap-Fehler" -#: libpq/be-secure-gssapi.c:396 +#: libpq/be-secure-gssapi.c:403 #, c-format msgid "incoming GSSAPI message did not use confidentiality" msgstr "eingehende GSSAPI-Nachricht verwendete keine Vertraulichkeit" -#: libpq/be-secure-gssapi.c:575 -#, c-format -msgid "oversize GSSAPI packet sent by the client (%zu > %d)" -msgstr "übergroßes GSSAPI-Paket vom Client gesendet (%zu > %d)" - -#: libpq/be-secure-gssapi.c:600 +#: libpq/be-secure-gssapi.c:610 msgid "could not accept GSSAPI security context" msgstr "konnte GSSAPI-Sicherheitskontext nicht akzeptieren" -#: libpq/be-secure-gssapi.c:701 +#: libpq/be-secure-gssapi.c:728 msgid "GSSAPI size check error" msgstr "GSSAPI-Fehler bei der Größenprüfung" @@ -16801,7 +16808,7 @@ msgstr "unbenanntes Portal mit Parametern: %s" msgid "FULL JOIN is only supported with merge-joinable or hash-joinable join conditions" msgstr "FULL JOIN wird nur für Merge- oder Hash-Verbund-fähige Verbundbedingungen unterstützt" -#: optimizer/plan/createplan.c:7175 parser/parse_merge.c:203 +#: optimizer/plan/createplan.c:7177 parser/parse_merge.c:203 #: rewrite/rewriteHandler.c:1680 #, c-format msgid "cannot execute MERGE on relation \"%s\"" @@ -19798,32 +19805,32 @@ msgstr "Fehlgeschlagener Systemaufruf war MapViewOfFileEx." msgid "autovacuum worker took too long to start; canceled" msgstr "Autovacuum-Worker benötigte zu lange zum Starten; abgebrochen" -#: postmaster/autovacuum.c:2203 +#: postmaster/autovacuum.c:2218 #, c-format msgid "autovacuum: dropping orphan temp table \"%s.%s.%s\"" msgstr "Autovacuum: lösche verwaiste temporäre Tabelle »%s.%s.%s«" -#: postmaster/autovacuum.c:2439 +#: postmaster/autovacuum.c:2461 #, c-format msgid "automatic vacuum of table \"%s.%s.%s\"" msgstr "automatisches Vacuum der Tabelle »%s.%s.%s«" -#: postmaster/autovacuum.c:2442 +#: postmaster/autovacuum.c:2464 #, c-format msgid "automatic analyze of table \"%s.%s.%s\"" msgstr "automatisches Analysieren der Tabelle »%s.%s.%s«" -#: postmaster/autovacuum.c:2636 +#: postmaster/autovacuum.c:2660 #, c-format msgid "processing work entry for relation \"%s.%s.%s\"" msgstr "verarbeite Arbeitseintrag für Relation »%s.%s.%s«" -#: postmaster/autovacuum.c:3254 +#: postmaster/autovacuum.c:3291 #, c-format msgid "autovacuum not started because of misconfiguration" msgstr "Autovacuum wegen Fehlkonfiguration nicht gestartet" -#: postmaster/autovacuum.c:3255 +#: postmaster/autovacuum.c:3292 #, c-format msgid "Enable the \"track_counts\" option." msgstr "Schalten Sie die Option »track_counts« ein." @@ -19880,24 +19887,24 @@ msgid_plural "Up to %d background workers can be registered with the current set msgstr[0] "Mit den aktuellen Einstellungen können bis zu %d Background-Worker registriert werden." msgstr[1] "Mit den aktuellen Einstellungen können bis zu %d Background-Worker registriert werden." -#: postmaster/bgworker.c:931 postmaster/checkpointer.c:445 +#: postmaster/bgworker.c:931 postmaster/checkpointer.c:448 #, c-format msgid "Consider increasing the configuration parameter \"%s\"." msgstr "Erhöhen Sie eventuell den Konfigurationsparameter »%s«." -#: postmaster/checkpointer.c:441 +#: postmaster/checkpointer.c:444 #, c-format msgid "checkpoints are occurring too frequently (%d second apart)" msgid_plural "checkpoints are occurring too frequently (%d seconds apart)" msgstr[0] "Checkpoints passieren zu oft (alle %d Sekunde)" msgstr[1] "Checkpoints passieren zu oft (alle %d Sekunden)" -#: postmaster/checkpointer.c:1067 +#: postmaster/checkpointer.c:1073 #, c-format msgid "checkpoint request failed" msgstr "Checkpoint-Anforderung fehlgeschlagen" -#: postmaster/checkpointer.c:1068 +#: postmaster/checkpointer.c:1074 #, c-format msgid "Consult recent messages in the server log for details." msgstr "Einzelheiten finden Sie in den letzten Meldungen im Serverlog." @@ -20703,22 +20710,22 @@ msgstr "konnte Daten nicht an Shared-Memory-Queue senden" msgid "logical replication apply worker will serialize the remaining changes of remote transaction %u to a file" msgstr "Apply-Worker für logische Replikation wird die restlichen Änderungen der Remote-Transaktion %u in eine Datei serialisieren" -#: replication/logical/decode.c:177 replication/logical/logical.c:141 +#: replication/logical/decode.c:177 replication/logical/logical.c:143 #, c-format msgid "logical decoding on standby requires \"wal_level\" >= \"logical\" on the primary" msgstr "logische Dekodierung auf dem Standby-Server erfordert »wal_level« >= »logical« auf dem Primärserver" -#: replication/logical/launcher.c:334 +#: replication/logical/launcher.c:347 #, c-format msgid "cannot start logical replication workers when max_replication_slots = 0" msgstr "Arbeitsprozesse für logische Replikation können nicht gestartet werden, wenn max_replication_slots = 0" -#: replication/logical/launcher.c:427 +#: replication/logical/launcher.c:440 #, c-format msgid "out of logical replication worker slots" msgstr "alle Slots für Arbeitsprozesse für logische Replikation belegt" -#: replication/logical/launcher.c:428 replication/logical/launcher.c:514 +#: replication/logical/launcher.c:441 replication/logical/launcher.c:527 #: replication/slot.c:1524 storage/lmgr/lock.c:985 storage/lmgr/lock.c:1023 #: storage/lmgr/lock.c:2836 storage/lmgr/lock.c:4221 storage/lmgr/lock.c:4286 #: storage/lmgr/lock.c:4636 storage/lmgr/predicate.c:2469 @@ -20727,111 +20734,111 @@ msgstr "alle Slots für Arbeitsprozesse für logische Replikation belegt" msgid "You might need to increase \"%s\"." msgstr "Sie müssen möglicherweise »%s« erhöhen." -#: replication/logical/launcher.c:513 +#: replication/logical/launcher.c:526 #, c-format msgid "out of background worker slots" msgstr "alle Slots für Background-Worker belegt" -#: replication/logical/launcher.c:720 +#: replication/logical/launcher.c:733 #, c-format msgid "logical replication worker slot %d is empty, cannot attach" msgstr "Arbeitsprozess-Slot %d für logische Replikation ist leer, kann nicht zugeteilt werden" -#: replication/logical/launcher.c:729 +#: replication/logical/launcher.c:742 #, c-format msgid "logical replication worker slot %d is already used by another worker, cannot attach" msgstr "Arbeitsprozess-Slot %d für logische Replikation wird schon von einem anderen Arbeitsprozess verwendet, kann nicht zugeteilt werden" -#: replication/logical/logical.c:121 +#: replication/logical/logical.c:123 #, c-format msgid "logical decoding requires \"wal_level\" >= \"logical\"" msgstr "logische Dekodierung erfordert »wal_level« >= »logical«" -#: replication/logical/logical.c:126 +#: replication/logical/logical.c:128 #, c-format msgid "logical decoding requires a database connection" msgstr "logische Dekodierung benötigt eine Datenbankverbindung" -#: replication/logical/logical.c:365 replication/logical/logical.c:519 +#: replication/logical/logical.c:367 replication/logical/logical.c:521 #, c-format msgid "cannot use physical replication slot for logical decoding" msgstr "physischer Replikations-Slot kann nicht für logisches Dekodieren verwendet werden" -#: replication/logical/logical.c:370 replication/logical/logical.c:529 +#: replication/logical/logical.c:372 replication/logical/logical.c:531 #, c-format msgid "replication slot \"%s\" was not created in this database" msgstr "Replikations-Slot »%s« wurde nicht in dieser Datenbank erzeugt" -#: replication/logical/logical.c:377 +#: replication/logical/logical.c:379 #, c-format msgid "cannot create logical replication slot in transaction that has performed writes" msgstr "logischer Replikations-Slot kann nicht in einer Transaktion erzeugt werden, die Schreibvorgänge ausgeführt hat" -#: replication/logical/logical.c:540 +#: replication/logical/logical.c:542 #, c-format msgid "cannot use replication slot \"%s\" for logical decoding" msgstr "physischer Replikations-Slot »%s« kann nicht für logisches Dekodieren verwendet werden" -#: replication/logical/logical.c:542 replication/slot.c:798 +#: replication/logical/logical.c:544 replication/slot.c:798 #: replication/slot.c:829 #, c-format msgid "This replication slot is being synchronized from the primary server." msgstr "Dieser Replikations-Slot wird vom Primärserver synchronisiert." -#: replication/logical/logical.c:543 +#: replication/logical/logical.c:545 #, c-format msgid "Specify another replication slot." msgstr "Geben Sie einen anderen Replikations-Slot an." -#: replication/logical/logical.c:554 replication/logical/logical.c:561 +#: replication/logical/logical.c:556 replication/logical/logical.c:563 #, c-format msgid "can no longer get changes from replication slot \"%s\"" msgstr "aus Replikations-Slot »%s« können keine Änderungen mehr gelesen werden" -#: replication/logical/logical.c:556 +#: replication/logical/logical.c:558 #, c-format msgid "This slot has been invalidated because it exceeded the maximum reserved size." msgstr "Dieser Slot wurde ungültig gemacht, weil er die maximale reservierte Größe überschritten hat." -#: replication/logical/logical.c:563 +#: replication/logical/logical.c:565 #, c-format msgid "This slot has been invalidated because it was conflicting with recovery." msgstr "Dieser Slot wurde ungültig gemacht, weil er mit der Wiederherstellung kollidierte." -#: replication/logical/logical.c:628 +#: replication/logical/logical.c:630 #, c-format msgid "starting logical decoding for slot \"%s\"" msgstr "starte logisches Dekodieren für Slot »%s«" -#: replication/logical/logical.c:630 +#: replication/logical/logical.c:632 #, c-format msgid "Streaming transactions committing after %X/%X, reading WAL from %X/%X." msgstr "Streaming beginnt bei Transaktionen, die nach %X/%X committen; lese WAL ab %X/%X." -#: replication/logical/logical.c:778 +#: replication/logical/logical.c:780 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X" msgstr "Slot »%s«, Ausgabe-Plugin »%s«, im Callback %s, zugehörige LSN %X/%X" -#: replication/logical/logical.c:784 +#: replication/logical/logical.c:786 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback" msgstr "Slot »%s«, Ausgabe-Plugin »%s«, im Callback %s" -#: replication/logical/logical.c:955 replication/logical/logical.c:1000 -#: replication/logical/logical.c:1045 replication/logical/logical.c:1091 +#: replication/logical/logical.c:957 replication/logical/logical.c:1002 +#: replication/logical/logical.c:1047 replication/logical/logical.c:1093 #, c-format msgid "logical replication at prepare time requires a %s callback" msgstr "logische Replikation bei PREPARE TRANSACTION benötigt einen %s-Callback" -#: replication/logical/logical.c:1323 replication/logical/logical.c:1372 -#: replication/logical/logical.c:1413 replication/logical/logical.c:1499 -#: replication/logical/logical.c:1548 +#: replication/logical/logical.c:1325 replication/logical/logical.c:1374 +#: replication/logical/logical.c:1415 replication/logical/logical.c:1501 +#: replication/logical/logical.c:1550 #, c-format msgid "logical streaming requires a %s callback" msgstr "logisches Streaming benötigt einen %s-Callback" -#: replication/logical/logical.c:1458 +#: replication/logical/logical.c:1460 #, c-format msgid "logical streaming at prepare time requires a %s callback" msgstr "logisches Streaming bei PREPARE TRANSACTION benötigt einen %s-Callback" @@ -20928,7 +20935,7 @@ msgid "could not find free replication state slot for replication origin with ID msgstr "konnte keinen freien Replication-State-Slot für Replication-Origin mit ID %d finden" #: replication/logical/origin.c:957 replication/logical/origin.c:1158 -#: replication/slot.c:2401 +#: replication/slot.c:2397 #, c-format msgid "Increase \"max_replication_slots\" and try again." msgstr "Erhöhen Sie »max_replication_slots« und versuchen Sie es erneut." @@ -20981,57 +20988,52 @@ msgstr "Zielrelation für logische Replikation »%s.%s« verwendet Systemspalten msgid "logical replication target relation \"%s.%s\" does not exist" msgstr "Zielrelation für logische Replikation »%s.%s« existiert nicht" -#: replication/logical/reorderbuffer.c:3999 +#: replication/logical/reorderbuffer.c:4129 #, c-format msgid "could not write to data file for XID %u: %m" msgstr "konnte nicht in Datendatei für XID %u schreiben: %m" -#: replication/logical/reorderbuffer.c:4345 -#: replication/logical/reorderbuffer.c:4370 +#: replication/logical/reorderbuffer.c:4475 +#: replication/logical/reorderbuffer.c:4500 #, c-format msgid "could not read from reorderbuffer spill file: %m" msgstr "konnte nicht aus Reorder-Buffer-Spill-Datei lesen: %m" -#: replication/logical/reorderbuffer.c:4349 -#: replication/logical/reorderbuffer.c:4374 +#: replication/logical/reorderbuffer.c:4479 +#: replication/logical/reorderbuffer.c:4504 #, c-format msgid "could not read from reorderbuffer spill file: read %d instead of %u bytes" msgstr "konnte nicht aus Reorder-Buffer-Spill-Datei lesen: %d statt %u Bytes gelesen" -#: replication/logical/reorderbuffer.c:4624 +#: replication/logical/reorderbuffer.c:4754 #, c-format msgid "could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m" msgstr "konnte Datei »%s« nicht löschen, bei Löschen von pg_replslot/%s/xid*: %m" -#: replication/logical/reorderbuffer.c:5120 +#: replication/logical/reorderbuffer.c:5250 #, c-format msgid "could not read from file \"%s\": read %d instead of %d bytes" msgstr "konnte nicht aus Datei »%s« lesen: %d statt %d Bytes gelesen" -#: replication/logical/slotsync.c:215 +#: replication/logical/slotsync.c:215 replication/logical/slotsync.c:579 #, c-format -msgid "could not synchronize replication slot \"%s\" because remote slot precedes local slot" -msgstr "konnte Replikations-Slot »%s« nicht synchronisieren, weil der Remote-Slot dem lokalen vorangeht" +msgid "could not synchronize replication slot \"%s\"" +msgstr "konnte Replikations-Slot »%s« nicht synchronisieren" #: replication/logical/slotsync.c:217 #, c-format -msgid "The remote slot has LSN %X/%X and catalog xmin %u, but the local slot has LSN %X/%X and catalog xmin %u." -msgstr "Der Remote-Slot hat LSN %X/%X und Katalog-xmin %u, aber der lokale Slot hat LSN %X/%X und Katalog-xmin %u." +msgid "Synchronization could lead to data loss, because the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u." +msgstr "Synchronisation könnte zu Datenverlust führen, weil der Remote-Slot WAL bei LSN %X/%X und Katalog-xmin %u benötigt, aber der Standby LSN %X/%X und Katalog-xmin %u hat." #: replication/logical/slotsync.c:459 #, c-format msgid "dropped replication slot \"%s\" of database with OID %u" msgstr "Replikations-Slot »%s« von Datenbank mit OID %u wurde gelöscht" -#: replication/logical/slotsync.c:579 -#, c-format -msgid "could not synchronize replication slot \"%s\"" -msgstr "konnte Replikations-Slot »%s« nicht synchronisieren" - #: replication/logical/slotsync.c:580 #, c-format -msgid "Logical decoding could not find consistent point from local slot's LSN %X/%X." -msgstr "Logisches Dekodieren konnte keinen konsistenten Punkt von der LSN des lokalen Slots %X/%X finden." +msgid "Synchronization could lead to data loss, because the standby could not build a consistent snapshot to decode WALs at LSN %X/%X." +msgstr "Synchronisation könnte zu Datenverlust führen, weil der Standby keinen konsistenten Snapshot zum Dekodieren von WAL bei LSN %X/%X bauen konnte." #: replication/logical/slotsync.c:589 #, c-format @@ -21082,7 +21084,7 @@ msgstr "Replikations-Slot »%s«, der in »%s« angegeben ist, existiert auf dem msgid "replication slot synchronization requires \"%s\" to be specified in \"%s\"" msgstr "Replikations-Slot-Synchronisierung erfordert, dass »%s« in »%s« angegeben wird" -#: replication/logical/slotsync.c:1050 +#: replication/logical/slotsync.c:1048 #, c-format msgid "replication slot synchronization requires \"wal_level\" >= \"logical\"" msgstr "Replikations-Slot-Synchronisierung erfordert »wal_level« >= »logical«" @@ -21148,58 +21150,58 @@ msgid_plural "exported logical decoding snapshot: \"%s\" with %u transaction IDs msgstr[0] "logischer Dekodierungs-Snapshot exportiert: »%s« mit %u Transaktions-ID" msgstr[1] "logischer Dekodierungs-Snapshot exportiert: »%s« mit %u Transaktions-IDs" -#: replication/logical/snapbuild.c:1443 replication/logical/snapbuild.c:1540 -#: replication/logical/snapbuild.c:2056 +#: replication/logical/snapbuild.c:1451 replication/logical/snapbuild.c:1548 +#: replication/logical/snapbuild.c:2064 #, c-format msgid "logical decoding found consistent point at %X/%X" msgstr "logisches Dekodieren fand konsistenten Punkt bei %X/%X" -#: replication/logical/snapbuild.c:1445 +#: replication/logical/snapbuild.c:1453 #, c-format msgid "There are no running transactions." msgstr "Keine laufenden Transaktionen." -#: replication/logical/snapbuild.c:1492 +#: replication/logical/snapbuild.c:1500 #, c-format msgid "logical decoding found initial starting point at %X/%X" msgstr "logisches Dekodieren fand initialen Startpunkt bei %X/%X" -#: replication/logical/snapbuild.c:1494 replication/logical/snapbuild.c:1518 +#: replication/logical/snapbuild.c:1502 replication/logical/snapbuild.c:1526 #, c-format msgid "Waiting for transactions (approximately %d) older than %u to end." msgstr "Warten auf Abschluss der Transaktionen (ungefähr %d), die älter als %u sind." -#: replication/logical/snapbuild.c:1516 +#: replication/logical/snapbuild.c:1524 #, c-format msgid "logical decoding found initial consistent point at %X/%X" msgstr "logisches Dekodieren fand initialen konsistenten Punkt bei %X/%X" -#: replication/logical/snapbuild.c:1542 +#: replication/logical/snapbuild.c:1550 #, c-format msgid "There are no old transactions anymore." msgstr "Es laufen keine alten Transaktionen mehr." -#: replication/logical/snapbuild.c:1943 +#: replication/logical/snapbuild.c:1951 #, c-format msgid "snapbuild state file \"%s\" has wrong magic number: %u instead of %u" msgstr "Scanbuild-State-Datei »%s« hat falsche magische Zahl %u statt %u" -#: replication/logical/snapbuild.c:1949 +#: replication/logical/snapbuild.c:1957 #, c-format msgid "snapbuild state file \"%s\" has unsupported version: %u instead of %u" msgstr "Snapbuild-State-Datei »%s« hat nicht unterstützte Version: %u statt %u" -#: replication/logical/snapbuild.c:1990 +#: replication/logical/snapbuild.c:1998 #, c-format msgid "checksum mismatch for snapbuild state file \"%s\": is %u, should be %u" msgstr "Prüfsummenfehler bei Snapbuild-State-Datei »%s«: ist %u, sollte %u sein" -#: replication/logical/snapbuild.c:2058 +#: replication/logical/snapbuild.c:2066 #, c-format msgid "Logical decoding will begin using saved snapshot." msgstr "Logische Dekodierung beginnt mit gespeichertem Snapshot." -#: replication/logical/snapbuild.c:2165 +#: replication/logical/snapbuild.c:2173 #, c-format msgid "could not parse file name \"%s\"" msgstr "konnte Dateinamen »%s« nicht parsen" @@ -21209,52 +21211,52 @@ msgstr "konnte Dateinamen »%s« nicht parsen" msgid "logical replication table synchronization worker for subscription \"%s\", table \"%s\" has finished" msgstr "Arbeitsprozess für logische Replikation für Tabellensynchronisation für Subskription »%s«, Tabelle »%s« hat abgeschlossen" -#: replication/logical/tablesync.c:641 +#: replication/logical/tablesync.c:667 #, c-format msgid "logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled" msgstr "Apply-Worker für logische Replikation für Subskription »%s« wird neu starten, damit two_phase eingeschaltet werden kann" -#: replication/logical/tablesync.c:827 replication/logical/tablesync.c:969 +#: replication/logical/tablesync.c:853 replication/logical/tablesync.c:995 #, c-format msgid "could not fetch table info for table \"%s.%s\" from publisher: %s" msgstr "konnte Tabelleninformationen für Tabelle »%s.%s« nicht vom Publikationsserver holen: %s" -#: replication/logical/tablesync.c:834 +#: replication/logical/tablesync.c:860 #, c-format msgid "table \"%s.%s\" not found on publisher" msgstr "Tabelle »%s.%s« nicht auf dem Publikationsserver gefunden" -#: replication/logical/tablesync.c:892 +#: replication/logical/tablesync.c:918 #, c-format msgid "could not fetch column list info for table \"%s.%s\" from publisher: %s" msgstr "konnte Spaltenlisteninformationen für Tabelle »%s.%s« nicht vom Publikationsserver holen: %s" -#: replication/logical/tablesync.c:1071 +#: replication/logical/tablesync.c:1097 #, c-format msgid "could not fetch table WHERE clause info for table \"%s.%s\" from publisher: %s" msgstr "konnte WHERE-Klausel-Informationen für Tabelle »%s.%s« nicht vom Publikationsserver holen: %s" -#: replication/logical/tablesync.c:1230 +#: replication/logical/tablesync.c:1256 #, c-format msgid "could not start initial contents copy for table \"%s.%s\": %s" msgstr "konnte Kopieren des Anfangsinhalts für Tabelle »%s.%s« nicht starten: %s" -#: replication/logical/tablesync.c:1429 +#: replication/logical/tablesync.c:1455 #, c-format msgid "table copy could not start transaction on publisher: %s" msgstr "beim Kopieren der Tabelle konnte die Transaktion auf dem Publikationsserver nicht gestartet werden: %s" -#: replication/logical/tablesync.c:1472 +#: replication/logical/tablesync.c:1498 #, c-format msgid "replication origin \"%s\" already exists" msgstr "Replication-Origin »%s« existiert bereits" -#: replication/logical/tablesync.c:1505 replication/logical/worker.c:2363 +#: replication/logical/tablesync.c:1531 replication/logical/worker.c:2363 #, c-format msgid "user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"" msgstr "Benutzer »%s« kann nicht in eine Relation mit Sicherheit auf Zeilenebene replizieren: »%s«" -#: replication/logical/tablesync.c:1518 +#: replication/logical/tablesync.c:1544 #, c-format msgid "table copy could not finish transaction on publisher: %s" msgstr "beim Kopieren der Tabelle konnte die Transaktion auf dem Publikationsserver nicht beenden werden: %s" @@ -21334,82 +21336,82 @@ msgstr "Arbeitsprozess für logische Replikation für Subskription »%s« wird n msgid "subscription has no replication slot set" msgstr "für die Subskription ist kein Replikations-Slot gesetzt" -#: replication/logical/worker.c:4612 +#: replication/logical/worker.c:4620 #, c-format msgid "logical replication worker for subscription %u will not start because the subscription was removed during startup" msgstr "Arbeitsprozess für logische Replikation für Subskription %u« wird nicht starten, weil die Subskription während des Starts entfernt wurde" -#: replication/logical/worker.c:4628 +#: replication/logical/worker.c:4636 #, c-format msgid "logical replication worker for subscription \"%s\" will not start because the subscription was disabled during startup" msgstr "Arbeitsprozess für logische Replikation für Subskription »%s« wird nicht starten, weil die Subskription während des Starts deaktiviert wurde" -#: replication/logical/worker.c:4652 +#: replication/logical/worker.c:4660 #, c-format msgid "logical replication table synchronization worker for subscription \"%s\", table \"%s\" has started" msgstr "Arbeitsprozess für logische Replikation für Tabellensynchronisation für Subskription »%s«, Tabelle »%s« hat gestartet" -#: replication/logical/worker.c:4657 +#: replication/logical/worker.c:4665 #, c-format msgid "logical replication apply worker for subscription \"%s\" has started" msgstr "Apply-Worker für logische Replikation für Subskription »%s« hat gestartet" -#: replication/logical/worker.c:4779 +#: replication/logical/worker.c:4795 #, c-format msgid "subscription \"%s\" has been disabled because of an error" msgstr "Subskription »%s« wurde wegen eines Fehlers deaktiviert" -#: replication/logical/worker.c:4827 +#: replication/logical/worker.c:4843 #, c-format msgid "logical replication starts skipping transaction at LSN %X/%X" msgstr "logische Replikation beginnt Überspringen von Transaktion bei %X/%X" -#: replication/logical/worker.c:4841 +#: replication/logical/worker.c:4857 #, c-format msgid "logical replication completed skipping transaction at LSN %X/%X" msgstr "logische Replikation beendet Überspringen von Transaktion bei %X/%X" -#: replication/logical/worker.c:4923 +#: replication/logical/worker.c:4945 #, c-format msgid "skip-LSN of subscription \"%s\" cleared" msgstr "Skip-LSN von Subskription »%s« gelöscht" -#: replication/logical/worker.c:4924 +#: replication/logical/worker.c:4946 #, c-format msgid "Remote transaction's finish WAL location (LSN) %X/%X did not match skip-LSN %X/%X." msgstr "Die WAL-Endposition (LSN) %X/%X der Remote-Transaktion stimmte nicht mit der Skip-LSN %X/%X überein." -#: replication/logical/worker.c:4950 +#: replication/logical/worker.c:4974 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\"" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s«" -#: replication/logical/worker.c:4954 +#: replication/logical/worker.c:4978 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« in Transaktion %u" -#: replication/logical/worker.c:4959 +#: replication/logical/worker.c:4983 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u, finished at %X/%X" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« in Transaktion %u, beendet bei %X/%X" -#: replication/logical/worker.c:4970 +#: replication/logical/worker.c:4994 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« für Replikationszielrelation »%s.%s« in Transaktion %u" -#: replication/logical/worker.c:4977 +#: replication/logical/worker.c:5001 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u, finished at %X/%X" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« für Replikationszielrelation »%s.%s« in Transaktion %u, beendet bei %X/%X" -#: replication/logical/worker.c:4988 +#: replication/logical/worker.c:5012 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« für Replikationszielrelation »%s.%s« Spalte »%s« in Transaktion %u" -#: replication/logical/worker.c:4996 +#: replication/logical/worker.c:5020 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u, finished at %X/%X" msgstr "Verarbeiten empfangener Daten für Replication-Origin »%s« bei Nachrichtentyp »%s« für Replikationszielrelation »%s.%s« Spalte »%s« in Transaktion %u, beendet bei %X/%X" @@ -21514,7 +21516,7 @@ msgstr "alle Replikations-Slots sind in Benutzung" msgid "Free one or increase \"max_replication_slots\"." msgstr "Geben Sie einen frei oder erhöhen Sie »max_replication_slots«." -#: replication/slot.c:560 replication/slot.c:2467 replication/slotfuncs.c:661 +#: replication/slot.c:560 replication/slot.c:2463 replication/slotfuncs.c:661 #: utils/activity/pgstat_replslot.c:56 utils/adt/genfile.c:728 #, c-format msgid "replication slot \"%s\" does not exist" @@ -21565,7 +21567,7 @@ msgstr "Replikations-Slot »%s« kann nicht geändert werden" msgid "cannot enable failover for a replication slot on the standby" msgstr "Failover kann nicht für einen Replikations-Slot auf dem Standby eingeschaltet werden" -#: replication/slot.c:969 replication/slot.c:1927 replication/slot.c:2311 +#: replication/slot.c:969 replication/slot.c:1923 replication/slot.c:2307 #, c-format msgid "could not remove directory \"%s\"" msgstr "konnte Verzeichnis »%s« nicht löschen" @@ -21616,112 +21618,112 @@ msgstr "Prozess %d wird beendet, um Replikations-Slot »%s« freizugeben" msgid "invalidating obsolete replication slot \"%s\"" msgstr "obsoleter Replikations-Slot »%s« wird ungültig gemacht" -#: replication/slot.c:2249 +#: replication/slot.c:2245 #, c-format msgid "replication slot file \"%s\" has wrong magic number: %u instead of %u" msgstr "Replikations-Slot-Datei »%s« hat falsche magische Zahl: %u statt %u" -#: replication/slot.c:2256 +#: replication/slot.c:2252 #, c-format msgid "replication slot file \"%s\" has unsupported version %u" msgstr "Replikations-Slot-Datei »%s« hat nicht unterstützte Version %u" -#: replication/slot.c:2263 +#: replication/slot.c:2259 #, c-format msgid "replication slot file \"%s\" has corrupted length %u" msgstr "Replikations-Slot-Datei »%s« hat falsche Länge %u" -#: replication/slot.c:2299 +#: replication/slot.c:2295 #, c-format msgid "checksum mismatch for replication slot file \"%s\": is %u, should be %u" msgstr "Prüfsummenfehler bei Replikations-Slot-Datei »%s«: ist %u, sollte %u sein" -#: replication/slot.c:2335 +#: replication/slot.c:2331 #, c-format msgid "logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"" msgstr "logischer Replikations-Slot »%s« existiert, aber »wal_level« < »logical«" -#: replication/slot.c:2337 +#: replication/slot.c:2333 #, c-format msgid "Change \"wal_level\" to be \"logical\" or higher." msgstr "Ändern Sie »wal_level« in »logical« oder höher." -#: replication/slot.c:2349 +#: replication/slot.c:2345 #, c-format msgid "logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = \"off\"" msgstr "logischer Replikations-Slot »%s« existiert auf dem Standby, aber »hot_standby« = »off«" -#: replication/slot.c:2351 +#: replication/slot.c:2347 #, c-format msgid "Change \"hot_standby\" to be \"on\"." msgstr "Ändern Sie »hot_standby« auf »on«." -#: replication/slot.c:2356 +#: replication/slot.c:2352 #, c-format msgid "physical replication slot \"%s\" exists, but \"wal_level\" < \"replica\"" msgstr "physischer Replikations-Slot »%s« existiert, aber »wal_level« < »replica«" -#: replication/slot.c:2358 +#: replication/slot.c:2354 #, c-format msgid "Change \"wal_level\" to be \"replica\" or higher." msgstr "Ändern Sie »wal_level« in »replica« oder höher." -#: replication/slot.c:2400 +#: replication/slot.c:2396 #, c-format msgid "too many replication slots active before shutdown" msgstr "zu viele aktive Replikations-Slots vor dem Herunterfahren" -#: replication/slot.c:2475 +#: replication/slot.c:2471 #, c-format msgid "\"%s\" is not a physical replication slot" msgstr "»%s« ist kein physischer Replikations-Slot" -#: replication/slot.c:2654 +#: replication/slot.c:2650 #, c-format msgid "replication slot \"%s\" specified in parameter \"%s\" does not exist" msgstr "Replikations-Slot »%s«, der in Parameter »%s« angegeben ist, existiert nicht" -#: replication/slot.c:2656 replication/slot.c:2690 replication/slot.c:2705 +#: replication/slot.c:2652 replication/slot.c:2686 replication/slot.c:2701 #, c-format msgid "Logical replication is waiting on the standby associated with replication slot \"%s\"." msgstr "Logische Replikation wartet auf den Standby, der zum Replikations-Slot »%s« gehört." -#: replication/slot.c:2658 +#: replication/slot.c:2654 #, c-format msgid "Create the replication slot \"%s\" or amend parameter \"%s\"." msgstr "Erzeugen Sie den Replikations-Slot »%s« oder berichtigen Sie den Parameter »%s«." -#: replication/slot.c:2668 +#: replication/slot.c:2664 #, c-format msgid "cannot specify logical replication slot \"%s\" in parameter \"%s\"" msgstr "logischer Replikations-Slot »%s« kann nicht in Parameter »%s« angegeben werden" -#: replication/slot.c:2670 +#: replication/slot.c:2666 #, c-format msgid "Logical replication is waiting for correction on replication slot \"%s\"." msgstr "Logische Replikation wartet auf Korrektur bei Replikations-Slot »%s«." -#: replication/slot.c:2672 +#: replication/slot.c:2668 #, c-format msgid "Remove the logical replication slot \"%s\" from parameter \"%s\"." msgstr "Entfernen Sie den Replikations-Slot »%s« aus dem Parameter »%s«." -#: replication/slot.c:2688 +#: replication/slot.c:2684 #, c-format msgid "physical replication slot \"%s\" specified in parameter \"%s\" has been invalidated" msgstr "der physische Replikations-Slot »%s«, der in Parameter »%s« angegeben wurde, wurde ungültig gemacht" -#: replication/slot.c:2692 +#: replication/slot.c:2688 #, c-format msgid "Drop and recreate the replication slot \"%s\", or amend parameter \"%s\"." msgstr "Löschen Sie den Replikations-Slot »%s« und erzeugen Sie ihn neu, oder berichtigen Sie den Parameter »%s«." -#: replication/slot.c:2703 +#: replication/slot.c:2699 #, c-format msgid "replication slot \"%s\" specified in parameter \"%s\" does not have active_pid" msgstr "der Replikations-Slot »%s«, der in Parameter »%s« angegeben wurde, hat keine active_pid" -#: replication/slot.c:2707 +#: replication/slot.c:2703 #, c-format msgid "Start the standby associated with the replication slot \"%s\", or amend parameter \"%s\"." msgstr "Starten Sie den zum Replikations-Slot »%s« gehörenden Standby oder berichtigen Sie den Parameter »%s«." @@ -22024,7 +22026,7 @@ msgstr "ungültiger Standby-Message-Typ »%c«" msgid "unexpected message type \"%c\"" msgstr "unerwarteter Message-Typ »%c«" -#: replication/walsender.c:2774 +#: replication/walsender.c:2778 #, c-format msgid "terminating walsender process due to replication timeout" msgstr "WAL-Sender-Prozess wird abgebrochen wegen Zeitüberschreitung bei der Replikation" @@ -22730,138 +22732,138 @@ msgstr "konnte Fileset »%s« nicht löschen: %m" msgid "could not truncate file \"%s\": %m" msgstr "kann Datei »%s« nicht kürzen: %m" -#: storage/file/fd.c:583 storage/file/fd.c:655 storage/file/fd.c:691 +#: storage/file/fd.c:580 storage/file/fd.c:652 storage/file/fd.c:688 #, c-format msgid "could not flush dirty data: %m" msgstr "konnte schmutzige Daten nicht flushen: %m" -#: storage/file/fd.c:613 +#: storage/file/fd.c:610 #, c-format msgid "could not determine dirty data size: %m" msgstr "konnte Größe der schmutzigen Daten nicht bestimmen: %m" -#: storage/file/fd.c:665 +#: storage/file/fd.c:662 #, c-format msgid "could not munmap() while flushing data: %m" msgstr "munmap() fehlgeschlagen beim Flushen von Daten: %m" -#: storage/file/fd.c:983 +#: storage/file/fd.c:980 #, c-format msgid "getrlimit failed: %m" msgstr "getrlimit fehlgeschlagen: %m" -#: storage/file/fd.c:1073 +#: storage/file/fd.c:1070 #, c-format msgid "insufficient file descriptors available to start server process" msgstr "nicht genug Dateideskriptoren verfügbar, um Serverprozess zu starten" -#: storage/file/fd.c:1074 +#: storage/file/fd.c:1071 #, c-format msgid "System allows %d, server needs at least %d." msgstr "System erlaubt %d, Server benötigt mindestens %d." -#: storage/file/fd.c:1162 storage/file/fd.c:2618 storage/file/fd.c:2727 -#: storage/file/fd.c:2878 +#: storage/file/fd.c:1159 storage/file/fd.c:2615 storage/file/fd.c:2724 +#: storage/file/fd.c:2875 #, c-format msgid "out of file descriptors: %m; release and retry" msgstr "keine Dateideskriptoren mehr: %m; freigeben und nochmal versuchen" -#: storage/file/fd.c:1536 +#: storage/file/fd.c:1533 #, c-format msgid "temporary file: path \"%s\", size %lu" msgstr "temporäre Datei: Pfad »%s«, Größe %lu" -#: storage/file/fd.c:1675 +#: storage/file/fd.c:1672 #, c-format msgid "cannot create temporary directory \"%s\": %m" msgstr "konnte temporäres Verzeichnis »%s« nicht erzeugen: %m" -#: storage/file/fd.c:1682 +#: storage/file/fd.c:1679 #, c-format msgid "cannot create temporary subdirectory \"%s\": %m" msgstr "konnte temporäres Unterverzeichnis »%s« nicht erzeugen: %m" -#: storage/file/fd.c:1879 +#: storage/file/fd.c:1876 #, c-format msgid "could not create temporary file \"%s\": %m" msgstr "konnte temporäre Datei »%s« nicht erzeugen: %m" -#: storage/file/fd.c:1915 +#: storage/file/fd.c:1912 #, c-format msgid "could not open temporary file \"%s\": %m" msgstr "konnte temporäre Datei »%s« nicht öffnen: %m" -#: storage/file/fd.c:1956 +#: storage/file/fd.c:1953 #, c-format msgid "could not unlink temporary file \"%s\": %m" msgstr "konnte temporäre Datei »%s« nicht löschen: %m" -#: storage/file/fd.c:2044 +#: storage/file/fd.c:2041 #, c-format msgid "could not delete file \"%s\": %m" msgstr "konnte Datei »%s« nicht löschen: %m" -#: storage/file/fd.c:2234 +#: storage/file/fd.c:2231 #, c-format msgid "temporary file size exceeds temp_file_limit (%dkB)" msgstr "Größe der temporären Datei überschreitet temp_file_limit (%dkB)" -#: storage/file/fd.c:2594 storage/file/fd.c:2653 +#: storage/file/fd.c:2591 storage/file/fd.c:2650 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"" msgstr "maxAllocatedDescs (%d) überschritten beim Versuch, die Datei »%s« zu öffnen" -#: storage/file/fd.c:2698 +#: storage/file/fd.c:2695 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to execute command \"%s\"" msgstr "maxAllocatedDescs (%d) überschritten beim Versuch, den Befehl »%s« auszuführen" -#: storage/file/fd.c:2854 +#: storage/file/fd.c:2851 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"" msgstr "maxAllocatedDescs (%d) überschritten beim Versuch, das Verzeichnis »%s« zu öffnen" -#: storage/file/fd.c:3384 +#: storage/file/fd.c:3381 #, c-format msgid "unexpected file found in temporary-files directory: \"%s\"" msgstr "unerwartete Datei im Verzeichnis für temporäre Dateien gefunden: »%s«" -#: storage/file/fd.c:3502 +#: storage/file/fd.c:3499 #, c-format msgid "syncing data directory (syncfs), elapsed time: %ld.%02d s, current path: %s" msgstr "synchronisiere Datenverzeichnis (syncfs), abgelaufene Zeit: %ld.%02d s, aktueller Pfad: %s" -#: storage/file/fd.c:3729 +#: storage/file/fd.c:3726 #, c-format msgid "syncing data directory (pre-fsync), elapsed time: %ld.%02d s, current path: %s" msgstr "synchronisiere Datenverzeichnis (pre-fsync), abgelaufene Zeit: %ld.%02d s, aktueller Pfad: %s" -#: storage/file/fd.c:3761 +#: storage/file/fd.c:3758 #, c-format msgid "syncing data directory (fsync), elapsed time: %ld.%02d s, current path: %s" msgstr "synchronisiere Datenverzeichnis (fsync), abgelaufene Zeit: %ld.%02d s, aktueller Pfad: %s" -#: storage/file/fd.c:3950 +#: storage/file/fd.c:3947 #, c-format msgid "\"debug_io_direct\" is not supported on this platform." msgstr "debug_io_direct wird auf dieser Plattform nicht unterstützt." -#: storage/file/fd.c:3964 +#: storage/file/fd.c:3961 #, c-format msgid "Invalid list syntax in parameter \"%s\"" msgstr "Ungültige Listensyntax für Parameter »%s«" -#: storage/file/fd.c:3984 +#: storage/file/fd.c:3981 #, c-format msgid "Invalid option \"%s\"" msgstr "Ungültige Option »%s«" -#: storage/file/fd.c:3997 +#: storage/file/fd.c:3994 #, c-format msgid "\"debug_io_direct\" is not supported for WAL because XLOG_BLCKSZ is too small" msgstr "»debug_io_direct« wird für WAL nicht unterstützt, weil XLOG_BLCKSZ zu klein ist" -#: storage/file/fd.c:4004 +#: storage/file/fd.c:4001 #, c-format msgid "\"debug_io_direct\" is not supported for data because BLCKSZ is too small" msgstr "»debug_io_direct« wird für Daten nicht unterstützt, weil BLCKSZ zu klein ist" @@ -23195,107 +23197,107 @@ msgstr "Verklemmung (Deadlock) entdeckt" msgid "See server log for query details." msgstr "Einzelheiten zur Anfrage finden Sie im Serverlog." -#: storage/lmgr/lmgr.c:848 +#: storage/lmgr/lmgr.c:854 #, c-format msgid "while updating tuple (%u,%u) in relation \"%s\"" msgstr "beim Aktualisieren von Tupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:851 +#: storage/lmgr/lmgr.c:857 #, c-format msgid "while deleting tuple (%u,%u) in relation \"%s\"" msgstr "beim Löschen von Tupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:854 +#: storage/lmgr/lmgr.c:860 #, c-format msgid "while locking tuple (%u,%u) in relation \"%s\"" msgstr "beim Sperren von Tupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:857 +#: storage/lmgr/lmgr.c:863 #, c-format msgid "while locking updated version (%u,%u) of tuple in relation \"%s\"" msgstr "beim Sperren von aktualisierter Version (%u,%u) von Tupel in Relation »%s«" -#: storage/lmgr/lmgr.c:860 +#: storage/lmgr/lmgr.c:866 #, c-format msgid "while inserting index tuple (%u,%u) in relation \"%s\"" msgstr "beim Einfügen von Indextupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:863 +#: storage/lmgr/lmgr.c:869 #, c-format msgid "while checking uniqueness of tuple (%u,%u) in relation \"%s\"" msgstr "beim Prüfen der Eindeutigkeit von Tupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:866 +#: storage/lmgr/lmgr.c:872 #, c-format msgid "while rechecking updated tuple (%u,%u) in relation \"%s\"" msgstr "beim erneuten Prüfen des aktualisierten Tupels (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:869 +#: storage/lmgr/lmgr.c:875 #, c-format msgid "while checking exclusion constraint on tuple (%u,%u) in relation \"%s\"" msgstr "beim Prüfen eines Exclusion-Constraints für Tupel (%u,%u) in Relation »%s«" -#: storage/lmgr/lmgr.c:1239 +#: storage/lmgr/lmgr.c:1245 #, c-format msgid "relation %u of database %u" msgstr "Relation %u der Datenbank %u" -#: storage/lmgr/lmgr.c:1245 +#: storage/lmgr/lmgr.c:1251 #, c-format msgid "extension of relation %u of database %u" msgstr "Erweiterung von Relation %u in Datenbank %u" -#: storage/lmgr/lmgr.c:1251 +#: storage/lmgr/lmgr.c:1257 #, c-format msgid "pg_database.datfrozenxid of database %u" msgstr "pg_database.datfrozenxid der Datenbank %u" -#: storage/lmgr/lmgr.c:1256 +#: storage/lmgr/lmgr.c:1262 #, c-format msgid "page %u of relation %u of database %u" msgstr "Seite %u von Relation %u von Datenbank %u" -#: storage/lmgr/lmgr.c:1263 +#: storage/lmgr/lmgr.c:1269 #, c-format msgid "tuple (%u,%u) of relation %u of database %u" msgstr "Tupel (%u, %u) von Relation %u von Datenbank %u" -#: storage/lmgr/lmgr.c:1271 +#: storage/lmgr/lmgr.c:1277 #, c-format msgid "transaction %u" msgstr "Transaktion %u" -#: storage/lmgr/lmgr.c:1276 +#: storage/lmgr/lmgr.c:1282 #, c-format msgid "virtual transaction %d/%u" msgstr "virtuelle Transaktion %d/%u" -#: storage/lmgr/lmgr.c:1282 +#: storage/lmgr/lmgr.c:1288 #, c-format msgid "speculative token %u of transaction %u" msgstr "spekulatives Token %u von Transaktion %u" -#: storage/lmgr/lmgr.c:1288 +#: storage/lmgr/lmgr.c:1294 #, c-format msgid "object %u of class %u of database %u" msgstr "Objekt %u von Klasse %u von Datenbank %u" -#: storage/lmgr/lmgr.c:1296 +#: storage/lmgr/lmgr.c:1302 #, c-format msgid "user lock [%u,%u,%u]" msgstr "Benutzersperre [%u,%u,%u]" -#: storage/lmgr/lmgr.c:1303 +#: storage/lmgr/lmgr.c:1309 #, c-format msgid "advisory lock [%u,%u,%u,%u]" msgstr "Benutzersperre [%u,%u,%u,%u]" -#: storage/lmgr/lmgr.c:1311 +#: storage/lmgr/lmgr.c:1317 #, c-format msgid "remote transaction %u of subscription %u of database %u" msgstr "Remote-Transaktion %u von Subskription %u in Datenbank %u" -#: storage/lmgr/lmgr.c:1318 +#: storage/lmgr/lmgr.c:1324 #, c-format msgid "unrecognized locktag type %d" msgstr "unbekannter Locktag-Typ %d" @@ -23996,12 +23998,12 @@ msgstr "Verbindungsende: Sitzungszeit: %d:%02d:%02d.%03d Benutzer=%s Datenbank=% msgid "bind message has %d result formats but query has %d columns" msgstr "Bind-Message hat %d Ergebnisspalten, aber Anfrage hat %d Spalten" -#: tcop/pquery.c:942 tcop/pquery.c:1696 +#: tcop/pquery.c:942 tcop/pquery.c:1687 #, c-format msgid "cursor can only scan forward" msgstr "Cursor kann nur vorwärts scannen" -#: tcop/pquery.c:943 tcop/pquery.c:1697 +#: tcop/pquery.c:943 tcop/pquery.c:1688 #, c-format msgid "Declare it with SCROLL option to enable backward scan." msgstr "Deklarieren Sie ihn mit der Option SCROLL, um rückwarts scannen zu können." @@ -24346,102 +24348,102 @@ msgstr "Wait-Event »%s« existiert bereits in Typ »%s«" msgid "too many custom wait events" msgstr "zu viele benutzerdefinierte Wait-Events" -#: utils/adt/acl.c:183 utils/adt/name.c:93 +#: utils/adt/acl.c:200 utils/adt/name.c:93 #, c-format msgid "identifier too long" msgstr "Bezeichner zu lang" -#: utils/adt/acl.c:184 utils/adt/name.c:94 +#: utils/adt/acl.c:201 utils/adt/name.c:94 #, c-format msgid "Identifier must be less than %d characters." msgstr "Bezeichner muss weniger als %d Zeichen haben." -#: utils/adt/acl.c:272 +#: utils/adt/acl.c:289 #, c-format msgid "unrecognized key word: \"%s\"" msgstr "unbekanntes Schlüsselwort: »%s«" -#: utils/adt/acl.c:273 +#: utils/adt/acl.c:290 #, c-format msgid "ACL key word must be \"group\" or \"user\"." msgstr "ACL-Schlüsselwort muss »group« oder »user« sein." -#: utils/adt/acl.c:281 +#: utils/adt/acl.c:298 #, c-format msgid "missing name" msgstr "Name fehlt" -#: utils/adt/acl.c:282 +#: utils/adt/acl.c:299 #, c-format msgid "A name must follow the \"group\" or \"user\" key word." msgstr "Auf das Schlüsselwort »group« oder »user« muss ein Name folgen." -#: utils/adt/acl.c:288 +#: utils/adt/acl.c:305 #, c-format msgid "missing \"=\" sign" msgstr "»=«-Zeichen fehlt" -#: utils/adt/acl.c:350 +#: utils/adt/acl.c:367 #, c-format msgid "invalid mode character: must be one of \"%s\"" msgstr "ungültiges Moduszeichen: muss eines aus »%s« sein" -#: utils/adt/acl.c:380 +#: utils/adt/acl.c:397 #, c-format msgid "a name must follow the \"/\" sign" msgstr "auf das »/«-Zeichen muss ein Name folgen" -#: utils/adt/acl.c:392 +#: utils/adt/acl.c:409 #, c-format msgid "defaulting grantor to user ID %u" msgstr "nicht angegebener Grantor wird auf user ID %u gesetzt" -#: utils/adt/acl.c:578 +#: utils/adt/acl.c:595 #, c-format msgid "ACL array contains wrong data type" msgstr "ACL-Array enthält falschen Datentyp" -#: utils/adt/acl.c:582 +#: utils/adt/acl.c:599 #, c-format msgid "ACL arrays must be one-dimensional" msgstr "ACL-Arrays müssen eindimensional sein" -#: utils/adt/acl.c:586 +#: utils/adt/acl.c:603 #, c-format msgid "ACL arrays must not contain null values" msgstr "ACL-Array darf keine NULL-Werte enthalten" -#: utils/adt/acl.c:615 +#: utils/adt/acl.c:632 #, c-format msgid "extra garbage at the end of the ACL specification" msgstr "überflüssiger Müll am Ende der ACL-Angabe" -#: utils/adt/acl.c:1263 +#: utils/adt/acl.c:1280 #, c-format msgid "grant options cannot be granted back to your own grantor" msgstr "Grant-Optionen können nicht an den eigenen Grantor gegeben werden" -#: utils/adt/acl.c:1579 +#: utils/adt/acl.c:1596 #, c-format msgid "aclinsert is no longer supported" msgstr "aclinsert wird nicht mehr unterstützt" -#: utils/adt/acl.c:1589 +#: utils/adt/acl.c:1606 #, c-format msgid "aclremove is no longer supported" msgstr "aclremove wird nicht mehr unterstützt" -#: utils/adt/acl.c:1709 +#: utils/adt/acl.c:1726 #, c-format msgid "unrecognized privilege type: \"%s\"" msgstr "unbekannter Privilegtyp: »%s«" -#: utils/adt/acl.c:3550 utils/adt/regproc.c:100 utils/adt/regproc.c:265 +#: utils/adt/acl.c:3567 utils/adt/regproc.c:100 utils/adt/regproc.c:265 #, c-format msgid "function \"%s\" does not exist" msgstr "Funktion »%s« existiert nicht" -#: utils/adt/acl.c:5196 +#: utils/adt/acl.c:5213 #, c-format msgid "must be able to SET ROLE \"%s\"" msgstr "Berechtigung nur für Rollen, die SET ROLE \"%s\" ausführen können" @@ -24467,7 +24469,7 @@ msgstr "Eingabedatentyp ist kein Array" #: utils/adt/int.c:1025 utils/adt/int.c:1058 utils/adt/int.c:1072 #: utils/adt/int.c:1086 utils/adt/int.c:1117 utils/adt/int.c:1199 #: utils/adt/int.c:1263 utils/adt/int.c:1331 utils/adt/int.c:1337 -#: utils/adt/int8.c:1256 utils/adt/numeric.c:1917 utils/adt/numeric.c:4454 +#: utils/adt/int8.c:1256 utils/adt/numeric.c:1918 utils/adt/numeric.c:4455 #: utils/adt/rangetypes.c:1488 utils/adt/rangetypes.c:1501 #: utils/adt/varbit.c:1195 utils/adt/varbit.c:1596 utils/adt/varlena.c:1135 #: utils/adt/varlena.c:3137 @@ -24827,8 +24829,8 @@ msgstr "Kodierungsumwandlung zwischen %s und ASCII wird nicht unterstützt" #: utils/adt/geo_ops.c:3428 utils/adt/geo_ops.c:4650 utils/adt/geo_ops.c:4665 #: utils/adt/geo_ops.c:4672 utils/adt/int.c:174 utils/adt/int.c:186 #: utils/adt/jsonpath.c:185 utils/adt/mac.c:94 utils/adt/mac8.c:226 -#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7221 -#: utils/adt/numeric.c:7424 utils/adt/numeric.c:8371 utils/adt/numutils.c:356 +#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7222 +#: utils/adt/numeric.c:7425 utils/adt/numeric.c:8372 utils/adt/numutils.c:356 #: utils/adt/numutils.c:618 utils/adt/numutils.c:880 utils/adt/numutils.c:919 #: utils/adt/numutils.c:941 utils/adt/numutils.c:1005 utils/adt/numutils.c:1027 #: utils/adt/pg_lsn.c:73 utils/adt/tid.c:72 utils/adt/tid.c:80 @@ -24849,10 +24851,10 @@ msgstr "money ist außerhalb des gültigen Bereichs" #: utils/adt/int.c:1101 utils/adt/int.c:1139 utils/adt/int.c:1167 #: utils/adt/int8.c:514 utils/adt/int8.c:572 utils/adt/int8.c:942 #: utils/adt/int8.c:1022 utils/adt/int8.c:1084 utils/adt/int8.c:1164 -#: utils/adt/numeric.c:3191 utils/adt/numeric.c:3214 utils/adt/numeric.c:3299 -#: utils/adt/numeric.c:3317 utils/adt/numeric.c:3413 utils/adt/numeric.c:8920 -#: utils/adt/numeric.c:9233 utils/adt/numeric.c:9581 utils/adt/numeric.c:9697 -#: utils/adt/numeric.c:11208 utils/adt/timestamp.c:3713 +#: utils/adt/numeric.c:3192 utils/adt/numeric.c:3215 utils/adt/numeric.c:3300 +#: utils/adt/numeric.c:3318 utils/adt/numeric.c:3414 utils/adt/numeric.c:8921 +#: utils/adt/numeric.c:9234 utils/adt/numeric.c:9582 utils/adt/numeric.c:9698 +#: utils/adt/numeric.c:11209 utils/adt/timestamp.c:3713 #, c-format msgid "division by zero" msgstr "Division durch Null" @@ -24899,7 +24901,7 @@ msgid "date out of range: \"%s\"" msgstr "date ist außerhalb des gültigen Bereichs: »%s«" #: utils/adt/date.c:222 utils/adt/date.c:520 utils/adt/date.c:544 -#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2552 +#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2554 #, c-format msgid "date out of range" msgstr "date ist außerhalb des gültigen Bereichs" @@ -24973,8 +24975,8 @@ msgstr "Einheit »%s« nicht erkannt für Typ %s" #: utils/adt/timestamp.c:6260 utils/adt/timestamp.c:6347 #: utils/adt/timestamp.c:6388 utils/adt/timestamp.c:6392 #: utils/adt/timestamp.c:6446 utils/adt/timestamp.c:6450 -#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2574 -#: utils/adt/xml.c:2581 utils/adt/xml.c:2601 utils/adt/xml.c:2608 +#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2576 +#: utils/adt/xml.c:2583 utils/adt/xml.c:2603 utils/adt/xml.c:2610 #, c-format msgid "timestamp out of range" msgstr "timestamp ist außerhalb des gültigen Bereichs" @@ -25006,7 +25008,7 @@ msgstr "kann unendlichen interval-Wert nicht von time subtrahieren" #: utils/adt/date.c:2115 utils/adt/date.c:2667 utils/adt/float.c:1036 #: utils/adt/float.c:1112 utils/adt/int.c:635 utils/adt/int.c:682 -#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2595 +#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2596 #: utils/adt/timestamp.c:3810 utils/adt/timestamp.c:3847 #: utils/adt/timestamp.c:3888 #, c-format @@ -25184,34 +25186,34 @@ msgstr "»%s« ist außerhalb des gültigen Bereichs für Typ double precision" #: utils/adt/float.c:1247 utils/adt/float.c:1321 utils/adt/int.c:355 #: utils/adt/int.c:893 utils/adt/int.c:915 utils/adt/int.c:929 #: utils/adt/int.c:943 utils/adt/int.c:975 utils/adt/int.c:1213 -#: utils/adt/int8.c:1277 utils/adt/numeric.c:4593 utils/adt/numeric.c:4598 +#: utils/adt/int8.c:1277 utils/adt/numeric.c:4594 utils/adt/numeric.c:4599 #, c-format msgid "smallint out of range" msgstr "smallint ist außerhalb des gültigen Bereichs" -#: utils/adt/float.c:1447 utils/adt/numeric.c:3709 utils/adt/numeric.c:10112 +#: utils/adt/float.c:1447 utils/adt/numeric.c:3710 utils/adt/numeric.c:10113 #, c-format msgid "cannot take square root of a negative number" msgstr "Quadratwurzel von negativer Zahl kann nicht ermittelt werden" -#: utils/adt/float.c:1515 utils/adt/numeric.c:3997 utils/adt/numeric.c:4109 +#: utils/adt/float.c:1515 utils/adt/numeric.c:3998 utils/adt/numeric.c:4110 #, c-format msgid "zero raised to a negative power is undefined" msgstr "null hoch eine negative Zahl ist undefiniert" -#: utils/adt/float.c:1519 utils/adt/numeric.c:4001 utils/adt/numeric.c:11003 +#: utils/adt/float.c:1519 utils/adt/numeric.c:4002 utils/adt/numeric.c:11004 #, c-format msgid "a negative number raised to a non-integer power yields a complex result" msgstr "eine negative Zahl hoch eine nicht ganze Zahl ergibt ein komplexes Ergebnis" -#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3909 -#: utils/adt/numeric.c:10783 +#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3910 +#: utils/adt/numeric.c:10784 #, c-format msgid "cannot take logarithm of zero" msgstr "Logarithmus von null kann nicht ermittelt werden" -#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3847 -#: utils/adt/numeric.c:3904 utils/adt/numeric.c:10787 +#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3848 +#: utils/adt/numeric.c:3905 utils/adt/numeric.c:10788 #, c-format msgid "cannot take logarithm of a negative number" msgstr "Logarithmus negativer Zahlen kann nicht ermittelt werden" @@ -25225,22 +25227,22 @@ msgstr "Logarithmus negativer Zahlen kann nicht ermittelt werden" msgid "input is out of range" msgstr "Eingabe ist außerhalb des gültigen Bereichs" -#: utils/adt/float.c:4000 utils/adt/numeric.c:1857 +#: utils/adt/float.c:4000 utils/adt/numeric.c:1858 #, c-format msgid "count must be greater than zero" msgstr "Anzahl muss größer als null sein" -#: utils/adt/float.c:4005 utils/adt/numeric.c:1868 +#: utils/adt/float.c:4005 utils/adt/numeric.c:1869 #, c-format msgid "operand, lower bound, and upper bound cannot be NaN" msgstr "Operand, Untergrenze und Obergrenze dürfen nicht NaN sein" -#: utils/adt/float.c:4011 utils/adt/numeric.c:1873 +#: utils/adt/float.c:4011 utils/adt/numeric.c:1874 #, c-format msgid "lower and upper bounds must be finite" msgstr "Untergrenze und Obergrenze müssen endlich sein" -#: utils/adt/float.c:4077 utils/adt/numeric.c:1887 +#: utils/adt/float.c:4077 utils/adt/numeric.c:1888 #, c-format msgid "lower bound cannot equal upper bound" msgstr "Untergrenze kann nicht gleich der Obergrenze sein" @@ -25605,7 +25607,7 @@ msgstr "Schrittgröße kann nicht gleich null sein" #: utils/adt/int8.c:994 utils/adt/int8.c:1008 utils/adt/int8.c:1041 #: utils/adt/int8.c:1055 utils/adt/int8.c:1069 utils/adt/int8.c:1100 #: utils/adt/int8.c:1122 utils/adt/int8.c:1136 utils/adt/int8.c:1150 -#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4542 +#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4543 #: utils/adt/rangetypes.c:1535 utils/adt/rangetypes.c:1548 #: utils/adt/varbit.c:1676 #, c-format @@ -25659,7 +25661,7 @@ msgstr "Array muss zwei Spalten haben" msgid "mismatched array dimensions" msgstr "Array-Dimensionen passen nicht" -#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1956 +#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1962 #, c-format msgid "duplicate JSON object key value" msgstr "doppelter JSON-Objekt-Schlüsselwert" @@ -25724,23 +25726,23 @@ msgstr "kann jsonb-Objekt nicht in Typ %s umwandeln" msgid "cannot cast jsonb array or object to type %s" msgstr "kann jsonb-Array oder -Objekt nicht in Typ %s umwandeln" -#: utils/adt/jsonb_util.c:756 +#: utils/adt/jsonb_util.c:753 #, c-format msgid "number of jsonb object pairs exceeds the maximum allowed (%zu)" msgstr "Anzahl der jsonb-Objekte-Paare überschreitet erlaubtes Maximum (%zu)" -#: utils/adt/jsonb_util.c:797 +#: utils/adt/jsonb_util.c:794 #, c-format msgid "number of jsonb array elements exceeds the maximum allowed (%zu)" msgstr "Anzahl der jsonb-Arrayelemente überschreitet erlaubtes Maximum (%zu)" -#: utils/adt/jsonb_util.c:1671 utils/adt/jsonb_util.c:1691 +#: utils/adt/jsonb_util.c:1677 utils/adt/jsonb_util.c:1697 #, c-format msgid "total size of jsonb array elements exceeds the maximum of %d bytes" msgstr "Gesamtgröße der jsonb-Array-Elemente überschreitet die maximale Größe von %d Bytes" -#: utils/adt/jsonb_util.c:1752 utils/adt/jsonb_util.c:1787 -#: utils/adt/jsonb_util.c:1807 +#: utils/adt/jsonb_util.c:1758 utils/adt/jsonb_util.c:1793 +#: utils/adt/jsonb_util.c:1813 #, c-format msgid "total size of jsonb object elements exceeds the maximum of %d bytes" msgstr "Gesamtgröße der jsonb-Objektelemente überschreitet die maximale Größe von %d Bytes" @@ -26233,12 +26235,12 @@ msgstr "nichtdeterministische Sortierfolgen werden von ILIKE nicht unterstützt" msgid "LIKE pattern must not end with escape character" msgstr "LIKE-Muster darf nicht mit Escape-Zeichen enden" -#: utils/adt/like_match.c:293 utils/adt/regexp.c:800 +#: utils/adt/like_match.c:293 utils/adt/regexp.c:803 #, c-format msgid "invalid escape string" msgstr "ungültige ESCAPE-Zeichenkette" -#: utils/adt/like_match.c:294 utils/adt/regexp.c:801 +#: utils/adt/like_match.c:294 utils/adt/regexp.c:804 #, c-format msgid "Escape string must be empty or one character." msgstr "ESCAPE-Zeichenkette muss null oder ein Zeichen lang sein." @@ -26449,10 +26451,10 @@ msgstr "Ergebnis ist außerhalb des gültigen Bereichs" msgid "cannot subtract inet values of different sizes" msgstr "Subtraktion von »inet«-Werten unterschiedlicher Größe nicht möglich" -#: utils/adt/numeric.c:793 utils/adt/numeric.c:3659 utils/adt/numeric.c:7216 -#: utils/adt/numeric.c:7419 utils/adt/numeric.c:7891 utils/adt/numeric.c:10586 -#: utils/adt/numeric.c:11061 utils/adt/numeric.c:11155 -#: utils/adt/numeric.c:11290 +#: utils/adt/numeric.c:793 utils/adt/numeric.c:3660 utils/adt/numeric.c:7217 +#: utils/adt/numeric.c:7420 utils/adt/numeric.c:7892 utils/adt/numeric.c:10587 +#: utils/adt/numeric.c:11062 utils/adt/numeric.c:11156 +#: utils/adt/numeric.c:11291 #, c-format msgid "value overflows numeric format" msgstr "Wert verursacht Überlauf im »numeric«-Format" @@ -26517,64 +26519,64 @@ msgstr "Schrittgröße kann nicht NaN sein" msgid "step size cannot be infinity" msgstr "Schrittgröße kann nicht unendlich sein" -#: utils/adt/numeric.c:3649 +#: utils/adt/numeric.c:3650 #, c-format msgid "factorial of a negative number is undefined" msgstr "Fakultät einer negativen Zahl ist undefiniert" -#: utils/adt/numeric.c:4256 +#: utils/adt/numeric.c:4257 #, c-format msgid "lower bound cannot be NaN" msgstr "Untergrenze kann nicht NaN sein" -#: utils/adt/numeric.c:4260 +#: utils/adt/numeric.c:4261 #, c-format msgid "lower bound cannot be infinity" msgstr "Untergrenze kann nicht unendlich sein" -#: utils/adt/numeric.c:4267 +#: utils/adt/numeric.c:4268 #, c-format msgid "upper bound cannot be NaN" msgstr "Obergrenze kann nicht NaN sein" -#: utils/adt/numeric.c:4271 +#: utils/adt/numeric.c:4272 #, c-format msgid "upper bound cannot be infinity" msgstr "Obergrenze kann nicht unendlich sein" -#: utils/adt/numeric.c:4432 utils/adt/numeric.c:4520 utils/adt/numeric.c:4580 -#: utils/adt/numeric.c:4776 +#: utils/adt/numeric.c:4433 utils/adt/numeric.c:4521 utils/adt/numeric.c:4581 +#: utils/adt/numeric.c:4777 #, c-format msgid "cannot convert NaN to %s" msgstr "kann NaN nicht in %s umwandeln" -#: utils/adt/numeric.c:4436 utils/adt/numeric.c:4524 utils/adt/numeric.c:4584 -#: utils/adt/numeric.c:4780 +#: utils/adt/numeric.c:4437 utils/adt/numeric.c:4525 utils/adt/numeric.c:4585 +#: utils/adt/numeric.c:4781 #, c-format msgid "cannot convert infinity to %s" msgstr "kann Unendlich nicht in %s umwandeln" -#: utils/adt/numeric.c:4789 +#: utils/adt/numeric.c:4790 #, c-format msgid "pg_lsn out of range" msgstr "pg_lsn ist außerhalb des gültigen Bereichs" -#: utils/adt/numeric.c:7981 utils/adt/numeric.c:8032 +#: utils/adt/numeric.c:7982 utils/adt/numeric.c:8033 #, c-format msgid "numeric field overflow" msgstr "Feldüberlauf bei Typ »numeric«" -#: utils/adt/numeric.c:7982 +#: utils/adt/numeric.c:7983 #, c-format msgid "A field with precision %d, scale %d must round to an absolute value less than %s%d." msgstr "Ein Feld mit Präzision %d, Skala %d muss beim Runden einen Betrag von weniger als %s%d ergeben." -#: utils/adt/numeric.c:8033 +#: utils/adt/numeric.c:8034 #, c-format msgid "A field with precision %d, scale %d cannot hold an infinite value." msgstr "Ein Feld mit Präzision %d, Skala %d kann keinen unendlichen Wert enthalten." -#: utils/adt/numeric.c:11359 utils/adt/pseudorandomfuncs.c:135 +#: utils/adt/numeric.c:11360 utils/adt/pseudorandomfuncs.c:135 #: utils/adt/pseudorandomfuncs.c:159 #, c-format msgid "lower bound must be less than or equal to upper bound" @@ -26879,7 +26881,7 @@ msgstr "Zu viele Kommas." msgid "Junk after right parenthesis or bracket." msgstr "Müll nach rechter runder oder eckiger Klammer." -#: utils/adt/regexp.c:304 utils/adt/regexp.c:1996 utils/adt/varlena.c:4273 +#: utils/adt/regexp.c:304 utils/adt/regexp.c:2022 utils/adt/varlena.c:4273 #, c-format msgid "regular expression failed: %s" msgstr "regulärer Ausdruck fehlgeschlagen: %s" @@ -26894,33 +26896,33 @@ msgstr "ungültige Option für regulären Ausdruck: »%.*s«" msgid "If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly." msgstr "Wenn Sie regexp_replace() mit einem Startparameter verwenden wollten, wandeln Sie das vierte Argument explizit in integer um." -#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1082 -#: utils/adt/regexp.c:1146 utils/adt/regexp.c:1155 utils/adt/regexp.c:1164 -#: utils/adt/regexp.c:1173 utils/adt/regexp.c:1853 utils/adt/regexp.c:1862 -#: utils/adt/regexp.c:1871 utils/misc/guc.c:6820 utils/misc/guc.c:6854 +#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1108 +#: utils/adt/regexp.c:1172 utils/adt/regexp.c:1181 utils/adt/regexp.c:1190 +#: utils/adt/regexp.c:1199 utils/adt/regexp.c:1879 utils/adt/regexp.c:1888 +#: utils/adt/regexp.c:1897 utils/misc/guc.c:6831 utils/misc/guc.c:6865 #, c-format msgid "invalid value for parameter \"%s\": %d" msgstr "ungültiger Wert für Parameter »%s«: %d" -#: utils/adt/regexp.c:936 +#: utils/adt/regexp.c:939 #, c-format msgid "SQL regular expression may not contain more than two escape-double-quote separators" msgstr "SQL regulärer Ausdruck darf nicht mehr als zwei Escape-Double-Quote-Separatoren enthalten" #. translator: %s is a SQL function name -#: utils/adt/regexp.c:1093 utils/adt/regexp.c:1184 utils/adt/regexp.c:1271 -#: utils/adt/regexp.c:1310 utils/adt/regexp.c:1698 utils/adt/regexp.c:1753 -#: utils/adt/regexp.c:1882 +#: utils/adt/regexp.c:1119 utils/adt/regexp.c:1210 utils/adt/regexp.c:1297 +#: utils/adt/regexp.c:1336 utils/adt/regexp.c:1724 utils/adt/regexp.c:1779 +#: utils/adt/regexp.c:1908 #, c-format msgid "%s does not support the \"global\" option" msgstr "%s unterstützt die »Global«-Option nicht" -#: utils/adt/regexp.c:1312 +#: utils/adt/regexp.c:1338 #, c-format msgid "Use the regexp_matches function instead." msgstr "Verwenden Sie stattdessen die Funktion regexp_matches." -#: utils/adt/regexp.c:1500 +#: utils/adt/regexp.c:1526 #, c-format msgid "too many regular expression matches" msgstr "zu viele Treffer für regulären Ausdruck" @@ -26935,8 +26937,8 @@ msgstr "es gibt mehrere Funktionen namens »%s«" msgid "more than one operator named %s" msgstr "es gibt mehrere Operatoren namens %s" -#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10516 -#: utils/adt/ruleutils.c:10729 +#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10526 +#: utils/adt/ruleutils.c:10739 #, c-format msgid "too many arguments" msgstr "zu viele Argumente" @@ -27635,136 +27637,136 @@ msgstr "ungültiger XML-Kommentar" msgid "not an XML document" msgstr "kein XML-Dokument" -#: utils/adt/xml.c:1008 utils/adt/xml.c:1031 +#: utils/adt/xml.c:1020 utils/adt/xml.c:1043 #, c-format msgid "invalid XML processing instruction" msgstr "ungültige XML-Verarbeitungsanweisung" -#: utils/adt/xml.c:1009 +#: utils/adt/xml.c:1021 #, c-format msgid "XML processing instruction target name cannot be \"%s\"." msgstr "Die Zielangabe der XML-Verarbeitungsanweisung darf nicht »%s« sein." -#: utils/adt/xml.c:1032 +#: utils/adt/xml.c:1044 #, c-format msgid "XML processing instruction cannot contain \"?>\"." msgstr "XML-Verarbeitungsanweisung darf nicht »?>« enthalten." -#: utils/adt/xml.c:1111 +#: utils/adt/xml.c:1123 #, c-format msgid "xmlvalidate is not implemented" msgstr "xmlvalidate ist nicht implementiert" -#: utils/adt/xml.c:1167 +#: utils/adt/xml.c:1179 #, c-format msgid "could not initialize XML library" msgstr "konnte XML-Bibliothek nicht initialisieren" -#: utils/adt/xml.c:1168 +#: utils/adt/xml.c:1180 #, c-format msgid "libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu." msgstr "libxml2 hat inkompatiblen char-Typ: sizeof(char)=%zu, sizeof(xmlChar)=%zu." -#: utils/adt/xml.c:1254 +#: utils/adt/xml.c:1266 #, c-format msgid "could not set up XML error handler" msgstr "konnte XML-Fehlerbehandlung nicht einrichten" -#: utils/adt/xml.c:1255 +#: utils/adt/xml.c:1267 #, c-format msgid "This probably indicates that the version of libxml2 being used is not compatible with the libxml2 header files that PostgreSQL was built with." msgstr "Das deutet wahrscheinlich darauf hin, dass die verwendete Version von libxml2 nicht mit den Header-Dateien der Version, mit der PostgreSQL gebaut wurde, kompatibel ist." -#: utils/adt/xml.c:2281 +#: utils/adt/xml.c:2283 msgid "Invalid character value." msgstr "Ungültiger Zeichenwert." -#: utils/adt/xml.c:2284 +#: utils/adt/xml.c:2286 msgid "Space required." msgstr "Leerzeichen benötigt." -#: utils/adt/xml.c:2287 +#: utils/adt/xml.c:2289 msgid "standalone accepts only 'yes' or 'no'." msgstr "standalone akzeptiert nur »yes« oder »no«." -#: utils/adt/xml.c:2290 +#: utils/adt/xml.c:2292 msgid "Malformed declaration: missing version." msgstr "Fehlerhafte Deklaration: Version fehlt." -#: utils/adt/xml.c:2293 +#: utils/adt/xml.c:2295 msgid "Missing encoding in text declaration." msgstr "Fehlende Kodierung in Textdeklaration." -#: utils/adt/xml.c:2296 +#: utils/adt/xml.c:2298 msgid "Parsing XML declaration: '?>' expected." msgstr "Beim Parsen der XML-Deklaration: »?>« erwartet." -#: utils/adt/xml.c:2299 +#: utils/adt/xml.c:2301 #, c-format msgid "Unrecognized libxml error code: %d." msgstr "Unbekannter Libxml-Fehlercode: %d." -#: utils/adt/xml.c:2553 +#: utils/adt/xml.c:2555 #, c-format msgid "XML does not support infinite date values." msgstr "XML unterstützt keine unendlichen Datumswerte." -#: utils/adt/xml.c:2575 utils/adt/xml.c:2602 +#: utils/adt/xml.c:2577 utils/adt/xml.c:2604 #, c-format msgid "XML does not support infinite timestamp values." msgstr "XML unterstützt keine unendlichen timestamp-Werte." -#: utils/adt/xml.c:3018 +#: utils/adt/xml.c:3020 #, c-format msgid "invalid query" msgstr "ungültige Anfrage" -#: utils/adt/xml.c:3110 +#: utils/adt/xml.c:3112 #, c-format msgid "portal \"%s\" does not return tuples" msgstr "Portal »%s« gibt keine Tupel zurück" -#: utils/adt/xml.c:4362 +#: utils/adt/xml.c:4364 #, c-format msgid "invalid array for XML namespace mapping" msgstr "ungültiges Array for XML-Namensraumabbildung" -#: utils/adt/xml.c:4363 +#: utils/adt/xml.c:4365 #, c-format msgid "The array must be two-dimensional with length of the second axis equal to 2." msgstr "Das Array muss zweidimensional sein und die Länge der zweiten Achse muss gleich 2 sein." -#: utils/adt/xml.c:4387 +#: utils/adt/xml.c:4389 #, c-format msgid "empty XPath expression" msgstr "leerer XPath-Ausdruck" -#: utils/adt/xml.c:4439 +#: utils/adt/xml.c:4441 #, c-format msgid "neither namespace name nor URI may be null" msgstr "weder Namensraumname noch URI dürfen NULL sein" -#: utils/adt/xml.c:4446 +#: utils/adt/xml.c:4448 #, c-format msgid "could not register XML namespace with name \"%s\" and URI \"%s\"" msgstr "konnte XML-Namensraum mit Namen »%s« und URI »%s« nicht registrieren" -#: utils/adt/xml.c:4795 +#: utils/adt/xml.c:4797 #, c-format msgid "DEFAULT namespace is not supported" msgstr "DEFAULT-Namensraum wird nicht unterstützt" -#: utils/adt/xml.c:4824 +#: utils/adt/xml.c:4826 #, c-format msgid "row path filter must not be empty string" msgstr "Zeilenpfadfilter darf nicht leer sein" -#: utils/adt/xml.c:4858 +#: utils/adt/xml.c:4860 #, c-format msgid "column path filter must not be empty string" msgstr "Spaltenpfadfilter darf nicht leer sein" -#: utils/adt/xml.c:5005 +#: utils/adt/xml.c:5007 #, c-format msgid "more than one value returned by column XPath expression" msgstr "XPath-Ausdruck für Spalte gab mehr als einen Wert zurück" @@ -28234,7 +28236,7 @@ msgstr "Die Datei ist anscheinend aus Versehen übrig geblieben, konnte aber nic msgid "could not write lock file \"%s\": %m" msgstr "konnte Sperrdatei »%s« nicht schreiben: %m" -#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5765 +#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5770 #, c-format msgid "could not read from file \"%s\": %m" msgstr "konnte nicht aus Datei »%s« lesen: %m" @@ -28504,7 +28506,7 @@ msgstr "bind_textdomain_codeset fehlgeschlagen" msgid "invalid byte sequence for encoding \"%s\": %s" msgstr "ungültige Byte-Sequenz für Kodierung »%s«: %s" -#: utils/mb/mbutils.c:1751 +#: utils/mb/mbutils.c:1759 #, c-format msgid "character with byte sequence %s in encoding \"%s\" has no equivalent in encoding \"%s\"" msgstr "Zeichen mit Byte-Folge %s in Kodierung »%s« hat keine Entsprechung in Kodierung »%s«" @@ -28669,7 +28671,7 @@ msgid "parameter \"%s\" cannot be changed now" msgstr "Parameter »%s« kann jetzt nicht geändert werden" #: utils/misc/guc.c:3541 utils/misc/guc.c:3603 utils/misc/guc.c:4671 -#: utils/misc/guc.c:6756 +#: utils/misc/guc.c:6767 #, c-format msgid "permission denied to set parameter \"%s\"" msgstr "keine Berechtigung, um Parameter »%s« zu setzen" @@ -28694,12 +28696,12 @@ msgstr "Parameter »%s« kann nicht zurückgesetzt werden" msgid "parameter \"%s\" cannot be set locally in functions" msgstr "Parameter »%s« kann nicht lokal in Funktionen gesetzt werden" -#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5450 +#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5455 #, c-format msgid "permission denied to examine \"%s\"" msgstr "keine Berechtigung, um »%s« zu inspizieren" -#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5451 +#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5456 #, c-format msgid "Only roles with privileges of the \"%s\" role may examine this parameter." msgstr "Nur Rollen mit den Privilegien der Rolle »%s« können diesen Parameter inspizieren." @@ -28714,47 +28716,47 @@ msgstr "ALTER SYSTEM ist in dieser Umgebung nicht erlaubt" msgid "permission denied to perform ALTER SYSTEM RESET ALL" msgstr "keine Berechtigung um ALTER SYSTEM RESET ALL auszuführen" -#: utils/misc/guc.c:4740 +#: utils/misc/guc.c:4745 #, c-format msgid "parameter value for ALTER SYSTEM must not contain a newline" msgstr "Parameterwert für ALTER SYSTEM darf keine Newline enthalten" -#: utils/misc/guc.c:4785 +#: utils/misc/guc.c:4790 #, c-format msgid "could not parse contents of file \"%s\"" msgstr "konnte Inhalt der Datei »%s« nicht parsen" -#: utils/misc/guc.c:4967 +#: utils/misc/guc.c:4972 #, c-format msgid "attempt to redefine parameter \"%s\"" msgstr "Versuch, den Parameter »%s« zu redefinieren" -#: utils/misc/guc.c:5306 +#: utils/misc/guc.c:5311 #, c-format msgid "invalid configuration parameter name \"%s\", removing it" msgstr "ungültiger Konfigurationsparametername »%s«, wird entfernt" -#: utils/misc/guc.c:5308 +#: utils/misc/guc.c:5313 #, c-format msgid "\"%s\" is now a reserved prefix." msgstr "»%s« ist jetzt ein reservierter Präfix." -#: utils/misc/guc.c:6179 +#: utils/misc/guc.c:6184 #, c-format msgid "while setting parameter \"%s\" to \"%s\"" msgstr "beim Setzen von Parameter »%s« auf »%s«" -#: utils/misc/guc.c:6348 +#: utils/misc/guc.c:6353 #, c-format msgid "parameter \"%s\" could not be set" msgstr "Parameter »%s« kann nicht gesetzt werden" -#: utils/misc/guc.c:6438 +#: utils/misc/guc.c:6443 #, c-format msgid "could not parse setting for parameter \"%s\"" msgstr "konnte Wert von Parameter »%s« nicht lesen" -#: utils/misc/guc.c:6888 +#: utils/misc/guc.c:6899 #, c-format msgid "invalid value for parameter \"%s\": %g" msgstr "ungültiger Wert für Parameter »%s«: %g" diff --git a/src/backend/po/ja.po b/src/backend/po/ja.po index a690ed5f6813f..3350a3aee278a 100644 --- a/src/backend/po/ja.po +++ b/src/backend/po/ja.po @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: postgres (PostgreSQL 17)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-04-04 09:46+0900\n" -"PO-Revision-Date: 2025-04-04 11:04+0900\n" +"POT-Creation-Date: 2025-06-20 13:19+0900\n" +"PO-Revision-Date: 2025-06-20 14:01+0900\n" "Last-Translator: Kyotaro Horiguchi \n" "Language-Team: jpug-doc \n" "Language: ja\n" @@ -87,19 +87,19 @@ msgstr "記録されていません" msgid "could not open file \"%s\" for reading: %m" msgstr "ファイル\"%s\"を読み込み用にオープンできませんでした: %m" -#: ../common/controldata_utils.c:108 ../common/controldata_utils.c:110 access/transam/timeline.c:143 access/transam/timeline.c:362 access/transam/twophase.c:1353 access/transam/xlog.c:3477 access/transam/xlog.c:4341 access/transam/xlogrecovery.c:1238 access/transam/xlogrecovery.c:1336 access/transam/xlogrecovery.c:1373 access/transam/xlogrecovery.c:1440 backup/basebackup.c:2123 backup/walsummary.c:283 commands/extension.c:3548 libpq/hba.c:764 -#: replication/logical/origin.c:745 replication/logical/origin.c:781 replication/logical/reorderbuffer.c:5113 replication/logical/snapbuild.c:2052 replication/slot.c:2236 replication/slot.c:2277 replication/walsender.c:655 storage/file/buffile.c:470 storage/file/copydir.c:185 utils/adt/genfile.c:197 utils/adt/misc.c:1028 utils/cache/relmapper.c:829 +#: ../common/controldata_utils.c:108 ../common/controldata_utils.c:110 access/transam/timeline.c:143 access/transam/timeline.c:362 access/transam/twophase.c:1353 access/transam/xlog.c:3478 access/transam/xlog.c:4342 access/transam/xlogrecovery.c:1238 access/transam/xlogrecovery.c:1336 access/transam/xlogrecovery.c:1373 access/transam/xlogrecovery.c:1440 backup/basebackup.c:2123 backup/walsummary.c:283 commands/extension.c:3548 libpq/hba.c:764 +#: replication/logical/origin.c:745 replication/logical/origin.c:781 replication/logical/reorderbuffer.c:5243 replication/logical/snapbuild.c:2099 replication/slot.c:2236 replication/slot.c:2277 replication/walsender.c:659 storage/file/buffile.c:470 storage/file/copydir.c:185 utils/adt/genfile.c:197 utils/adt/misc.c:1028 utils/cache/relmapper.c:829 #, c-format msgid "could not read file \"%s\": %m" msgstr "ファイル\"%s\"の読み込みに失敗しました: %m" -#: ../common/controldata_utils.c:116 ../common/controldata_utils.c:119 access/transam/xlog.c:3482 access/transam/xlog.c:4346 replication/logical/origin.c:750 replication/logical/origin.c:789 replication/logical/snapbuild.c:2057 replication/slot.c:2240 replication/slot.c:2281 replication/walsender.c:660 utils/cache/relmapper.c:833 +#: ../common/controldata_utils.c:116 ../common/controldata_utils.c:119 access/transam/xlog.c:3483 access/transam/xlog.c:4347 replication/logical/origin.c:750 replication/logical/origin.c:789 replication/logical/snapbuild.c:2104 replication/slot.c:2240 replication/slot.c:2281 replication/walsender.c:664 utils/cache/relmapper.c:833 #, c-format msgid "could not read file \"%s\": read %d of %zu" msgstr "ファイル\"%1$s\"を読み込めませんでした: %3$zuバイトのうち%2$dバイトを読み込みました" -#: ../common/controldata_utils.c:128 ../common/controldata_utils.c:132 ../common/controldata_utils.c:277 ../common/controldata_utils.c:280 access/heap/rewriteheap.c:1141 access/heap/rewriteheap.c:1246 access/transam/timeline.c:392 access/transam/timeline.c:438 access/transam/timeline.c:512 access/transam/twophase.c:1365 access/transam/twophase.c:1784 access/transam/xlog.c:3323 access/transam/xlog.c:3517 access/transam/xlog.c:3522 access/transam/xlog.c:3658 -#: access/transam/xlog.c:4311 access/transam/xlog.c:5246 commands/copyfrom.c:1799 commands/copyto.c:325 libpq/be-fsstubs.c:470 libpq/be-fsstubs.c:540 replication/logical/origin.c:683 replication/logical/origin.c:822 replication/logical/reorderbuffer.c:5165 replication/logical/snapbuild.c:1819 replication/logical/snapbuild.c:1943 replication/slot.c:2126 replication/slot.c:2288 replication/walsender.c:670 storage/file/copydir.c:208 storage/file/copydir.c:213 +#: ../common/controldata_utils.c:128 ../common/controldata_utils.c:132 ../common/controldata_utils.c:277 ../common/controldata_utils.c:280 access/heap/rewriteheap.c:1141 access/heap/rewriteheap.c:1246 access/transam/timeline.c:392 access/transam/timeline.c:438 access/transam/timeline.c:512 access/transam/twophase.c:1365 access/transam/twophase.c:1784 access/transam/xlog.c:3324 access/transam/xlog.c:3518 access/transam/xlog.c:3523 access/transam/xlog.c:3659 +#: access/transam/xlog.c:4312 access/transam/xlog.c:5247 commands/copyfrom.c:1799 commands/copyto.c:325 libpq/be-fsstubs.c:470 libpq/be-fsstubs.c:540 replication/logical/origin.c:683 replication/logical/origin.c:822 replication/logical/reorderbuffer.c:5295 replication/logical/snapbuild.c:1866 replication/logical/snapbuild.c:1990 replication/slot.c:2126 replication/slot.c:2288 replication/walsender.c:674 storage/file/copydir.c:208 storage/file/copydir.c:213 #: storage/file/fd.c:828 storage/file/fd.c:3753 storage/file/fd.c:3859 utils/cache/relmapper.c:841 utils/cache/relmapper.c:956 #, c-format msgid "could not close file \"%s\": %m" @@ -122,28 +122,28 @@ msgstr "" "されるものと一致しないようです。この場合以下の結果は不正確になります。また、\n" "PostgreSQLインストレーションはこのデータディレクトリと互換性がなくなります。" -#: ../common/controldata_utils.c:225 ../common/controldata_utils.c:230 ../common/file_utils.c:70 ../common/file_utils.c:347 ../common/file_utils.c:406 ../common/file_utils.c:480 access/heap/rewriteheap.c:1229 access/transam/timeline.c:111 access/transam/timeline.c:251 access/transam/timeline.c:348 access/transam/twophase.c:1309 access/transam/xlog.c:3230 access/transam/xlog.c:3393 access/transam/xlog.c:3432 access/transam/xlog.c:3625 access/transam/xlog.c:4331 -#: access/transam/xlogrecovery.c:4264 access/transam/xlogrecovery.c:4367 access/transam/xlogutils.c:836 backup/basebackup.c:547 backup/basebackup.c:1598 backup/walsummary.c:220 libpq/hba.c:624 postmaster/syslogger.c:1511 replication/logical/origin.c:735 replication/logical/reorderbuffer.c:3766 replication/logical/reorderbuffer.c:4320 replication/logical/reorderbuffer.c:5093 replication/logical/snapbuild.c:1774 replication/logical/snapbuild.c:1884 -#: replication/slot.c:2208 replication/walsender.c:628 replication/walsender.c:3051 storage/file/copydir.c:151 storage/file/fd.c:803 storage/file/fd.c:3510 storage/file/fd.c:3740 storage/file/fd.c:3830 storage/smgr/md.c:661 utils/cache/relmapper.c:818 utils/cache/relmapper.c:935 utils/error/elog.c:2124 utils/init/miscinit.c:1580 utils/init/miscinit.c:1714 utils/init/miscinit.c:1791 utils/misc/guc.c:4777 utils/misc/guc.c:4827 +#: ../common/controldata_utils.c:225 ../common/controldata_utils.c:230 ../common/file_utils.c:70 ../common/file_utils.c:347 ../common/file_utils.c:406 ../common/file_utils.c:480 access/heap/rewriteheap.c:1229 access/transam/timeline.c:111 access/transam/timeline.c:251 access/transam/timeline.c:348 access/transam/twophase.c:1309 access/transam/xlog.c:3231 access/transam/xlog.c:3394 access/transam/xlog.c:3433 access/transam/xlog.c:3626 access/transam/xlog.c:4332 +#: access/transam/xlogrecovery.c:4264 access/transam/xlogrecovery.c:4367 access/transam/xlogutils.c:836 backup/basebackup.c:547 backup/basebackup.c:1598 backup/walsummary.c:220 libpq/hba.c:624 postmaster/syslogger.c:1511 replication/logical/origin.c:735 replication/logical/reorderbuffer.c:3896 replication/logical/reorderbuffer.c:4450 replication/logical/reorderbuffer.c:5223 replication/logical/snapbuild.c:1821 replication/logical/snapbuild.c:1931 +#: replication/slot.c:2208 replication/walsender.c:632 replication/walsender.c:3085 storage/file/copydir.c:151 storage/file/fd.c:803 storage/file/fd.c:3510 storage/file/fd.c:3740 storage/file/fd.c:3830 storage/smgr/md.c:661 utils/cache/relmapper.c:818 utils/cache/relmapper.c:935 utils/error/elog.c:2124 utils/init/miscinit.c:1580 utils/init/miscinit.c:1714 utils/init/miscinit.c:1791 utils/misc/guc.c:4777 utils/misc/guc.c:4827 #, c-format msgid "could not open file \"%s\": %m" msgstr "ファイル\"%s\"をオープンできませんでした: %m" -#: ../common/controldata_utils.c:246 ../common/controldata_utils.c:249 access/transam/twophase.c:1757 access/transam/twophase.c:1766 access/transam/xlog.c:9280 access/transam/xlogfuncs.c:698 backup/basebackup_server.c:173 backup/basebackup_server.c:266 backup/walsummary.c:304 postmaster/postmaster.c:4127 postmaster/syslogger.c:1522 postmaster/syslogger.c:1535 postmaster/syslogger.c:1548 utils/cache/relmapper.c:947 +#: ../common/controldata_utils.c:246 ../common/controldata_utils.c:249 access/transam/twophase.c:1757 access/transam/twophase.c:1766 access/transam/xlog.c:9323 access/transam/xlogfuncs.c:698 backup/basebackup_server.c:173 backup/basebackup_server.c:266 backup/walsummary.c:304 postmaster/postmaster.c:4127 postmaster/syslogger.c:1522 postmaster/syslogger.c:1535 postmaster/syslogger.c:1548 utils/cache/relmapper.c:947 #, c-format msgid "could not write file \"%s\": %m" msgstr "ファイル\"%s\"を書き出せませんでした: %m" -#: ../common/controldata_utils.c:263 ../common/controldata_utils.c:268 ../common/file_utils.c:418 ../common/file_utils.c:488 access/heap/rewriteheap.c:925 access/heap/rewriteheap.c:1135 access/heap/rewriteheap.c:1240 access/transam/timeline.c:432 access/transam/timeline.c:506 access/transam/twophase.c:1778 access/transam/xlog.c:3316 access/transam/xlog.c:3511 access/transam/xlog.c:4304 access/transam/xlog.c:8655 access/transam/xlog.c:8700 -#: backup/basebackup_server.c:207 commands/dbcommands.c:514 replication/logical/snapbuild.c:1812 replication/slot.c:2112 replication/slot.c:2218 storage/file/fd.c:820 storage/file/fd.c:3851 storage/smgr/md.c:1331 storage/smgr/md.c:1376 storage/sync/sync.c:446 utils/misc/guc.c:4530 +#: ../common/controldata_utils.c:263 ../common/controldata_utils.c:268 ../common/file_utils.c:418 ../common/file_utils.c:488 access/heap/rewriteheap.c:925 access/heap/rewriteheap.c:1135 access/heap/rewriteheap.c:1240 access/transam/timeline.c:432 access/transam/timeline.c:506 access/transam/twophase.c:1778 access/transam/xlog.c:3317 access/transam/xlog.c:3512 access/transam/xlog.c:4305 access/transam/xlog.c:8698 access/transam/xlog.c:8743 +#: backup/basebackup_server.c:207 commands/dbcommands.c:514 replication/logical/snapbuild.c:1859 replication/slot.c:2112 replication/slot.c:2218 storage/file/fd.c:820 storage/file/fd.c:3851 storage/smgr/md.c:1331 storage/smgr/md.c:1376 storage/sync/sync.c:446 utils/misc/guc.c:4530 #, c-format msgid "could not fsync file \"%s\": %m" msgstr "ファイル\"%s\"をfsyncできませんでした: %m" #: ../common/cryptohash.c:261 ../common/cryptohash_openssl.c:158 ../common/cryptohash_openssl.c:356 ../common/exec.c:562 ../common/exec.c:607 ../common/exec.c:699 ../common/hmac.c:309 ../common/hmac.c:325 ../common/hmac_openssl.c:160 ../common/hmac_openssl.c:357 ../common/md5_common.c:156 ../common/parse_manifest.c:157 ../common/parse_manifest.c:852 ../common/psprintf.c:143 ../common/scram-common.c:268 ../common/stringinfo.c:314 ../port/path.c:828 ../port/path.c:865 -#: ../port/path.c:882 access/transam/twophase.c:1418 access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1352 libpq/auth.c:1396 libpq/auth.c:1953 libpq/be-secure-gssapi.c:524 postmaster/bgworker.c:355 postmaster/bgworker.c:945 postmaster/postmaster.c:3560 postmaster/postmaster.c:4021 postmaster/postmaster.c:4383 postmaster/walsummarizer.c:935 replication/libpqwalreceiver/libpqwalreceiver.c:387 replication/logical/logical.c:210 replication/walsender.c:835 -#: storage/buffer/localbuf.c:606 storage/file/fd.c:912 storage/file/fd.c:1443 storage/file/fd.c:1604 storage/file/fd.c:2531 storage/ipc/procarray.c:1465 storage/ipc/procarray.c:2219 storage/ipc/procarray.c:2226 storage/ipc/procarray.c:2731 storage/ipc/procarray.c:3435 utils/adt/formatting.c:1725 utils/adt/formatting.c:1873 utils/adt/formatting.c:2075 utils/adt/pg_locale.c:532 utils/adt/pg_locale.c:696 utils/fmgr/dfmgr.c:229 utils/hash/dynahash.c:516 -#: utils/hash/dynahash.c:616 utils/hash/dynahash.c:1099 utils/mb/mbutils.c:401 utils/mb/mbutils.c:429 utils/mb/mbutils.c:814 utils/mb/mbutils.c:841 utils/misc/guc.c:649 utils/misc/guc.c:674 utils/misc/guc.c:1062 utils/misc/guc.c:4508 utils/misc/tzparser.c:477 utils/mmgr/aset.c:451 utils/mmgr/bump.c:183 utils/mmgr/dsa.c:707 utils/mmgr/dsa.c:729 utils/mmgr/dsa.c:810 utils/mmgr/generation.c:215 utils/mmgr/mcxt.c:1154 utils/mmgr/slab.c:370 +#: ../port/path.c:882 access/transam/twophase.c:1418 access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1352 libpq/auth.c:1396 libpq/auth.c:1953 libpq/be-secure-gssapi.c:534 libpq/be-secure-gssapi.c:714 postmaster/bgworker.c:355 postmaster/bgworker.c:945 postmaster/postmaster.c:3560 postmaster/postmaster.c:4021 postmaster/postmaster.c:4383 postmaster/walsummarizer.c:935 replication/libpqwalreceiver/libpqwalreceiver.c:387 replication/logical/logical.c:212 +#: replication/walsender.c:839 storage/buffer/localbuf.c:606 storage/file/fd.c:912 storage/file/fd.c:1443 storage/file/fd.c:1604 storage/file/fd.c:2531 storage/ipc/procarray.c:1465 storage/ipc/procarray.c:2219 storage/ipc/procarray.c:2226 storage/ipc/procarray.c:2731 storage/ipc/procarray.c:3435 utils/adt/formatting.c:1725 utils/adt/formatting.c:1873 utils/adt/formatting.c:2075 utils/adt/pg_locale.c:532 utils/adt/pg_locale.c:696 utils/fmgr/dfmgr.c:229 +#: utils/hash/dynahash.c:517 utils/hash/dynahash.c:617 utils/hash/dynahash.c:1100 utils/mb/mbutils.c:401 utils/mb/mbutils.c:429 utils/mb/mbutils.c:814 utils/mb/mbutils.c:841 utils/misc/guc.c:649 utils/misc/guc.c:674 utils/misc/guc.c:1062 utils/misc/guc.c:4508 utils/misc/tzparser.c:477 utils/mmgr/aset.c:451 utils/mmgr/bump.c:183 utils/mmgr/dsa.c:707 utils/mmgr/dsa.c:729 utils/mmgr/dsa.c:810 utils/mmgr/generation.c:215 utils/mmgr/mcxt.c:1154 utils/mmgr/slab.c:370 #, c-format msgid "out of memory" msgstr "メモリ不足です" @@ -216,7 +216,7 @@ msgid "could not synchronize file system for file \"%s\": %m" msgstr "ファイル\"%s\"に対してファイルシステムを同期できませんでした: %m" #: ../common/file_utils.c:120 ../common/file_utils.c:566 ../common/file_utils.c:570 access/transam/twophase.c:1321 access/transam/xlogarchive.c:111 access/transam/xlogarchive.c:235 backup/basebackup.c:355 backup/basebackup.c:553 backup/basebackup.c:624 backup/walsummary.c:247 backup/walsummary.c:254 commands/copyfrom.c:1749 commands/copyto.c:700 commands/extension.c:3527 commands/tablespace.c:804 commands/tablespace.c:893 postmaster/pgarch.c:680 -#: replication/logical/snapbuild.c:1670 replication/logical/snapbuild.c:2173 storage/file/fd.c:1968 storage/file/fd.c:2054 storage/file/fd.c:3564 utils/adt/dbsize.c:105 utils/adt/dbsize.c:257 utils/adt/dbsize.c:337 utils/adt/genfile.c:437 utils/adt/genfile.c:612 utils/adt/misc.c:340 +#: replication/logical/snapbuild.c:1717 replication/logical/snapbuild.c:2220 storage/file/fd.c:1968 storage/file/fd.c:2054 storage/file/fd.c:3564 utils/adt/dbsize.c:105 utils/adt/dbsize.c:257 utils/adt/dbsize.c:337 utils/adt/genfile.c:437 utils/adt/genfile.c:612 utils/adt/misc.c:340 #, c-format msgid "could not stat file \"%s\": %m" msgstr "ファイル\"%s\"のstatに失敗しました: %m" @@ -236,7 +236,7 @@ msgstr "ディレクトリ\"%s\"をオープンできませんでした: %m" msgid "could not read directory \"%s\": %m" msgstr "ディレクトリ\"%s\"を読み取れませんでした: %m" -#: ../common/file_utils.c:498 access/transam/xlogarchive.c:389 postmaster/pgarch.c:834 postmaster/syslogger.c:1559 replication/logical/snapbuild.c:1831 replication/slot.c:936 replication/slot.c:1998 replication/slot.c:2140 storage/file/fd.c:838 utils/time/snapmgr.c:1255 +#: ../common/file_utils.c:498 access/transam/xlogarchive.c:389 postmaster/pgarch.c:834 postmaster/syslogger.c:1559 replication/logical/snapbuild.c:1878 replication/slot.c:936 replication/slot.c:1998 replication/slot.c:2140 storage/file/fd.c:838 utils/time/snapmgr.c:1255 #, c-format msgid "could not rename file \"%s\" to \"%s\": %m" msgstr "ファイル\"%s\"の名前を\"%s\"に変更できませんでした: %m" @@ -245,101 +245,101 @@ msgstr "ファイル\"%s\"の名前を\"%s\"に変更できませんでした: % msgid "internal error" msgstr "内部エラー" -#: ../common/jsonapi.c:2121 +#: ../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "再帰降下パーサーは差分字句解析器を使用できません。" -#: ../common/jsonapi.c:2123 +#: ../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "差分パーサーは差分字句解析器を必要とします。" -#: ../common/jsonapi.c:2125 +#: ../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "JSONのネストが深すぎます、可能な最大の深さは6400です。" -#: ../common/jsonapi.c:2127 +#: ../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "エスケープシーケンス\"\\%.*s\"は不正です。" -#: ../common/jsonapi.c:2131 +#: ../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "0x%02x値を持つ文字はエスケープしなければなりません" -#: ../common/jsonapi.c:2135 +#: ../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "入力の終端を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2138 +#: ../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "配列要素または\"]\"を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2141 +#: ../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "\",\"または\"]\"を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2144 +#: ../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "\":\"を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2147 +#: ../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "JSON値を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2150 +#: ../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "入力文字列が予期せず終了しました。" -#: ../common/jsonapi.c:2152 +#: ../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "文字列または\"}\"を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2155 +#: ../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "\",\"または\"}\"を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2158 +#: ../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "文字列を想定していましたが、\"%.*s\"でした。" -#: ../common/jsonapi.c:2161 +#: ../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "トークン\"%.*s\"は不正です。" -#: ../common/jsonapi.c:2164 jsonpath_scan.l:608 +#: ../common/jsonapi.c:2167 jsonpath_scan.l:608 #, c-format msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 はテキストに変換できません。" -#: ../common/jsonapi.c:2166 +#: ../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "\"\\u\"の後には16進数の4桁が続かなければなりません。" -#: ../common/jsonapi.c:2169 +#: ../common/jsonapi.c:2172 msgid "Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8." msgstr "エンコーディングがUTF-8ではない場合、コードポイントの値が 007F 以上についてはUnicodeエスケープの値は使用できません。" -#: ../common/jsonapi.c:2178 +#: ../common/jsonapi.c:2181 #, c-format msgid "Unicode escape value could not be translated to the server's encoding %s." msgstr "Unicodeエスケープの値がサーバーエンコーディング%sに変換できませんでした。" -#: ../common/jsonapi.c:2185 jsonpath_scan.l:641 +#: ../common/jsonapi.c:2188 jsonpath_scan.l:641 #, c-format msgid "Unicode high surrogate must not follow a high surrogate." msgstr "Unicodeのハイサロゲートはハイサロゲートに続いてはいけません。" -#: ../common/jsonapi.c:2187 jsonpath_scan.l:652 jsonpath_scan.l:662 jsonpath_scan.l:713 +#: ../common/jsonapi.c:2190 jsonpath_scan.l:652 jsonpath_scan.l:662 jsonpath_scan.l:713 #, c-format msgid "Unicode low surrogate must follow a high surrogate." msgstr "Unicodeのローサロゲートはハイサロゲートに続かなければなりません。" @@ -581,7 +581,7 @@ msgstr "制限付きトークンで再実行できませんでした: %lu" msgid "could not get exit code from subprocess: error code %lu" msgstr "サブプロセスの終了コードを取得できませんでした: エラーコード %lu" -#: ../common/rmtree.c:97 access/heap/rewriteheap.c:1214 access/transam/twophase.c:1717 access/transam/xlogarchive.c:119 access/transam/xlogarchive.c:399 postmaster/postmaster.c:1048 postmaster/syslogger.c:1488 replication/logical/origin.c:591 replication/logical/reorderbuffer.c:4589 replication/logical/snapbuild.c:1712 replication/logical/snapbuild.c:2146 replication/slot.c:2192 storage/file/fd.c:878 storage/file/fd.c:3378 storage/file/fd.c:3440 +#: ../common/rmtree.c:97 access/heap/rewriteheap.c:1214 access/transam/twophase.c:1717 access/transam/xlogarchive.c:119 access/transam/xlogarchive.c:399 postmaster/postmaster.c:1048 postmaster/syslogger.c:1488 replication/logical/origin.c:591 replication/logical/reorderbuffer.c:4719 replication/logical/snapbuild.c:1759 replication/logical/snapbuild.c:2193 replication/slot.c:2192 storage/file/fd.c:878 storage/file/fd.c:3378 storage/file/fd.c:3440 #: storage/file/reinit.c:261 storage/ipc/dsm.c:343 storage/smgr/md.c:381 storage/smgr/md.c:440 storage/sync/sync.c:243 utils/time/snapmgr.c:1591 #, c-format msgid "could not remove file \"%s\": %m" @@ -792,7 +792,7 @@ msgstr "インデックス\"%s\"は有効ではありません" msgid "cannot accept a value of type %s" msgstr "%s型の値は受け付けられません" -#: access/brin/brin_pageops.c:75 access/brin/brin_pageops.c:361 access/brin/brin_pageops.c:851 access/gin/ginentrypage.c:109 access/gist/gist.c:1463 access/spgist/spgdoinsert.c:2001 access/spgist/spgdoinsert.c:2278 +#: access/brin/brin_pageops.c:75 access/brin/brin_pageops.c:361 access/brin/brin_pageops.c:851 access/gin/ginentrypage.c:109 access/gist/gist.c:1470 access/spgist/spgdoinsert.c:2001 access/spgist/spgdoinsert.c:2278 #, c-format msgid "index row size %zu exceeds maximum %zu for index \"%s\"" msgstr "インデックス行サイズ%1$zuはインデックス\"%3$s\"での最大値%2$zuを超えています" @@ -915,67 +915,72 @@ msgstr "ユーザー定義リレーションのパラメータ型の制限を超 msgid "RESET must not include values for parameters" msgstr "RESETにはパラメータの値を含めてはいけません" -#: access/common/reloptions.c:1263 +#: access/common/reloptions.c:1264 #, c-format msgid "unrecognized parameter namespace \"%s\"" msgstr "認識できないパラメータ namaspace \"%s\"" -#: access/common/reloptions.c:1300 commands/variable.c:1214 +#: access/common/reloptions.c:1294 commands/foreigncmds.c:86 +#, c-format +msgid "invalid option name \"%s\": must not contain \"=\"" +msgstr "不正なオプション名\"%s\": \"=\"が含まれていてはなりません" + +#: access/common/reloptions.c:1309 commands/variable.c:1214 #, c-format msgid "tables declared WITH OIDS are not supported" msgstr "WITH OIDSと定義されたテーブルはサポートされません" -#: access/common/reloptions.c:1468 +#: access/common/reloptions.c:1477 #, c-format msgid "unrecognized parameter \"%s\"" msgstr "認識できないラメータ \"%s\"" -#: access/common/reloptions.c:1580 +#: access/common/reloptions.c:1589 #, c-format msgid "parameter \"%s\" specified more than once" msgstr "パラメータ\"%s\"が複数回指定されました" -#: access/common/reloptions.c:1596 +#: access/common/reloptions.c:1605 #, c-format msgid "invalid value for boolean option \"%s\": %s" msgstr "不正なブール型オプションの値 \"%s\": %s" -#: access/common/reloptions.c:1608 +#: access/common/reloptions.c:1617 #, c-format msgid "invalid value for integer option \"%s\": %s" msgstr "不正な整数型オプションの値 \"%s\": %s" -#: access/common/reloptions.c:1614 access/common/reloptions.c:1634 +#: access/common/reloptions.c:1623 access/common/reloptions.c:1643 #, c-format msgid "value %s out of bounds for option \"%s\"" msgstr "値%sはオプション\"%s\"の範囲外です" -#: access/common/reloptions.c:1616 +#: access/common/reloptions.c:1625 #, c-format msgid "Valid values are between \"%d\" and \"%d\"." msgstr "有効な値の範囲は\"%d\"~\"%d\"です。" -#: access/common/reloptions.c:1628 +#: access/common/reloptions.c:1637 #, c-format msgid "invalid value for floating point option \"%s\": %s" msgstr "不正な浮動小数点型オプションの値 \"%s\": %s" -#: access/common/reloptions.c:1636 +#: access/common/reloptions.c:1645 #, c-format msgid "Valid values are between \"%f\" and \"%f\"." msgstr "有効な値の範囲は\"%f\"~\"%f\"です。" -#: access/common/reloptions.c:1658 +#: access/common/reloptions.c:1667 #, c-format msgid "invalid value for enum option \"%s\": %s" msgstr "不正な列挙型オプションの値 \"%s\": %s" -#: access/common/reloptions.c:1989 +#: access/common/reloptions.c:1998 #, c-format msgid "cannot specify storage parameters for a partitioned table" msgstr "パーティション親テーブルに対してストレージパラメータは指定できません" -#: access/common/reloptions.c:1990 +#: access/common/reloptions.c:1999 #, c-format msgid "Specify storage parameters for its leaf partitions instead." msgstr "代わりにリーフパーティションに対してストレージパラメータを指定してください。" @@ -1065,7 +1070,7 @@ msgstr "これは、PostgreSQL 9.1へアップグレードする前のクラッ msgid "Please REINDEX it." msgstr "REINDEXを行ってください。" -#: access/gist/gist.c:1196 +#: access/gist/gist.c:1203 #, c-format msgid "fixing incomplete split in index \"%s\", block %u" msgstr "インデックス\"%s\"内の不完全な分割を修正します、ブロック%u" @@ -1105,7 +1110,7 @@ msgstr "アクセスメソッド\"%2$s\"の演算子族\"%1$s\"は演算子%3$s msgid "could not determine which collation to use for string hashing" msgstr "文字列のハッシュ値計算で使用する照合順序を特定できませんでした" -#: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:673 catalog/heap.c:679 commands/createas.c:201 commands/createas.c:508 commands/indexcmds.c:2021 commands/tablecmds.c:18211 commands/view.c:81 regex/regc_pg_locale.c:245 utils/adt/formatting.c:1653 utils/adt/formatting.c:1801 utils/adt/formatting.c:1991 utils/adt/like.c:189 utils/adt/like_support.c:1024 utils/adt/varchar.c:738 utils/adt/varchar.c:1009 utils/adt/varchar.c:1066 +#: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:673 catalog/heap.c:679 commands/createas.c:201 commands/createas.c:508 commands/indexcmds.c:2021 commands/tablecmds.c:18219 commands/view.c:81 regex/regc_pg_locale.c:245 utils/adt/formatting.c:1653 utils/adt/formatting.c:1801 utils/adt/formatting.c:1991 utils/adt/like.c:189 utils/adt/like_support.c:1024 utils/adt/varchar.c:738 utils/adt/varchar.c:1009 utils/adt/varchar.c:1066 #: utils/adt/varlena.c:1521 #, c-format msgid "Use the COLLATE clause to set the collation explicitly." @@ -1156,37 +1161,37 @@ msgstr "アクセスメソッド\"%2$s\"の演算子族\"%1$s\"は演算子%3$s msgid "operator family \"%s\" of access method %s is missing cross-type operator(s)" msgstr "アクセスメソッド\"%2$s\"の演算子族\"%1$s\"は異なる型間に対応する演算子を含んでいません" -#: access/heap/heapam.c:2206 +#: access/heap/heapam.c:2241 #, c-format msgid "cannot insert tuples in a parallel worker" msgstr "並列ワーカーではタプルの挿入はできません" -#: access/heap/heapam.c:2725 +#: access/heap/heapam.c:2764 #, c-format msgid "cannot delete tuples during a parallel operation" msgstr "並列処理中はタプルの削除はできません" -#: access/heap/heapam.c:2772 +#: access/heap/heapam.c:2811 #, c-format msgid "attempted to delete invisible tuple" msgstr "不可視のタプルを削除しようとしました" -#: access/heap/heapam.c:3220 access/heap/heapam.c:6501 access/index/genam.c:818 +#: access/heap/heapam.c:3261 access/heap/heapam.c:6542 access/index/genam.c:818 #, c-format msgid "cannot update tuples during a parallel operation" msgstr "並列処理中はタプルの更新はできません" -#: access/heap/heapam.c:3397 +#: access/heap/heapam.c:3438 #, c-format msgid "attempted to update invisible tuple" msgstr "不可視のタプルを更新しようとしました" -#: access/heap/heapam.c:4908 access/heap/heapam.c:4946 access/heap/heapam.c:5211 access/heap/heapam_handler.c:468 +#: access/heap/heapam.c:4949 access/heap/heapam.c:4987 access/heap/heapam.c:5252 access/heap/heapam_handler.c:468 #, c-format msgid "could not obtain lock on row in relation \"%s\"" msgstr "リレーション\"%s\"の行ロックを取得できませんでした" -#: access/heap/heapam.c:6314 commands/trigger.c:3340 executor/nodeModifyTable.c:2376 executor/nodeModifyTable.c:2467 +#: access/heap/heapam.c:6355 commands/trigger.c:3340 executor/nodeModifyTable.c:2397 executor/nodeModifyTable.c:2488 #, c-format msgid "tuple to be updated was already modified by an operation triggered by the current command" msgstr "更新対象のタプルはすでに現在のコマンドによって起動された操作によって変更されています" @@ -1206,7 +1211,7 @@ msgstr "行が大きすぎます: サイズは%zu、上限は%zu" msgid "could not write to file \"%s\", wrote %d of %d: %m" msgstr "ファイル\"%1$s\"に書き込めませんでした、%3$dバイト中%2$dバイト書き込みました: %m" -#: access/heap/rewriteheap.c:977 access/heap/rewriteheap.c:1094 access/transam/timeline.c:329 access/transam/timeline.c:481 access/transam/xlog.c:3255 access/transam/xlog.c:3446 access/transam/xlog.c:4283 access/transam/xlog.c:9269 access/transam/xlogfuncs.c:692 backup/basebackup_server.c:149 backup/basebackup_server.c:242 commands/dbcommands.c:494 postmaster/launch_backend.c:340 postmaster/postmaster.c:4114 postmaster/walsummarizer.c:1212 +#: access/heap/rewriteheap.c:977 access/heap/rewriteheap.c:1094 access/transam/timeline.c:329 access/transam/timeline.c:481 access/transam/xlog.c:3256 access/transam/xlog.c:3447 access/transam/xlog.c:4284 access/transam/xlog.c:9312 access/transam/xlogfuncs.c:692 backup/basebackup_server.c:149 backup/basebackup_server.c:242 commands/dbcommands.c:494 postmaster/launch_backend.c:340 postmaster/postmaster.c:4114 postmaster/walsummarizer.c:1212 #: replication/logical/origin.c:603 replication/slot.c:2059 storage/file/copydir.c:157 storage/smgr/md.c:230 utils/time/snapmgr.c:1234 #, c-format msgid "could not create file \"%s\": %m" @@ -1217,7 +1222,7 @@ msgstr "ファイル\"%s\"を作成できませんでした: %m" msgid "could not truncate file \"%s\" to %u: %m" msgstr "ファイル\"%s\"を%uバイトに切り詰められませんでした: %m" -#: access/heap/rewriteheap.c:1122 access/transam/timeline.c:384 access/transam/timeline.c:424 access/transam/timeline.c:498 access/transam/xlog.c:3305 access/transam/xlog.c:3502 access/transam/xlog.c:4295 commands/dbcommands.c:506 postmaster/launch_backend.c:351 postmaster/launch_backend.c:363 replication/logical/origin.c:615 replication/logical/origin.c:657 replication/logical/origin.c:676 replication/logical/snapbuild.c:1788 replication/slot.c:2094 +#: access/heap/rewriteheap.c:1122 access/transam/timeline.c:384 access/transam/timeline.c:424 access/transam/timeline.c:498 access/transam/xlog.c:3306 access/transam/xlog.c:3503 access/transam/xlog.c:4296 commands/dbcommands.c:506 postmaster/launch_backend.c:351 postmaster/launch_backend.c:363 replication/logical/origin.c:615 replication/logical/origin.c:657 replication/logical/origin.c:676 replication/logical/snapbuild.c:1835 replication/slot.c:2094 #: storage/file/buffile.c:545 storage/file/copydir.c:197 utils/init/miscinit.c:1655 utils/init/miscinit.c:1666 utils/init/miscinit.c:1674 utils/misc/guc.c:4491 utils/misc/guc.c:4522 utils/misc/guc.c:5675 utils/misc/guc.c:5693 utils/time/snapmgr.c:1239 utils/time/snapmgr.c:1246 #, c-format msgid "could not write to file \"%s\": %m" @@ -1460,7 +1465,7 @@ msgstr "システムカタログのスキャン中にトランザクションが msgid "cannot access index \"%s\" while it is being reindexed" msgstr "再作成中であるためインデックス\"%s\"にアクセスできません" -#: access/index/indexam.c:203 catalog/objectaddress.c:1356 commands/indexcmds.c:2851 commands/tablecmds.c:281 commands/tablecmds.c:305 commands/tablecmds.c:17906 commands/tablecmds.c:19795 +#: access/index/indexam.c:203 catalog/objectaddress.c:1356 commands/indexcmds.c:2851 commands/tablecmds.c:281 commands/tablecmds.c:305 commands/tablecmds.c:17914 commands/tablecmds.c:19811 #, c-format msgid "\"%s\" is not an index" msgstr "\"%s\"はインデックスではありません" @@ -1505,17 +1510,17 @@ msgstr "インデックス\"%s\"に削除処理中の内部ページがありま msgid "This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it." msgstr "これは9.3かそれ以前のバージョンで、アップグレード前にVACUUMが中断された際に起きた可能性があります。REINDEXしてください。" -#: access/nbtree/nbtutils.c:5108 +#: access/nbtree/nbtutils.c:5114 #, c-format msgid "index row size %zu exceeds btree version %u maximum %zu for index \"%s\"" msgstr "インデックス行サイズ%1$zuはインデックス\"%4$s\"でのbtreeバージョン %2$u の最大値%3$zuを超えています" -#: access/nbtree/nbtutils.c:5114 +#: access/nbtree/nbtutils.c:5120 #, c-format msgid "Index row references tuple (%u,%u) in relation \"%s\"." msgstr "インデックス行はリレーション\"%3$s\"のタプル(%1$u,%2$u)を参照しています。" -#: access/nbtree/nbtutils.c:5118 +#: access/nbtree/nbtutils.c:5124 #, c-format msgid "" "Values larger than 1/3 of a buffer page cannot be indexed.\n" @@ -1991,7 +1996,7 @@ msgstr "ファイル\"%s\"内に格納されているサイズが不正です" msgid "calculated CRC checksum does not match value stored in file \"%s\"" msgstr "算出されたCRCチェックサムがファイル\"%s\"に格納されている値と一致しません" -#: access/transam/twophase.c:1419 access/transam/xlogrecovery.c:565 postmaster/walsummarizer.c:936 replication/logical/logical.c:211 replication/walsender.c:836 +#: access/transam/twophase.c:1419 access/transam/xlogrecovery.c:565 postmaster/walsummarizer.c:936 replication/logical/logical.c:213 replication/walsender.c:840 #, c-format msgid "Failed while allocating a WAL reading processor." msgstr "WALリーダの割り当てに中に失敗しました。" @@ -2225,445 +2230,445 @@ msgstr "パラレル処理中にセーブポイントのロールバックはで msgid "cannot have more than 2^32-1 subtransactions in a transaction" msgstr "1トランザクション内には 2^32-1 個より多くのサブトランザクションを作成できません" -#: access/transam/xlog.c:1542 +#: access/transam/xlog.c:1543 #, c-format msgid "request to flush past end of generated WAL; request %X/%X, current position %X/%X" msgstr "生成されたWALより先の位置までのフラッシュ要求; 要求 %X/%X, 現在位置 %X/%X" -#: access/transam/xlog.c:1769 +#: access/transam/xlog.c:1770 #, c-format msgid "cannot read past end of generated WAL: requested %X/%X, current position %X/%X" msgstr "生成されたWALより先の位置までの読み込み要求; 要求 %X/%X, 現在位置 %X/%X" -#: access/transam/xlog.c:2210 access/transam/xlog.c:4501 +#: access/transam/xlog.c:2211 access/transam/xlog.c:4502 #, c-format msgid "The WAL segment size must be a power of two between 1 MB and 1 GB." msgstr "WALセグメントサイズは1MBから1GBまでの間の2の累乗でなければなりません。" -#: access/transam/xlog.c:2228 +#: access/transam/xlog.c:2229 #, c-format msgid "\"%s\" must be set to -1 during binary upgrade mode." msgstr "バイナリアップグレードモード中は\"%s\"は-1に設定されている必要があります。" -#: access/transam/xlog.c:2477 +#: access/transam/xlog.c:2478 #, c-format msgid "could not write to log file \"%s\" at offset %u, length %zu: %m" msgstr "ログファイル \"%s\" のオフセット%uに長さ%zuの書き込みができませんでした: %m" -#: access/transam/xlog.c:3739 access/transam/xlogutils.c:831 replication/walsender.c:3045 +#: access/transam/xlog.c:3740 access/transam/xlogutils.c:831 replication/walsender.c:3079 #, c-format msgid "requested WAL segment %s has already been removed" msgstr "要求された WAL セグメント %s はすでに削除されています" -#: access/transam/xlog.c:4061 +#: access/transam/xlog.c:4062 #, c-format msgid "could not rename file \"%s\": %m" msgstr "ファイル\"%s\"の名前を変更できませんでした: %m" -#: access/transam/xlog.c:4104 access/transam/xlog.c:4115 access/transam/xlog.c:4136 +#: access/transam/xlog.c:4105 access/transam/xlog.c:4116 access/transam/xlog.c:4137 #, c-format msgid "required WAL directory \"%s\" does not exist" msgstr "WALディレクトリ\"%s\"は存在しません" -#: access/transam/xlog.c:4121 access/transam/xlog.c:4142 +#: access/transam/xlog.c:4122 access/transam/xlog.c:4143 #, c-format msgid "creating missing WAL directory \"%s\"" msgstr "なかったWALディレクトリ\"%s\"を作成しています" -#: access/transam/xlog.c:4125 access/transam/xlog.c:4145 commands/dbcommands.c:3262 +#: access/transam/xlog.c:4126 access/transam/xlog.c:4146 commands/dbcommands.c:3262 #, c-format msgid "could not create missing directory \"%s\": %m" msgstr "なかったディレクトリ\"%s\"の作成に失敗しました: %m" -#: access/transam/xlog.c:4212 +#: access/transam/xlog.c:4213 #, c-format msgid "could not generate secret authorization token" msgstr "秘密の認証トークンを生成できませんでした" -#: access/transam/xlog.c:4363 access/transam/xlog.c:4373 access/transam/xlog.c:4399 access/transam/xlog.c:4407 access/transam/xlog.c:4415 access/transam/xlog.c:4421 access/transam/xlog.c:4429 access/transam/xlog.c:4437 access/transam/xlog.c:4445 access/transam/xlog.c:4453 access/transam/xlog.c:4461 access/transam/xlog.c:4469 access/transam/xlog.c:4479 access/transam/xlog.c:4487 utils/init/miscinit.c:1812 +#: access/transam/xlog.c:4364 access/transam/xlog.c:4374 access/transam/xlog.c:4400 access/transam/xlog.c:4408 access/transam/xlog.c:4416 access/transam/xlog.c:4422 access/transam/xlog.c:4430 access/transam/xlog.c:4438 access/transam/xlog.c:4446 access/transam/xlog.c:4454 access/transam/xlog.c:4462 access/transam/xlog.c:4470 access/transam/xlog.c:4480 access/transam/xlog.c:4488 utils/init/miscinit.c:1812 #, c-format msgid "database files are incompatible with server" msgstr "データベースファイルがサーバーと互換性がありません" -#: access/transam/xlog.c:4364 +#: access/transam/xlog.c:4365 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d (0x%08x), but the server was compiled with PG_CONTROL_VERSION %d (0x%08x)." msgstr "データベースクラスタはPG_CONTROL_VERSION %d (0x%08x)で初期化されましたが、サーバーはPG_CONTROL_VERSION %d (0x%08x)でコンパイルされています。" -#: access/transam/xlog.c:4368 +#: access/transam/xlog.c:4369 #, c-format msgid "This could be a problem of mismatched byte ordering. It looks like you need to initdb." msgstr "これはバイトオーダの不整合の可能性があります。initdbを実行する必要がありそうです。" -#: access/transam/xlog.c:4374 +#: access/transam/xlog.c:4375 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d, but the server was compiled with PG_CONTROL_VERSION %d." msgstr "データベースクラスタはPG_CONTROL_VERSION %d で初期化されましたが、サーバーは PG_CONTROL_VERSION %d でコンパイルされています。" -#: access/transam/xlog.c:4377 access/transam/xlog.c:4403 access/transam/xlog.c:4411 access/transam/xlog.c:4417 +#: access/transam/xlog.c:4378 access/transam/xlog.c:4404 access/transam/xlog.c:4412 access/transam/xlog.c:4418 #, c-format msgid "It looks like you need to initdb." msgstr "initdbが必要のようです。" -#: access/transam/xlog.c:4389 +#: access/transam/xlog.c:4390 #, c-format msgid "incorrect checksum in control file" msgstr "制御ファイル内のチェックサムが不正です" -#: access/transam/xlog.c:4400 +#: access/transam/xlog.c:4401 #, c-format msgid "The database cluster was initialized with CATALOG_VERSION_NO %d, but the server was compiled with CATALOG_VERSION_NO %d." msgstr "データベースクラスタは CATALOG_VERSION_NO %d で初期化されましたが、サーバーは CATALOG_VERSION_NO %d でコンパイルされています。" -#: access/transam/xlog.c:4408 +#: access/transam/xlog.c:4409 #, c-format msgid "The database cluster was initialized with MAXALIGN %d, but the server was compiled with MAXALIGN %d." msgstr "データベースクラスタは MAXALIGN %d で初期化されましたが、サーバーは MAXALIGN %d でコンパイルされています。" -#: access/transam/xlog.c:4416 +#: access/transam/xlog.c:4417 #, c-format msgid "The database cluster appears to use a different floating-point number format than the server executable." msgstr "データベースクラスタはサーバー実行ファイルと異なる浮動小数点書式を使用しているようです。" -#: access/transam/xlog.c:4422 +#: access/transam/xlog.c:4423 #, c-format msgid "The database cluster was initialized with BLCKSZ %d, but the server was compiled with BLCKSZ %d." msgstr "データベースクラスタは BLCKSZ %d で初期化されましたが、サーバーは BLCKSZ %d でコンパイルされています。" -#: access/transam/xlog.c:4425 access/transam/xlog.c:4433 access/transam/xlog.c:4441 access/transam/xlog.c:4449 access/transam/xlog.c:4457 access/transam/xlog.c:4465 access/transam/xlog.c:4473 access/transam/xlog.c:4482 access/transam/xlog.c:4490 +#: access/transam/xlog.c:4426 access/transam/xlog.c:4434 access/transam/xlog.c:4442 access/transam/xlog.c:4450 access/transam/xlog.c:4458 access/transam/xlog.c:4466 access/transam/xlog.c:4474 access/transam/xlog.c:4483 access/transam/xlog.c:4491 #, c-format msgid "It looks like you need to recompile or initdb." msgstr "再コンパイルもしくは initdb が必要そうです。" -#: access/transam/xlog.c:4430 +#: access/transam/xlog.c:4431 #, c-format msgid "The database cluster was initialized with RELSEG_SIZE %d, but the server was compiled with RELSEG_SIZE %d." msgstr "データベースクラスタは RELSEG_SIZE %d で初期化されましたが、サーバーは RELSEG_SIZE %d でコンパイルされています。" -#: access/transam/xlog.c:4438 +#: access/transam/xlog.c:4439 #, c-format msgid "The database cluster was initialized with XLOG_BLCKSZ %d, but the server was compiled with XLOG_BLCKSZ %d." msgstr "データベースクラスタは XLOG_BLCKSZ %d で初期化されましたが、サーバーは XLOG_BLCKSZ %d でコンパイルされています。" -#: access/transam/xlog.c:4446 +#: access/transam/xlog.c:4447 #, c-format msgid "The database cluster was initialized with NAMEDATALEN %d, but the server was compiled with NAMEDATALEN %d." msgstr "データベースクラスタは NAMEDATALEN %d で初期化されましたが、サーバーは NAMEDATALEN %d でコンパイルされています。" -#: access/transam/xlog.c:4454 +#: access/transam/xlog.c:4455 #, c-format msgid "The database cluster was initialized with INDEX_MAX_KEYS %d, but the server was compiled with INDEX_MAX_KEYS %d." msgstr "データベースクラスタは INDEX_MAX_KEYS %d で初期化されましたが、サーバーは INDEX_MAX_KEYS %d でコンパイルされています。" -#: access/transam/xlog.c:4462 +#: access/transam/xlog.c:4463 #, c-format msgid "The database cluster was initialized with TOAST_MAX_CHUNK_SIZE %d, but the server was compiled with TOAST_MAX_CHUNK_SIZE %d." msgstr "データベースクラスタは TOAST_MAX_CHUNK_SIZE %d で初期化されましたが、サーバーは TOAST_MAX_CHUNK_SIZE %d でコンパイルされています。" -#: access/transam/xlog.c:4470 +#: access/transam/xlog.c:4471 #, c-format msgid "The database cluster was initialized with LOBLKSIZE %d, but the server was compiled with LOBLKSIZE %d." msgstr "データベースクラスタは LOBLKSIZE %d で初期化されましたが、サーバーは LOBLKSIZE %d でコンパイルされています。" -#: access/transam/xlog.c:4480 +#: access/transam/xlog.c:4481 #, c-format msgid "The database cluster was initialized without USE_FLOAT8_BYVAL but the server was compiled with USE_FLOAT8_BYVAL." msgstr "データベースクラスタは USE_FLOAT8_BYVAL なしで初期化されましたが、サーバー側は USE_FLOAT8_BYVAL 付きでコンパイルされています。" -#: access/transam/xlog.c:4488 +#: access/transam/xlog.c:4489 #, c-format msgid "The database cluster was initialized with USE_FLOAT8_BYVAL but the server was compiled without USE_FLOAT8_BYVAL." msgstr "データベースクラスタは USE_FLOAT8_BYVAL 付きで初期化されましたが、サーバー側は USE_FLOAT8_BYVAL なしでコンパイルされています。" -#: access/transam/xlog.c:4497 +#: access/transam/xlog.c:4498 #, c-format msgid "invalid WAL segment size in control file (%d byte)" msgid_plural "invalid WAL segment size in control file (%d bytes)" msgstr[0] "制御ファイル中の不正なWALセグメントサイズ (%dバイト)" -#: access/transam/xlog.c:4510 +#: access/transam/xlog.c:4511 #, c-format msgid "\"min_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"min_wal_size\"は\"wal_segment_size\"の2倍以上でなければなりません" -#: access/transam/xlog.c:4514 +#: access/transam/xlog.c:4515 #, c-format msgid "\"max_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"max_wal_size\"は\"wal_segment_size\"の2倍以上でなければなりません" -#: access/transam/xlog.c:4662 catalog/namespace.c:4696 commands/tablespace.c:1210 commands/user.c:2529 commands/variable.c:72 replication/slot.c:2446 tcop/postgres.c:3715 utils/error/elog.c:2247 +#: access/transam/xlog.c:4663 catalog/namespace.c:4696 commands/tablespace.c:1210 commands/user.c:2529 commands/variable.c:72 replication/slot.c:2446 tcop/postgres.c:3715 utils/error/elog.c:2247 #, c-format msgid "List syntax is invalid." msgstr "リスト文法が無効です" -#: access/transam/xlog.c:4708 commands/user.c:2545 commands/variable.c:173 tcop/postgres.c:3731 utils/error/elog.c:2273 +#: access/transam/xlog.c:4709 commands/user.c:2545 commands/variable.c:173 tcop/postgres.c:3731 utils/error/elog.c:2273 #, c-format msgid "Unrecognized key word: \"%s\"." msgstr "不明なキーワードです: \"%s\"" -#: access/transam/xlog.c:5129 +#: access/transam/xlog.c:5130 #, c-format msgid "could not write bootstrap write-ahead log file: %m" msgstr "ブートストラップの先行書き込みログファイルに書き込めませんでした: %m" -#: access/transam/xlog.c:5137 +#: access/transam/xlog.c:5138 #, c-format msgid "could not fsync bootstrap write-ahead log file: %m" msgstr "ブートストラップの先行書き込みログファイルをfsyncできませんでした: %m" -#: access/transam/xlog.c:5143 +#: access/transam/xlog.c:5144 #, c-format msgid "could not close bootstrap write-ahead log file: %m" msgstr "ブートストラップの先行書き込みログファイルをクローズできませんでした: %m" -#: access/transam/xlog.c:5368 +#: access/transam/xlog.c:5369 #, c-format msgid "WAL was generated with \"wal_level=minimal\", cannot continue recovering" msgstr "\"wal_level=minimal\"でWALが生成されました、リカバリは続行不可です" -#: access/transam/xlog.c:5369 +#: access/transam/xlog.c:5370 #, c-format msgid "This happens if you temporarily set \"wal_level=minimal\" on the server." msgstr "これはこのサーバーで一時的に\"wal_level=minimal\"にした場合に起こります。" -#: access/transam/xlog.c:5370 +#: access/transam/xlog.c:5371 #, c-format msgid "Use a backup taken after setting \"wal_level\" to higher than \"minimal\"." msgstr "\"wal_level\"を\"minimal\"より上位に設定したあとに取得したバックアップを使用してください。" -#: access/transam/xlog.c:5435 +#: access/transam/xlog.c:5436 #, c-format msgid "control file contains invalid checkpoint location" msgstr "制御ファイル内のチェックポイント位置が不正です" -#: access/transam/xlog.c:5446 +#: access/transam/xlog.c:5447 #, c-format msgid "database system was shut down at %s" msgstr "データベースシステムは %s にシャットダウンしました" -#: access/transam/xlog.c:5452 +#: access/transam/xlog.c:5453 #, c-format msgid "database system was shut down in recovery at %s" msgstr "データベースシステムはリカバリ中 %s にシャットダウンしました" -#: access/transam/xlog.c:5458 +#: access/transam/xlog.c:5459 #, c-format msgid "database system shutdown was interrupted; last known up at %s" msgstr "データベースシステムはシャットダウン中に中断されました; %s まで動作していたことは確認できます" -#: access/transam/xlog.c:5464 +#: access/transam/xlog.c:5465 #, c-format msgid "database system was interrupted while in recovery at %s" msgstr "データベースシステムはリカバリ中 %s に中断されました" -#: access/transam/xlog.c:5466 +#: access/transam/xlog.c:5467 #, c-format msgid "This probably means that some data is corrupted and you will have to use the last backup for recovery." msgstr "これはおそらくデータ破損があり、リカバリのために直前のバックアップを使用しなければならないことを意味します。" -#: access/transam/xlog.c:5472 +#: access/transam/xlog.c:5473 #, c-format msgid "database system was interrupted while in recovery at log time %s" msgstr "データベースシステムはリカバリ中ログ時刻 %s に中断されました" -#: access/transam/xlog.c:5474 +#: access/transam/xlog.c:5475 #, c-format msgid "If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target." msgstr "これが1回以上起きた場合はデータが破損している可能性があるため、より以前のリカバリ目標を選ぶ必要があるかもしれません。" -#: access/transam/xlog.c:5480 +#: access/transam/xlog.c:5481 #, c-format msgid "database system was interrupted; last known up at %s" msgstr "データベースシステムは中断されました: %s まで動作していたことは確認できます" -#: access/transam/xlog.c:5487 +#: access/transam/xlog.c:5488 #, c-format msgid "control file contains invalid database cluster state" msgstr "制御ファイル内のデータベース・クラスタ状態が不正です" -#: access/transam/xlog.c:5875 +#: access/transam/xlog.c:5876 #, c-format msgid "WAL ends before end of online backup" msgstr "オンラインバックアップの終了より前にWALが終了しました" -#: access/transam/xlog.c:5876 +#: access/transam/xlog.c:5877 #, c-format msgid "All WAL generated while online backup was taken must be available at recovery." msgstr "オンラインバックアップ中に生成されたすべてのWALがリカバリで利用可能である必要があります。" -#: access/transam/xlog.c:5880 +#: access/transam/xlog.c:5881 #, c-format msgid "WAL ends before consistent recovery point" msgstr "WALが一貫性があるリカバリポイントより前で終了しました" -#: access/transam/xlog.c:5926 +#: access/transam/xlog.c:5927 #, c-format msgid "selected new timeline ID: %u" msgstr "新しいタイムラインIDを選択: %u" -#: access/transam/xlog.c:5959 +#: access/transam/xlog.c:5960 #, c-format msgid "archive recovery complete" msgstr "アーカイブリカバリが完了しました" -#: access/transam/xlog.c:6612 +#: access/transam/xlog.c:6613 #, c-format msgid "shutting down" msgstr "シャットダウンしています" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6651 +#: access/transam/xlog.c:6652 #, c-format msgid "restartpoint starting:%s%s%s%s%s%s%s%s" msgstr "リスタートポイント開始:%s%s%s%s%s%s%s%s" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6663 +#: access/transam/xlog.c:6664 #, c-format msgid "checkpoint starting:%s%s%s%s%s%s%s%s" msgstr "チェックポイント開始:%s%s%s%s%s%s%s%s" -#: access/transam/xlog.c:6728 +#: access/transam/xlog.c:6729 #, c-format msgid "restartpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "リスタートポイント完了: %d個のバッファを出力 (%.1f%%); %d個のWALファイルを追加、%d個を削除、%d個を再利用; 書き出し=%ld.%03d秒, 同期=%ld.%03d秒, 全体=%ld.%03d秒; 同期したファイル=%d, 最長=%ld.%03d秒, 平均=%ld.%03d秒; 距離=%d kB, 予測=%d kB; lsn=%X/%X, 再生lsn=%X/%X" -#: access/transam/xlog.c:6751 +#: access/transam/xlog.c:6752 #, c-format msgid "checkpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "チェックポイント完了: %d個のバッファを出力 (%.1f%%); %d個のWALファイルを追加、%d個を削除、%d個を再利用; 書き出し=%ld.%03d秒, 同期=%ld.%03d秒, 全体=%ld.%03d秒; 同期したファイル=%d, 最長=%ld.%03d秒, 平均=%ld.%03d秒; 距離=%d kB, 予測=%d kB; lsn=%X/%X, 再生lsn=%X/%X" -#: access/transam/xlog.c:7233 +#: access/transam/xlog.c:7244 #, c-format msgid "concurrent write-ahead log activity while database system is shutting down" msgstr "データベースのシャットダウンに並行して、先行書き込みログが発生しました" -#: access/transam/xlog.c:7818 +#: access/transam/xlog.c:7859 #, c-format msgid "recovery restart point at %X/%X" msgstr "リカバリ再開ポイントは%X/%Xです" -#: access/transam/xlog.c:7820 +#: access/transam/xlog.c:7861 #, c-format msgid "Last completed transaction was at log time %s." msgstr "最後に完了したトランザクションはログ時刻 %s のものです" -#: access/transam/xlog.c:8082 +#: access/transam/xlog.c:8125 #, c-format msgid "restore point \"%s\" created at %X/%X" msgstr "復帰ポイント\"%s\"が%X/%Xに作成されました" -#: access/transam/xlog.c:8289 +#: access/transam/xlog.c:8332 #, c-format msgid "online backup was canceled, recovery cannot continue" msgstr "オンラインバックアップはキャンセルされ、リカバリを継続できません" -#: access/transam/xlog.c:8347 +#: access/transam/xlog.c:8390 #, c-format msgid "unexpected timeline ID %u (should be %u) in shutdown checkpoint record" msgstr "シャットダウンチェックポイントレコードにおいて想定外のタイムラインID %u(%uのはず)がありました" -#: access/transam/xlog.c:8405 +#: access/transam/xlog.c:8448 #, c-format msgid "unexpected timeline ID %u (should be %u) in online checkpoint record" msgstr "オンラインチェックポイントレコードにおいて想定外のタイムラインID %u(%uのはず)がありました" -#: access/transam/xlog.c:8434 +#: access/transam/xlog.c:8477 #, c-format msgid "unexpected timeline ID %u (should be %u) in end-of-recovery record" msgstr "リカバリ終了チェックポイントレコードにおいて想定外のタイムラインID %u(%uのはず)がありました" -#: access/transam/xlog.c:8705 +#: access/transam/xlog.c:8748 #, c-format msgid "could not fsync write-through file \"%s\": %m" msgstr "ライトスルーファイル\"%s\"をfsyncできませんでした: %m" -#: access/transam/xlog.c:8710 +#: access/transam/xlog.c:8753 #, c-format msgid "could not fdatasync file \"%s\": %m" msgstr "ファイル\"%s\"をfdatasyncできませんでした: %m" -#: access/transam/xlog.c:8797 access/transam/xlog.c:9133 +#: access/transam/xlog.c:8840 access/transam/xlog.c:9176 #, c-format msgid "WAL level not sufficient for making an online backup" msgstr "オンラインバックアップを行うにはWALレベルが不十分です" -#: access/transam/xlog.c:8798 access/transam/xlogfuncs.c:248 +#: access/transam/xlog.c:8841 access/transam/xlogfuncs.c:248 #, c-format msgid "\"wal_level\" must be set to \"replica\" or \"logical\" at server start." msgstr "サーバーの開始時に\"wal_level\"を\"replica\"または \"logical\"にセットする必要があります。" -#: access/transam/xlog.c:8803 +#: access/transam/xlog.c:8846 #, c-format msgid "backup label too long (max %d bytes)" msgstr "バックアップラベルが長すぎます (最大%dバイト)" -#: access/transam/xlog.c:8924 +#: access/transam/xlog.c:8967 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed since last restartpoint" msgstr "\"full_page_writes=off\"で生成されたWALが最終リスタートポイント以降に再生されました" -#: access/transam/xlog.c:8926 access/transam/xlog.c:9222 +#: access/transam/xlog.c:8969 access/transam/xlog.c:9265 #, c-format msgid "This means that the backup being taken on the standby is corrupt and should not be used. Enable \"full_page_writes\" and run CHECKPOINT on the primary, and then try an online backup again." msgstr "つまりこのスタンバイで取得されたバックアップは破損しており、使用すべきではありません。プライマリで\"full_page_writes\"を有効にしCHECKPOINTを実行したのち、再度オンラインバックアップを試行してください。" -#: access/transam/xlog.c:9006 backup/basebackup.c:1417 utils/adt/misc.c:354 +#: access/transam/xlog.c:9049 backup/basebackup.c:1417 utils/adt/misc.c:354 #, c-format msgid "could not read symbolic link \"%s\": %m" msgstr "シンボリックリンク\"%s\"を読めませんでした: %m" -#: access/transam/xlog.c:9013 backup/basebackup.c:1422 utils/adt/misc.c:359 +#: access/transam/xlog.c:9056 backup/basebackup.c:1422 utils/adt/misc.c:359 #, c-format msgid "symbolic link \"%s\" target is too long" msgstr "シンボリックリンク\"%s\"の参照先が長すぎます" -#: access/transam/xlog.c:9134 +#: access/transam/xlog.c:9177 #, c-format msgid "wal_level must be set to \"replica\" or \"logical\" at server start." msgstr "サーバーの開始時にwal_levelを\"replica\"または \"logical\"に設定する必要があります。" -#: access/transam/xlog.c:9172 backup/basebackup.c:1281 +#: access/transam/xlog.c:9215 backup/basebackup.c:1281 #, c-format msgid "the standby was promoted during online backup" msgstr "オンラインバックアップ中にスタンバイが昇格しました" -#: access/transam/xlog.c:9173 backup/basebackup.c:1282 +#: access/transam/xlog.c:9216 backup/basebackup.c:1282 #, c-format msgid "This means that the backup being taken is corrupt and should not be used. Try taking another online backup." msgstr "つまり取得中のバックアップは破損しているため使用してはいけません。再度オンラインバックアップを取得してください。" -#: access/transam/xlog.c:9220 +#: access/transam/xlog.c:9263 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed during online backup" msgstr "\"full_page_writes=off\"で生成されたWALがオンラインバックアップ中に再生されました" -#: access/transam/xlog.c:9336 +#: access/transam/xlog.c:9379 #, c-format msgid "base backup done, waiting for required WAL segments to be archived" msgstr "ベースバックアップ完了、必要な WAL セグメントがアーカイブされるのを待っています" -#: access/transam/xlog.c:9350 +#: access/transam/xlog.c:9393 #, c-format msgid "still waiting for all required WAL segments to be archived (%d seconds elapsed)" msgstr "まだ必要なすべての WAL セグメントがアーカイブされるのを待っています(%d 秒経過)" -#: access/transam/xlog.c:9352 +#: access/transam/xlog.c:9395 #, c-format msgid "Check that your \"archive_command\" is executing properly. You can safely cancel this backup, but the database backup will not be usable without all the WAL segments." msgstr "\"archive_command\"が正しく実行されていることを確認してください。バックアップ処理は安全に取り消すことができますが、全てのWALセグメントがそろわなければこのバックアップは利用できません。" -#: access/transam/xlog.c:9359 +#: access/transam/xlog.c:9402 #, c-format msgid "all required WAL segments have been archived" msgstr "必要なすべての WAL セグメントがアーカイブされました" -#: access/transam/xlog.c:9363 +#: access/transam/xlog.c:9406 #, c-format msgid "WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup" msgstr "WAL アーカイブが有効になっていません。バックアップを完了させるには、すべての必要なWALセグメントが他の方法でコピーされたことを確認してください。" -#: access/transam/xlog.c:9402 +#: access/transam/xlog.c:9445 #, c-format msgid "aborting backup due to backend exiting before pg_backup_stop was called" msgstr "バックエンドがpg_backup_stopの呼び出し前に終了したため、バックアップは異常終了しました" @@ -3943,7 +3948,7 @@ msgid "cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS" msgstr "GRANT/REVOKE ON SCHEMAS を使っている時には IN SCHEMA 句は指定できません" #: catalog/aclchk.c:1617 catalog/catalog.c:659 catalog/objectaddress.c:1523 catalog/pg_publication.c:528 commands/analyze.c:380 commands/copy.c:951 commands/sequence.c:1655 commands/tablecmds.c:7574 commands/tablecmds.c:7728 commands/tablecmds.c:7778 commands/tablecmds.c:7852 commands/tablecmds.c:7922 commands/tablecmds.c:8052 commands/tablecmds.c:8181 commands/tablecmds.c:8275 commands/tablecmds.c:8376 commands/tablecmds.c:8503 commands/tablecmds.c:8533 -#: commands/tablecmds.c:8675 commands/tablecmds.c:8768 commands/tablecmds.c:8902 commands/tablecmds.c:9014 commands/tablecmds.c:12830 commands/tablecmds.c:13022 commands/tablecmds.c:13183 commands/tablecmds.c:14372 commands/tablecmds.c:16999 commands/trigger.c:942 parser/analyze.c:2530 parser/parse_relation.c:737 parser/parse_target.c:1067 parser/parse_type.c:144 parser/parse_utilcmd.c:3409 parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2923 +#: commands/tablecmds.c:8675 commands/tablecmds.c:8768 commands/tablecmds.c:8902 commands/tablecmds.c:9014 commands/tablecmds.c:12838 commands/tablecmds.c:13030 commands/tablecmds.c:13191 commands/tablecmds.c:14380 commands/tablecmds.c:17007 commands/trigger.c:942 parser/analyze.c:2530 parser/parse_relation.c:737 parser/parse_target.c:1067 parser/parse_type.c:144 parser/parse_utilcmd.c:3409 parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2923 #: utils/adt/ruleutils.c:2812 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist" @@ -3954,12 +3959,12 @@ msgstr "リレーション\"%2$s\"の列\"%1$s\"は存在しません" msgid "\"%s\" is an index" msgstr "\"%s\"はインデックスです" -#: catalog/aclchk.c:1869 commands/tablecmds.c:14529 commands/tablecmds.c:17915 +#: catalog/aclchk.c:1869 commands/tablecmds.c:14537 commands/tablecmds.c:17923 #, c-format msgid "\"%s\" is a composite type" msgstr "\"%s\"は複合型です" -#: catalog/aclchk.c:1877 catalog/objectaddress.c:1363 commands/tablecmds.c:263 commands/tablecmds.c:17879 utils/adt/acl.c:2107 utils/adt/acl.c:2137 utils/adt/acl.c:2170 utils/adt/acl.c:2206 utils/adt/acl.c:2237 utils/adt/acl.c:2268 +#: catalog/aclchk.c:1877 catalog/objectaddress.c:1363 commands/tablecmds.c:263 commands/tablecmds.c:17887 utils/adt/acl.c:2107 utils/adt/acl.c:2137 utils/adt/acl.c:2170 utils/adt/acl.c:2206 utils/adt/acl.c:2237 utils/adt/acl.c:2268 #, c-format msgid "\"%s\" is not a sequence" msgstr "\"%s\"はシーケンスではありません" @@ -4473,7 +4478,7 @@ msgstr[0] "" msgid "cannot drop %s because other objects depend on it" msgstr "他のオブジェクトが依存しているため%sを削除できません" -#: catalog/dependency.c:1153 catalog/dependency.c:1160 catalog/dependency.c:1171 commands/tablecmds.c:1459 commands/tablecmds.c:15121 commands/tablespace.c:460 commands/user.c:1302 commands/vacuum.c:211 commands/view.c:441 executor/execExprInterp.c:4655 executor/execExprInterp.c:4663 libpq/auth.c:324 replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1011 storage/lmgr/deadlock.c:1134 storage/lmgr/proc.c:1432 utils/misc/guc.c:3169 +#: catalog/dependency.c:1153 catalog/dependency.c:1160 catalog/dependency.c:1171 commands/tablecmds.c:1459 commands/tablecmds.c:15129 commands/tablespace.c:460 commands/user.c:1302 commands/vacuum.c:211 commands/view.c:441 executor/execExprInterp.c:4655 executor/execExprInterp.c:4663 libpq/auth.c:324 replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1078 storage/lmgr/deadlock.c:1134 storage/lmgr/proc.c:1432 utils/misc/guc.c:3169 #: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6825 utils/misc/guc.c:6859 utils/misc/guc.c:6893 utils/misc/guc.c:6936 utils/misc/guc.c:6978 #, c-format msgid "%s" @@ -4597,7 +4602,7 @@ msgstr "パーティション親テーブル\"%s\"に NO INHERIT 制約は追加 msgid "check constraint \"%s\" already exists" msgstr "検査制約\"%s\"はすでに存在します" -#: catalog/heap.c:2624 catalog/index.c:913 catalog/pg_constraint.c:724 commands/tablecmds.c:9389 +#: catalog/heap.c:2624 catalog/index.c:913 catalog/pg_constraint.c:725 commands/tablecmds.c:9389 #, c-format msgid "constraint \"%s\" for relation \"%s\" already exists" msgstr "すでに制約\"%s\"はリレーション\"%s\"に存在します" @@ -4622,7 +4627,7 @@ msgstr "制約\"%s\"は、リレーション\"%s\"上の NOT VALID 制約と競 msgid "merging constraint \"%s\" with inherited definition" msgstr "継承された定義により制約\"%s\"をマージしています" -#: catalog/heap.c:2683 catalog/pg_constraint.c:853 commands/tablecmds.c:3074 commands/tablecmds.c:3377 commands/tablecmds.c:7089 commands/tablecmds.c:15940 commands/tablecmds.c:16071 +#: catalog/heap.c:2683 catalog/pg_constraint.c:854 commands/tablecmds.c:3074 commands/tablecmds.c:3377 commands/tablecmds.c:7089 commands/tablecmds.c:15948 commands/tablecmds.c:16079 #, c-format msgid "too many inheritance parents" msgstr "継承の親テーブルが多すぎます" @@ -4862,7 +4867,7 @@ msgstr "テキスト検索設定\"%s\"は存在しません" msgid "cross-database references are not implemented: %s" msgstr "データベース間の参照は実装されていません: %s" -#: catalog/namespace.c:3335 gram.y:19181 gram.y:19221 parser/parse_expr.c:875 parser/parse_target.c:1266 +#: catalog/namespace.c:3335 gram.y:19188 gram.y:19228 parser/parse_expr.c:875 parser/parse_target.c:1266 #, c-format msgid "improper qualified name (too many dotted names): %s" msgstr "修飾名が不適切です(ドット区切りの名前が多すぎます): %s" @@ -4912,22 +4917,22 @@ msgstr "リカバリ中は一時テーブルを作成できません" msgid "cannot create temporary tables during a parallel operation" msgstr "並行処理中は一時テーブルを作成できません" -#: catalog/objectaddress.c:1371 commands/policy.c:93 commands/policy.c:373 commands/tablecmds.c:257 commands/tablecmds.c:299 commands/tablecmds.c:2327 commands/tablecmds.c:12958 +#: catalog/objectaddress.c:1371 commands/policy.c:93 commands/policy.c:373 commands/tablecmds.c:257 commands/tablecmds.c:299 commands/tablecmds.c:2327 commands/tablecmds.c:12966 #, c-format msgid "\"%s\" is not a table" msgstr "\"%s\"はテーブルではありません" -#: catalog/objectaddress.c:1378 commands/tablecmds.c:269 commands/tablecmds.c:17884 commands/view.c:114 +#: catalog/objectaddress.c:1378 commands/tablecmds.c:269 commands/tablecmds.c:17892 commands/view.c:114 #, c-format msgid "\"%s\" is not a view" msgstr "\"%s\"はビューではありません" -#: catalog/objectaddress.c:1385 commands/matview.c:199 commands/tablecmds.c:275 commands/tablecmds.c:17889 +#: catalog/objectaddress.c:1385 commands/matview.c:199 commands/tablecmds.c:275 commands/tablecmds.c:17897 #, c-format msgid "\"%s\" is not a materialized view" msgstr "\"%s\"は実体化ビューではありません" -#: catalog/objectaddress.c:1392 commands/tablecmds.c:293 commands/tablecmds.c:17894 +#: catalog/objectaddress.c:1392 commands/tablecmds.c:293 commands/tablecmds.c:17902 #, c-format msgid "\"%s\" is not a foreign table" msgstr "\"%s\"は外部テーブルではありません" @@ -4967,7 +4972,7 @@ msgstr "%4$s の関数 %1$d (%2$s, %3$s) がありません" msgid "user mapping for user \"%s\" on server \"%s\" does not exist" msgstr "ユーザー\"%s\"に対するユーザーマッピングがサーバー\"%s\"には存在しません" -#: catalog/objectaddress.c:1834 commands/foreigncmds.c:430 commands/foreigncmds.c:993 commands/foreigncmds.c:1356 foreign/foreign.c:713 +#: catalog/objectaddress.c:1834 commands/foreigncmds.c:441 commands/foreigncmds.c:1004 commands/foreigncmds.c:1367 foreign/foreign.c:713 #, c-format msgid "server \"%s\" does not exist" msgstr "サーバー\"%s\"は存在しません" @@ -5594,17 +5599,17 @@ msgstr "照合順序\"%s\"はすでに存在します" msgid "collation \"%s\" for encoding \"%s\" already exists" msgstr "エンコーディング\"%2$s\"の照合順序\"%1$s\"はすでに存在します" -#: catalog/pg_constraint.c:732 +#: catalog/pg_constraint.c:733 #, c-format msgid "constraint \"%s\" for domain %s already exists" msgstr "ドメイン\"%2$s\"の制約\"%1$s\"はすでに存在します" -#: catalog/pg_constraint.c:932 catalog/pg_constraint.c:1025 +#: catalog/pg_constraint.c:933 catalog/pg_constraint.c:1026 #, c-format msgid "constraint \"%s\" for table \"%s\" does not exist" msgstr "テーブル\"%2$s\"の制約\"%1$s\"は存在しません" -#: catalog/pg_constraint.c:1125 +#: catalog/pg_constraint.c:1126 #, c-format msgid "constraint \"%s\" for domain %s does not exist" msgstr "ドメイン\"%2$s\"に対する制約\"%1$s\"は存在しません" @@ -5689,7 +5694,7 @@ msgstr "パーティション\"%s\"を取り外せません" msgid "The partition is being detached concurrently or has an unfinished detach." msgstr "このパーティションは今現在取り外し中であるか取り外し処理が未完了の状態です。" -#: catalog/pg_inherits.c:595 commands/tablecmds.c:4800 commands/tablecmds.c:16186 +#: catalog/pg_inherits.c:595 commands/tablecmds.c:4800 commands/tablecmds.c:16194 #, c-format msgid "Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation." msgstr "ALTER TABLE ... DETACH PARTITION ... FINALIZE を実行して保留中の取り外し処理を完了させてください。" @@ -6171,12 +6176,12 @@ msgstr "パラメータ\"%s\"は READ_ONLY、SHAREABLE または READ_WRITE で msgid "event trigger \"%s\" already exists" msgstr "イベントトリガ\"%s\"はすでに存在します" -#: commands/alter.c:86 commands/foreigncmds.c:593 +#: commands/alter.c:86 commands/foreigncmds.c:604 #, c-format msgid "foreign-data wrapper \"%s\" already exists" msgstr "外部データラッパー\"%s\"はすでに存在します" -#: commands/alter.c:89 commands/foreigncmds.c:884 +#: commands/alter.c:89 commands/foreigncmds.c:895 #, c-format msgid "server \"%s\" already exists" msgstr "サーバー\"%s\"はすでに存在します" @@ -6271,7 +6276,7 @@ msgstr "アクセスメソッド\"%s\"は存在しません" msgid "handler function is not specified" msgstr "ハンドラ関数の指定がありません" -#: commands/amcmds.c:264 commands/event_trigger.c:200 commands/foreigncmds.c:489 commands/proclang.c:78 commands/trigger.c:702 parser/parse_clause.c:943 +#: commands/amcmds.c:264 commands/event_trigger.c:200 commands/foreigncmds.c:500 commands/proclang.c:78 commands/trigger.c:702 parser/parse_clause.c:943 #, c-format msgid "function %s must return type %s" msgstr "関数%sは型%sを返さなければなりません" @@ -6376,7 +6381,7 @@ msgstr "他のセッションの一時テーブルをクラスタ化できませ msgid "there is no previously clustered index for table \"%s\"" msgstr "テーブル\"%s\"には事前にクラスタ化されたインデックスはありません" -#: commands/cluster.c:191 commands/tablecmds.c:14830 commands/tablecmds.c:16762 +#: commands/cluster.c:191 commands/tablecmds.c:14838 commands/tablecmds.c:16770 #, c-format msgid "index \"%s\" for table \"%s\" does not exist" msgstr "テーブル\"%2$s\"にはインデックス\"%1$s\"は存在しません" @@ -6391,7 +6396,7 @@ msgstr "共有カタログをクラスタ化できません" msgid "cannot vacuum temporary tables of other sessions" msgstr "他のセッションの一時テーブルに対してはVACUUMを実行できません" -#: commands/cluster.c:513 commands/tablecmds.c:16772 +#: commands/cluster.c:513 commands/tablecmds.c:16780 #, c-format msgid "\"%s\" is not an index for table \"%s\"" msgstr "\"%s\"はテーブル\"%s\"のインデックスではありません" @@ -6455,8 +6460,8 @@ msgstr "\"%s\"のクラスタ化を行う権限がありません、スキップ msgid "collation attribute \"%s\" not recognized" msgstr "照合順序の属性\"%s\"が認識できません" -#: commands/collationcmds.c:123 commands/collationcmds.c:129 commands/define.c:388 commands/tablecmds.c:8162 replication/pgoutput/pgoutput.c:314 replication/pgoutput/pgoutput.c:337 replication/pgoutput/pgoutput.c:351 replication/pgoutput/pgoutput.c:361 replication/pgoutput/pgoutput.c:371 replication/pgoutput/pgoutput.c:381 replication/pgoutput/pgoutput.c:393 replication/walsender.c:1146 replication/walsender.c:1168 replication/walsender.c:1178 -#: replication/walsender.c:1187 replication/walsender.c:1426 +#: commands/collationcmds.c:123 commands/collationcmds.c:129 commands/define.c:388 commands/tablecmds.c:8162 replication/pgoutput/pgoutput.c:314 replication/pgoutput/pgoutput.c:337 replication/pgoutput/pgoutput.c:351 replication/pgoutput/pgoutput.c:361 replication/pgoutput/pgoutput.c:371 replication/pgoutput/pgoutput.c:381 replication/pgoutput/pgoutput.c:393 replication/walsender.c:1150 replication/walsender.c:1172 replication/walsender.c:1182 +#: replication/walsender.c:1191 replication/walsender.c:1430 #, c-format msgid "conflicting or redundant options" msgstr "競合するオプション、あるいは余計なオプションがあります" @@ -6525,7 +6530,7 @@ msgstr "デフォルト照合順序のバーションはリフレッシュでき #. translator: %s is an SQL command #. translator: %s is an SQL ALTER command -#: commands/collationcmds.c:447 commands/subscriptioncmds.c:1376 commands/tablecmds.c:7938 commands/tablecmds.c:7948 commands/tablecmds.c:7950 commands/tablecmds.c:14532 commands/tablecmds.c:17917 commands/tablecmds.c:17938 commands/typecmds.c:3787 commands/typecmds.c:3872 commands/typecmds.c:4226 +#: commands/collationcmds.c:447 commands/subscriptioncmds.c:1376 commands/tablecmds.c:7938 commands/tablecmds.c:7948 commands/tablecmds.c:7950 commands/tablecmds.c:14540 commands/tablecmds.c:17925 commands/tablecmds.c:17946 commands/typecmds.c:3787 commands/typecmds.c:3872 commands/typecmds.c:4226 #, c-format msgid "Use %s instead." msgstr "代わりに%sを使用してください" @@ -6935,17 +6940,17 @@ msgstr "COPYファイルのヘッダが不正です(サイズが不正です)" msgid "could not read from COPY file: %m" msgstr "COPYファイルから読み込めませんでした: %m" -#: commands/copyfromparse.c:278 commands/copyfromparse.c:303 replication/walsender.c:756 replication/walsender.c:782 tcop/postgres.c:381 +#: commands/copyfromparse.c:278 commands/copyfromparse.c:303 replication/walsender.c:760 replication/walsender.c:786 tcop/postgres.c:381 #, c-format msgid "unexpected EOF on client connection with an open transaction" msgstr "トランザクションを実行中のクライアント接続で想定外のEOFがありました" -#: commands/copyfromparse.c:294 replication/walsender.c:772 +#: commands/copyfromparse.c:294 replication/walsender.c:776 #, c-format msgid "unexpected message type 0x%02X during COPY from stdin" msgstr "標準入力からのCOPY中に想定外のメッセージタイプ0x%02Xがありました" -#: commands/copyfromparse.c:317 replication/walsender.c:803 +#: commands/copyfromparse.c:317 replication/walsender.c:807 #, c-format msgid "COPY from stdin failed: %s" msgstr "標準入力からのCOPYが失敗しました: %s" @@ -7599,7 +7604,7 @@ msgstr "\"%s\"は集約関数です" msgid "Use DROP AGGREGATE to drop aggregate functions." msgstr "集約関数を削除するにはDROP AGGREGATEを使用してください" -#: commands/dropcmds.c:153 commands/sequence.c:462 commands/tablecmds.c:3892 commands/tablecmds.c:4050 commands/tablecmds.c:4102 commands/tablecmds.c:17194 tcop/utility.c:1325 +#: commands/dropcmds.c:153 commands/sequence.c:462 commands/tablecmds.c:3892 commands/tablecmds.c:4050 commands/tablecmds.c:4102 commands/tablecmds.c:17202 tcop/utility.c:1325 #, c-format msgid "relation \"%s\" does not exist, skipping" msgstr "リレーション\"%s\"は存在しません、スキップします" @@ -7724,7 +7729,7 @@ msgstr "リレーション\"%2$s\"のルール\"%1$s\"は存在しません、 msgid "foreign-data wrapper \"%s\" does not exist, skipping" msgstr "外部データラッパ\"%s\"は存在しません、スキップします" -#: commands/dropcmds.c:448 commands/foreigncmds.c:1360 +#: commands/dropcmds.c:448 commands/foreigncmds.c:1371 #, c-format msgid "server \"%s\" does not exist, skipping" msgstr "外部データラッパ\"%s\"は存在しません、スキップします" @@ -8130,112 +8135,112 @@ msgstr "データ型%sの複範囲型がありませんでした" msgid "file \"%s\" is too large" msgstr "ファイル\"%s\"は大きすぎます" -#: commands/foreigncmds.c:148 commands/foreigncmds.c:157 +#: commands/foreigncmds.c:159 commands/foreigncmds.c:168 #, c-format msgid "option \"%s\" not found" msgstr "オプション\"%s\"が見つかりません" -#: commands/foreigncmds.c:167 +#: commands/foreigncmds.c:178 #, c-format msgid "option \"%s\" provided more than once" msgstr "オプション\"%s\"が2回以上指定されました" -#: commands/foreigncmds.c:221 commands/foreigncmds.c:229 +#: commands/foreigncmds.c:232 commands/foreigncmds.c:240 #, c-format msgid "permission denied to change owner of foreign-data wrapper \"%s\"" msgstr "外部データラッパー\"%s\"の所有者を変更する権限がありません" -#: commands/foreigncmds.c:223 +#: commands/foreigncmds.c:234 #, c-format msgid "Must be superuser to change owner of a foreign-data wrapper." msgstr "外部データラッパーの所有者を変更するにはスーパーユーザーである必要があります。" -#: commands/foreigncmds.c:231 +#: commands/foreigncmds.c:242 #, c-format msgid "The owner of a foreign-data wrapper must be a superuser." msgstr "外部データラッパーの所有者はスーパーユーザーでなければなりません" -#: commands/foreigncmds.c:291 commands/foreigncmds.c:707 foreign/foreign.c:691 +#: commands/foreigncmds.c:302 commands/foreigncmds.c:718 foreign/foreign.c:691 #, c-format msgid "foreign-data wrapper \"%s\" does not exist" msgstr "外部データラッパー\"%s\"は存在しません" -#: commands/foreigncmds.c:325 +#: commands/foreigncmds.c:336 #, c-format msgid "foreign-data wrapper with OID %u does not exist" msgstr "OID %uの外部データラッパーは存在しません" -#: commands/foreigncmds.c:462 +#: commands/foreigncmds.c:473 #, c-format msgid "foreign server with OID %u does not exist" msgstr "OID %uの外部サーバーは存在しません" -#: commands/foreigncmds.c:580 +#: commands/foreigncmds.c:591 #, c-format msgid "permission denied to create foreign-data wrapper \"%s\"" msgstr "外部データラッパー\"%s\"を作成する権限がありません" -#: commands/foreigncmds.c:582 +#: commands/foreigncmds.c:593 #, c-format msgid "Must be superuser to create a foreign-data wrapper." msgstr "外部データラッパを作成するにはスーパーユーザーである必要があります。" -#: commands/foreigncmds.c:697 +#: commands/foreigncmds.c:708 #, c-format msgid "permission denied to alter foreign-data wrapper \"%s\"" msgstr "外部データラッパー\"%s\"を変更する権限がありません" -#: commands/foreigncmds.c:699 +#: commands/foreigncmds.c:710 #, c-format msgid "Must be superuser to alter a foreign-data wrapper." msgstr "外部データラッパーを更新するにはスーパーユーザーである必要があります。" -#: commands/foreigncmds.c:730 +#: commands/foreigncmds.c:741 #, c-format msgid "changing the foreign-data wrapper handler can change behavior of existing foreign tables" msgstr "外部データラッパーのハンドラーを変更すると、既存の外部テーブルの振る舞いが変わることがあります" -#: commands/foreigncmds.c:745 +#: commands/foreigncmds.c:756 #, c-format msgid "changing the foreign-data wrapper validator can cause the options for dependent objects to become invalid" msgstr "外部データラッパーのバリデータ(検証用関数)を変更すると、それに依存するオプションが不正になる場合があります" -#: commands/foreigncmds.c:876 +#: commands/foreigncmds.c:887 #, c-format msgid "server \"%s\" already exists, skipping" msgstr "サーバー\"%s\"はすでに存在します、スキップします" -#: commands/foreigncmds.c:1144 +#: commands/foreigncmds.c:1155 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\", skipping" msgstr "\"%s\"のユーザーマッピングはサーバー\"%s\"に対してすでに存在します、スキップします" -#: commands/foreigncmds.c:1154 +#: commands/foreigncmds.c:1165 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\"" msgstr "\"%s\"のユーザーマッピングはサーバー\"%s\"に対してすでに存在します" -#: commands/foreigncmds.c:1254 commands/foreigncmds.c:1374 +#: commands/foreigncmds.c:1265 commands/foreigncmds.c:1385 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\"" msgstr "\"%s\"のユーザーマッピングはサーバー\"%s\"に対しては存在しません" -#: commands/foreigncmds.c:1379 +#: commands/foreigncmds.c:1390 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\", skipping" msgstr "\"%s\"のユーザーマッピングはサーバー\"%s\"に対しては存在しません、スキップします" -#: commands/foreigncmds.c:1507 foreign/foreign.c:404 +#: commands/foreigncmds.c:1518 foreign/foreign.c:404 #, c-format msgid "foreign-data wrapper \"%s\" has no handler" msgstr "外部データラッパー\"%s\"にはハンドラがありません" -#: commands/foreigncmds.c:1513 +#: commands/foreigncmds.c:1524 #, c-format msgid "foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA" msgstr "外部データラッパー\"%s\"は IMPORT FOREIGN SCHEMA をサポートしていません" -#: commands/foreigncmds.c:1615 +#: commands/foreigncmds.c:1626 #, c-format msgid "importing foreign table \"%s\"" msgstr "外部テーブル\"%s\"をインポートします" @@ -8756,7 +8761,7 @@ msgstr "包含列は NULLS FIRST/LAST オプションをサポートしません msgid "could not determine which collation to use for index expression" msgstr "インデックス式で使用する照合順序を特定できませんでした" -#: commands/indexcmds.c:2028 commands/tablecmds.c:18218 commands/typecmds.c:811 parser/parse_expr.c:2793 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 utils/adt/misc.c:630 +#: commands/indexcmds.c:2028 commands/tablecmds.c:18226 commands/typecmds.c:811 parser/parse_expr.c:2793 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 utils/adt/misc.c:630 #, c-format msgid "collations are not supported by type %s" msgstr "%s 型では照合順序はサポートされません" @@ -8791,7 +8796,7 @@ msgstr "アクセスメソッド\"%s\"はASC/DESCオプションをサポート msgid "access method \"%s\" does not support NULLS FIRST/LAST options" msgstr "アクセスメソッド\"%s\"はNULLS FIRST/LASTオプションをサポートしません" -#: commands/indexcmds.c:2210 commands/tablecmds.c:18243 commands/tablecmds.c:18249 commands/typecmds.c:2311 +#: commands/indexcmds.c:2210 commands/tablecmds.c:18251 commands/tablecmds.c:18257 commands/typecmds.c:2311 #, c-format msgid "data type %s has no default operator class for access method \"%s\"" msgstr "アクセスメソッド\"%2$s\"にはデータ型%1$s用のデフォルトの演算子クラスがありません" @@ -8861,7 +8866,7 @@ msgstr "パーティションテーブル\"%s.%s\"のインデックス再構築 msgid "while reindexing partitioned index \"%s.%s\"" msgstr "パーティションインデックス\"%s.%s\"のインデックス再構築中" -#: commands/indexcmds.c:3365 commands/indexcmds.c:4241 +#: commands/indexcmds.c:3365 commands/indexcmds.c:4249 #, c-format msgid "table \"%s.%s\" was reindexed" msgstr "テーブル\"%s.%s\"のインデックス再構築が完了しました" @@ -8891,12 +8896,12 @@ msgstr "このタイプのリレーションでインデックス並列再構築 msgid "cannot move non-shared relation to tablespace \"%s\"" msgstr "テーブルスペース\"%s\"への非共有リレーションの移動はできません" -#: commands/indexcmds.c:4222 commands/indexcmds.c:4234 +#: commands/indexcmds.c:4230 commands/indexcmds.c:4242 #, c-format msgid "index \"%s.%s\" was reindexed" msgstr " インデックス\"%s.%s\"の再構築が完了しました " -#: commands/indexcmds.c:4224 commands/indexcmds.c:4243 +#: commands/indexcmds.c:4232 commands/indexcmds.c:4251 #, c-format msgid "%s." msgstr "%s。" @@ -8911,7 +8916,7 @@ msgstr "リレーション\"%s\"はロックできません" msgid "CONCURRENTLY cannot be used when the materialized view is not populated" msgstr "実体化ビューにデータが投入されていない場合はCONCURRENTLYを使用することはできません" -#: commands/matview.c:212 gram.y:18918 +#: commands/matview.c:212 gram.y:18925 #, c-format msgid "%s and %s options cannot be used together" msgstr "%sオプションと%sオプションとを同時に使用することはできません" @@ -9221,7 +9226,7 @@ msgstr "演算子の属性\"%s\"は変更できません" msgid "operator attribute \"%s\" cannot be changed if it has already been set" msgstr "演算子の属性\"%s\"は、すでに設定されている場合には変更できません" -#: commands/policy.c:86 commands/policy.c:379 commands/statscmds.c:146 commands/tablecmds.c:1740 commands/tablecmds.c:2340 commands/tablecmds.c:3702 commands/tablecmds.c:6605 commands/tablecmds.c:9670 commands/tablecmds.c:17805 commands/tablecmds.c:17840 commands/trigger.c:316 commands/trigger.c:1332 commands/trigger.c:1442 rewrite/rewriteDefine.c:268 rewrite/rewriteDefine.c:779 rewrite/rewriteRemove.c:74 +#: commands/policy.c:86 commands/policy.c:379 commands/statscmds.c:146 commands/tablecmds.c:1740 commands/tablecmds.c:2340 commands/tablecmds.c:3702 commands/tablecmds.c:6605 commands/tablecmds.c:9670 commands/tablecmds.c:17813 commands/tablecmds.c:17848 commands/trigger.c:316 commands/trigger.c:1332 commands/trigger.c:1442 rewrite/rewriteDefine.c:268 rewrite/rewriteDefine.c:779 rewrite/rewriteRemove.c:74 #, c-format msgid "permission denied: \"%s\" is a system catalog" msgstr "権限がありません: \"%s\"はシステムカタログです" @@ -9271,7 +9276,7 @@ msgstr "カーソル名が不正です: 空ではいけません" msgid "cannot create a cursor WITH HOLD within security-restricted operation" msgstr "セキュリティー制限操作中は、WITH HOLD指定のカーソルを作成できません" -#: commands/portalcmds.c:189 commands/portalcmds.c:242 executor/execCurrent.c:70 utils/adt/xml.c:2936 utils/adt/xml.c:3106 +#: commands/portalcmds.c:189 commands/portalcmds.c:242 executor/execCurrent.c:70 utils/adt/xml.c:2949 utils/adt/xml.c:3119 #, c-format msgid "cursor \"%s\" does not exist" msgstr "カーソル\"%s\"は存在しません" @@ -9666,7 +9671,7 @@ msgstr "シーケンスは関連するテーブルと同じスキーマでなけ msgid "cannot change ownership of identity sequence" msgstr "識別シーケンスの所有者は変更できません" -#: commands/sequence.c:1671 commands/tablecmds.c:14519 commands/tablecmds.c:17214 +#: commands/sequence.c:1671 commands/tablecmds.c:14527 commands/tablecmds.c:17222 #, c-format msgid "Sequence \"%s\" is linked to table \"%s\"." msgstr "シーケンス\"%s\"はテーブル\"%s\"にリンクされています" @@ -9799,7 +9804,7 @@ msgstr "サブスクリプションを作成する権限がありません" msgid "Only roles with privileges of the \"%s\" role may create subscriptions." msgstr "\"%s\"ロールの権限を持つロールのみがサブスクリプションを作成できます。" -#: commands/subscriptioncmds.c:758 commands/subscriptioncmds.c:891 commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1345 replication/logical/worker.c:4514 +#: commands/subscriptioncmds.c:758 commands/subscriptioncmds.c:891 commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1345 replication/logical/worker.c:4524 #, c-format msgid "could not connect to the publisher: %s" msgstr "パブリッシャに接続できませんでした: %s" @@ -10023,7 +10028,7 @@ msgstr "実体化ビュー\"%s\"は存在しません、スキップします" msgid "Use DROP MATERIALIZED VIEW to remove a materialized view." msgstr "実体化ビューを削除するにはDROP MATERIALIZED VIEWを使用してください。" -#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19838 parser/parse_utilcmd.c:2251 +#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19854 parser/parse_utilcmd.c:2251 #, c-format msgid "index \"%s\" does not exist" msgstr "インデックス\"%s\"は存在しません" @@ -10046,7 +10051,7 @@ msgstr "\"%s\"は型ではありません" msgid "Use DROP TYPE to remove a type." msgstr "型を削除するにはDROP TYPEを使用してください" -#: commands/tablecmds.c:291 commands/tablecmds.c:14358 commands/tablecmds.c:16919 +#: commands/tablecmds.c:291 commands/tablecmds.c:14366 commands/tablecmds.c:16927 #, c-format msgid "foreign table \"%s\" does not exist" msgstr "外部テーブル\"%s\"は存在しません" @@ -10070,7 +10075,7 @@ msgstr "ON COMMITは一時テーブルでのみ使用できます" msgid "cannot create temporary table within security-restricted operation" msgstr "セキュリティー制限操作中は、一時テーブルを作成できません" -#: commands/tablecmds.c:801 commands/tablecmds.c:15778 +#: commands/tablecmds.c:801 commands/tablecmds.c:15786 #, c-format msgid "relation \"%s\" would be inherited from more than once" msgstr "リレーション\"%s\"が複数回継承されました" @@ -10095,7 +10100,7 @@ msgstr "パーティションテーブル\"%s\"では外部子テーブルを作 msgid "Table \"%s\" contains indexes that are unique." msgstr "テーブル\"%s\"はユニークインデックスを持っています" -#: commands/tablecmds.c:1338 commands/tablecmds.c:13374 +#: commands/tablecmds.c:1338 commands/tablecmds.c:13382 #, c-format msgid "too many array dimensions" msgstr "配列の次元多すぎます" @@ -10145,7 +10150,7 @@ msgstr "外部テーブル\"%s\"の切り詰めはできません" msgid "cannot truncate temporary tables of other sessions" msgstr "他のセッションの一時テーブルを削除できません" -#: commands/tablecmds.c:2606 commands/tablecmds.c:15675 +#: commands/tablecmds.c:2606 commands/tablecmds.c:15683 #, c-format msgid "cannot inherit from partitioned table \"%s\"" msgstr "パーティション親テーブル\"%s\"からの継承はできません" @@ -10165,17 +10170,17 @@ msgstr "継承しようとしたリレーション\"%s\"はテーブルまたは msgid "cannot create a temporary relation as partition of permanent relation \"%s\"" msgstr "一時リレーションを永続リレーション\"%s\"のパーティション子テーブルとして作ることはできません" -#: commands/tablecmds.c:2640 commands/tablecmds.c:15654 +#: commands/tablecmds.c:2640 commands/tablecmds.c:15662 #, c-format msgid "cannot inherit from temporary relation \"%s\"" msgstr "一時リレーション\"%s\"から継承することはできません" -#: commands/tablecmds.c:2650 commands/tablecmds.c:15662 +#: commands/tablecmds.c:2650 commands/tablecmds.c:15670 #, c-format msgid "cannot inherit from temporary relation of another session" msgstr "他のセッションの一時リレーションから継承することはできません" -#: commands/tablecmds.c:2791 commands/tablecmds.c:2845 commands/tablecmds.c:13057 parser/parse_utilcmd.c:1265 parser/parse_utilcmd.c:1308 parser/parse_utilcmd.c:1735 parser/parse_utilcmd.c:1843 +#: commands/tablecmds.c:2791 commands/tablecmds.c:2845 commands/tablecmds.c:13065 parser/parse_utilcmd.c:1265 parser/parse_utilcmd.c:1308 parser/parse_utilcmd.c:1735 parser/parse_utilcmd.c:1843 #, c-format msgid "cannot convert whole-row table reference" msgstr "行全体テーブル参照を変換できません" @@ -10458,12 +10463,12 @@ msgstr "型付けされたテーブルに列を追加できません" msgid "cannot add column to a partition" msgstr "パーティションに列は追加できません" -#: commands/tablecmds.c:7072 commands/tablecmds.c:15893 +#: commands/tablecmds.c:7072 commands/tablecmds.c:15901 #, c-format msgid "child table \"%s\" has different type for column \"%s\"" msgstr "子テーブル\"%s\"に異なる型の列\"%s\"があります" -#: commands/tablecmds.c:7078 commands/tablecmds.c:15899 +#: commands/tablecmds.c:7078 commands/tablecmds.c:15907 #, c-format msgid "child table \"%s\" has different collation for column \"%s\"" msgstr "子テーブル\"%s\"に異なる照合順序の列\"%s\"があります" @@ -10493,17 +10498,17 @@ msgstr "リレーション\"%2$s\"の列\"%1$s\"はすでに存在します、 msgid "column \"%s\" of relation \"%s\" already exists" msgstr "リレーション\"%2$s\"の列\"%1$s\"はすでに存在します" -#: commands/tablecmds.c:7546 commands/tablecmds.c:12685 +#: commands/tablecmds.c:7546 commands/tablecmds.c:12693 #, c-format msgid "cannot remove constraint from only the partitioned table when partitions exist" msgstr "子テーブルが存在する場合にはパーティション親テーブルのみから制約を削除することはできません" -#: commands/tablecmds.c:7547 commands/tablecmds.c:7861 commands/tablecmds.c:8039 commands/tablecmds.c:8146 commands/tablecmds.c:8263 commands/tablecmds.c:9082 commands/tablecmds.c:12686 +#: commands/tablecmds.c:7547 commands/tablecmds.c:7861 commands/tablecmds.c:8039 commands/tablecmds.c:8146 commands/tablecmds.c:8263 commands/tablecmds.c:9082 commands/tablecmds.c:12694 #, c-format msgid "Do not specify the ONLY keyword." msgstr "ONLYキーワードを指定しないでください。" -#: commands/tablecmds.c:7583 commands/tablecmds.c:7787 commands/tablecmds.c:7929 commands/tablecmds.c:8061 commands/tablecmds.c:8190 commands/tablecmds.c:8284 commands/tablecmds.c:8385 commands/tablecmds.c:8542 commands/tablecmds.c:8695 commands/tablecmds.c:8776 commands/tablecmds.c:8910 commands/tablecmds.c:12839 commands/tablecmds.c:14381 commands/tablecmds.c:17008 +#: commands/tablecmds.c:7583 commands/tablecmds.c:7787 commands/tablecmds.c:7929 commands/tablecmds.c:8061 commands/tablecmds.c:8190 commands/tablecmds.c:8284 commands/tablecmds.c:8385 commands/tablecmds.c:8542 commands/tablecmds.c:8695 commands/tablecmds.c:8776 commands/tablecmds.c:8910 commands/tablecmds.c:12847 commands/tablecmds.c:14389 commands/tablecmds.c:17016 #, c-format msgid "cannot alter system column \"%s\"" msgstr "システム列\"%s\"を変更できません" @@ -10703,7 +10708,7 @@ msgstr "パーティションテーブル\"%1$s\"にリレーション\"%2$s\" msgid "This feature is not yet supported on partitioned tables." msgstr "この機能はパーティションテーブルに対してはサポートされていません。" -#: commands/tablecmds.c:9664 commands/tablecmds.c:10125 +#: commands/tablecmds.c:9664 commands/tablecmds.c:10146 #, c-format msgid "referenced relation \"%s\" is not a table" msgstr "参照先のリレーション\"%s\"はテーブルではありません" @@ -10728,713 +10733,713 @@ msgstr "一時テーブルに対する制約は一時テーブルだけを参照 msgid "constraints on temporary tables must involve temporary tables of this session" msgstr "一時テーブルに対する制約にはこのセッションの一時テーブルを加える必要があります" -#: commands/tablecmds.c:9768 commands/tablecmds.c:9774 +#: commands/tablecmds.c:9769 commands/tablecmds.c:9775 #, c-format msgid "invalid %s action for foreign key constraint containing generated column" msgstr "生成カラムを含む外部キー制約に対する不正な %s 処理" -#: commands/tablecmds.c:9790 +#: commands/tablecmds.c:9791 #, c-format msgid "number of referencing and referenced columns for foreign key disagree" msgstr "外部キーの参照列数と被参照列数が合いません" -#: commands/tablecmds.c:9897 +#: commands/tablecmds.c:9898 #, c-format msgid "foreign key constraint \"%s\" cannot be implemented" msgstr "外部キー制約\"%sは実装されていません" -#: commands/tablecmds.c:9899 +#: commands/tablecmds.c:9900 #, c-format msgid "Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s." msgstr "キーとなる列\"%s\"と\"%s\"との間で型に互換性がありません:%sと%s" -#: commands/tablecmds.c:10068 +#: commands/tablecmds.c:10075 #, c-format msgid "column \"%s\" referenced in ON DELETE SET action must be part of foreign key" msgstr "ON DELETE SETアクションで参照されている列\"%s\"は外部キーの一部である必要があります" -#: commands/tablecmds.c:10425 commands/tablecmds.c:10865 parser/parse_utilcmd.c:822 parser/parse_utilcmd.c:945 +#: commands/tablecmds.c:10446 commands/tablecmds.c:10873 parser/parse_utilcmd.c:822 parser/parse_utilcmd.c:945 #, c-format msgid "foreign key constraints are not supported on foreign tables" msgstr "外部テーブルでは外部キー制約はサポートされていません" -#: commands/tablecmds.c:10848 +#: commands/tablecmds.c:10856 #, c-format msgid "cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"" msgstr "外部キー\"%2$s\"で参照されているため、テーブル\"%1$s\"を子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:11449 commands/tablecmds.c:11730 commands/tablecmds.c:12642 commands/tablecmds.c:12716 +#: commands/tablecmds.c:11457 commands/tablecmds.c:11738 commands/tablecmds.c:12650 commands/tablecmds.c:12724 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist" msgstr "リレーション\"%2$s\"の制約\"%1$s\"は存在しません" -#: commands/tablecmds.c:11456 +#: commands/tablecmds.c:11464 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key constraint" msgstr "リレーション\"%2$s\"の制約\"%1$s\"は外部キー制約ではありません" -#: commands/tablecmds.c:11494 +#: commands/tablecmds.c:11502 #, c-format msgid "cannot alter constraint \"%s\" on relation \"%s\"" msgstr "リレーション\"%2$s\"の制約\"%1$s\"を変更できません" -#: commands/tablecmds.c:11497 +#: commands/tablecmds.c:11505 #, c-format msgid "Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\"." msgstr "制約\"%1$s\"は、リレーション\"%3$s\"上の制約\"%2$s\"から派生しています。" -#: commands/tablecmds.c:11499 +#: commands/tablecmds.c:11507 #, c-format msgid "You may alter the constraint it derives from instead." msgstr "この制約の代わりに派生元の制約を変更することは可能です。" -#: commands/tablecmds.c:11738 +#: commands/tablecmds.c:11746 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint" msgstr "リレーション\"%2$s\"の制約\"%1$s\"は外部キー制約でも検査制約でもありません" -#: commands/tablecmds.c:11815 +#: commands/tablecmds.c:11823 #, c-format msgid "constraint must be validated on child tables too" msgstr "制約は子テーブルでも検証される必要があります" -#: commands/tablecmds.c:11902 +#: commands/tablecmds.c:11910 #, c-format msgid "column \"%s\" referenced in foreign key constraint does not exist" msgstr "外部キー制約で参照される列\"%s\"が存在しません" -#: commands/tablecmds.c:11908 +#: commands/tablecmds.c:11916 #, c-format msgid "system columns cannot be used in foreign keys" msgstr "システム列は外部キーに使用できません" -#: commands/tablecmds.c:11912 +#: commands/tablecmds.c:11920 #, c-format msgid "cannot have more than %d keys in a foreign key" msgstr "外部キーでは%dを超えるキーを持つことができません" -#: commands/tablecmds.c:11977 +#: commands/tablecmds.c:11985 #, c-format msgid "cannot use a deferrable primary key for referenced table \"%s\"" msgstr "被参照テーブル\"%s\"には遅延可能プライマリキーは使用できません" -#: commands/tablecmds.c:11994 +#: commands/tablecmds.c:12002 #, c-format msgid "there is no primary key for referenced table \"%s\"" msgstr "被参照テーブル\"%s\"にはプライマリキーがありません" -#: commands/tablecmds.c:12062 +#: commands/tablecmds.c:12070 #, c-format msgid "foreign key referenced-columns list must not contain duplicates" msgstr "外部キーの被参照列リストには重複があってはなりません" -#: commands/tablecmds.c:12154 +#: commands/tablecmds.c:12162 #, c-format msgid "cannot use a deferrable unique constraint for referenced table \"%s\"" msgstr "被参照テーブル\"%s\"に対しては、遅延可能な一意性制約は使用できません" -#: commands/tablecmds.c:12159 +#: commands/tablecmds.c:12167 #, c-format msgid "there is no unique constraint matching given keys for referenced table \"%s\"" msgstr "被参照テーブル\"%s\"に、指定したキーに一致する一意性制約がありません" -#: commands/tablecmds.c:12598 +#: commands/tablecmds.c:12606 #, c-format msgid "cannot drop inherited constraint \"%s\" of relation \"%s\"" msgstr "リレーション\"%2$s\"の継承された制約\"%1$s\"を削除できません" -#: commands/tablecmds.c:12648 +#: commands/tablecmds.c:12656 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist, skipping" msgstr "リレーション\"%2$s\"の制約\"%1$s\"は存在しません、スキップします" -#: commands/tablecmds.c:12823 +#: commands/tablecmds.c:12831 #, c-format msgid "cannot alter column type of typed table" msgstr "型付けされたテーブルの列の型を変更できません" -#: commands/tablecmds.c:12849 +#: commands/tablecmds.c:12857 #, c-format msgid "cannot specify USING when altering type of generated column" msgstr "生成列の型変更の際にはUSINGを指定することはできません" -#: commands/tablecmds.c:12850 commands/tablecmds.c:18061 commands/tablecmds.c:18151 commands/trigger.c:656 rewrite/rewriteHandler.c:941 rewrite/rewriteHandler.c:976 +#: commands/tablecmds.c:12858 commands/tablecmds.c:18069 commands/tablecmds.c:18159 commands/trigger.c:656 rewrite/rewriteHandler.c:941 rewrite/rewriteHandler.c:976 #, c-format msgid "Column \"%s\" is a generated column." msgstr "列\"%s\"は生成カラムです。" -#: commands/tablecmds.c:12860 +#: commands/tablecmds.c:12868 #, c-format msgid "cannot alter inherited column \"%s\"" msgstr "継承される列\"%s\"を変更できません" -#: commands/tablecmds.c:12869 +#: commands/tablecmds.c:12877 #, c-format msgid "cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"" msgstr "列\"%s\"はリレーション\"%s\"のパーティションキーの一部であるため、変更できません" -#: commands/tablecmds.c:12919 +#: commands/tablecmds.c:12927 #, c-format msgid "result of USING clause for column \"%s\" cannot be cast automatically to type %s" msgstr "列\"%s\"に対するUSING句の結果は自動的に%s型に型変換できません" -#: commands/tablecmds.c:12922 +#: commands/tablecmds.c:12930 #, c-format msgid "You might need to add an explicit cast." msgstr "必要に応じて明示的な型変換を追加してください。" -#: commands/tablecmds.c:12926 +#: commands/tablecmds.c:12934 #, c-format msgid "column \"%s\" cannot be cast automatically to type %s" msgstr "列\"%s\"は型%sには自動的に型変換できません" #. translator: USING is SQL, don't translate it -#: commands/tablecmds.c:12930 +#: commands/tablecmds.c:12938 #, c-format msgid "You might need to specify \"USING %s::%s\"." msgstr "必要に応じて\"USING %s::%s\"を追加してください。" -#: commands/tablecmds.c:13029 +#: commands/tablecmds.c:13037 #, c-format msgid "cannot alter inherited column \"%s\" of relation \"%s\"" msgstr "リレーション\"%2$s\"の継承列\"%1$s\"は変更できません" -#: commands/tablecmds.c:13058 +#: commands/tablecmds.c:13066 #, c-format msgid "USING expression contains a whole-row table reference." msgstr "USING式が行全体テーブル参照を含んでいます。" -#: commands/tablecmds.c:13069 +#: commands/tablecmds.c:13077 #, c-format msgid "type of inherited column \"%s\" must be changed in child tables too" msgstr "継承される列\"%s\"の型を子テーブルで変更しなければなりません" -#: commands/tablecmds.c:13194 +#: commands/tablecmds.c:13202 #, c-format msgid "cannot alter type of column \"%s\" twice" msgstr "列\"%s\"の型を2回変更することはできません" -#: commands/tablecmds.c:13232 +#: commands/tablecmds.c:13240 #, c-format msgid "generation expression for column \"%s\" cannot be cast automatically to type %s" msgstr "カラム\"%s\"に対する生成式は自動的に%s型にキャストできません" -#: commands/tablecmds.c:13237 +#: commands/tablecmds.c:13245 #, c-format msgid "default for column \"%s\" cannot be cast automatically to type %s" msgstr "列\"%s\"のデフォルト値を自動的に%s型にキャストできません" -#: commands/tablecmds.c:13541 +#: commands/tablecmds.c:13549 #, c-format msgid "cannot alter type of a column used by a function or procedure" msgstr "関数またはプロシージャで使用される列の型は変更できません" -#: commands/tablecmds.c:13542 commands/tablecmds.c:13557 commands/tablecmds.c:13577 commands/tablecmds.c:13596 commands/tablecmds.c:13655 +#: commands/tablecmds.c:13550 commands/tablecmds.c:13565 commands/tablecmds.c:13585 commands/tablecmds.c:13604 commands/tablecmds.c:13663 #, c-format msgid "%s depends on column \"%s\"" msgstr "%sは列\"%s\"に依存しています" -#: commands/tablecmds.c:13556 +#: commands/tablecmds.c:13564 #, c-format msgid "cannot alter type of a column used by a view or rule" msgstr "ビューまたはルールで使用される列の型は変更できません" -#: commands/tablecmds.c:13576 +#: commands/tablecmds.c:13584 #, c-format msgid "cannot alter type of a column used in a trigger definition" msgstr "トリガー定義で使用される列の型は変更できません" -#: commands/tablecmds.c:13595 +#: commands/tablecmds.c:13603 #, c-format msgid "cannot alter type of a column used in a policy definition" msgstr "ポリシ定義で使用されている列の型は変更できません" -#: commands/tablecmds.c:13626 +#: commands/tablecmds.c:13634 #, c-format msgid "cannot alter type of a column used by a generated column" msgstr "生成カラムで使用される列の型は変更できません" -#: commands/tablecmds.c:13627 +#: commands/tablecmds.c:13635 #, c-format msgid "Column \"%s\" is used by generated column \"%s\"." msgstr "カラム\"%s\"は生成カラム\"%s\"で使われています。" -#: commands/tablecmds.c:13654 +#: commands/tablecmds.c:13662 #, c-format msgid "cannot alter type of a column used by a publication WHERE clause" msgstr "パブリケーションのWHERE句で使用される列の型は変更できません" -#: commands/tablecmds.c:14489 commands/tablecmds.c:14501 +#: commands/tablecmds.c:14497 commands/tablecmds.c:14509 #, c-format msgid "cannot change owner of index \"%s\"" msgstr "インデックス\"%s\"の所有者を変更できません" -#: commands/tablecmds.c:14491 commands/tablecmds.c:14503 +#: commands/tablecmds.c:14499 commands/tablecmds.c:14511 #, c-format msgid "Change the ownership of the index's table instead." msgstr "代わりにインデックスのテーブルの所有者を変更してください。" -#: commands/tablecmds.c:14517 +#: commands/tablecmds.c:14525 #, c-format msgid "cannot change owner of sequence \"%s\"" msgstr "シーケンス\"%s\"の所有者を変更できません" -#: commands/tablecmds.c:14542 +#: commands/tablecmds.c:14550 #, c-format msgid "cannot change owner of relation \"%s\"" msgstr "リレーション\"%s\"の所有者を変更できません" -#: commands/tablecmds.c:15009 +#: commands/tablecmds.c:15017 #, c-format msgid "cannot have multiple SET TABLESPACE subcommands" msgstr "SET TABLESPACEサブコマンドを複数指定できません" -#: commands/tablecmds.c:15086 +#: commands/tablecmds.c:15094 #, c-format msgid "cannot set options for relation \"%s\"" msgstr "リレーション\"%s\"のオプションは設定できません" -#: commands/tablecmds.c:15120 commands/view.c:440 +#: commands/tablecmds.c:15128 commands/view.c:440 #, c-format msgid "WITH CHECK OPTION is supported only on automatically updatable views" msgstr "WITH CHECK OPTIONは自動更新可能ビューでのみサポートされます" -#: commands/tablecmds.c:15371 +#: commands/tablecmds.c:15379 #, c-format msgid "only tables, indexes, and materialized views exist in tablespaces" msgstr "テーブルスペースにはテーブル、インデックスおよび実体化ビューしかありません" -#: commands/tablecmds.c:15383 +#: commands/tablecmds.c:15391 #, c-format msgid "cannot move relations in to or out of pg_global tablespace" msgstr "pg_globalテーブルスペースとの間のリレーションの移動はできません" -#: commands/tablecmds.c:15475 +#: commands/tablecmds.c:15483 #, c-format msgid "aborting because lock on relation \"%s.%s\" is not available" msgstr "リレーション\"%s.%s\"のロックが獲得できなかったため中断します" -#: commands/tablecmds.c:15491 +#: commands/tablecmds.c:15499 #, c-format msgid "no matching relations in tablespace \"%s\" found" msgstr "テーブルスペース\"%s\"には合致するリレーションはありませんでした" -#: commands/tablecmds.c:15613 +#: commands/tablecmds.c:15621 #, c-format msgid "cannot change inheritance of typed table" msgstr "型付けされたテーブルの継承を変更できません" -#: commands/tablecmds.c:15618 commands/tablecmds.c:16118 +#: commands/tablecmds.c:15626 commands/tablecmds.c:16126 #, c-format msgid "cannot change inheritance of a partition" msgstr "パーティションの継承は変更できません" -#: commands/tablecmds.c:15623 +#: commands/tablecmds.c:15631 #, c-format msgid "cannot change inheritance of partitioned table" msgstr "パーティションテーブルの継承は変更できません" -#: commands/tablecmds.c:15669 +#: commands/tablecmds.c:15677 #, c-format msgid "cannot inherit to temporary relation of another session" msgstr "他のセッションの一時テーブルを継承できません" -#: commands/tablecmds.c:15682 +#: commands/tablecmds.c:15690 #, c-format msgid "cannot inherit from a partition" msgstr "パーティションからの継承はできません" -#: commands/tablecmds.c:15704 commands/tablecmds.c:18562 +#: commands/tablecmds.c:15712 commands/tablecmds.c:18570 #, c-format msgid "circular inheritance not allowed" msgstr "循環継承を行うことはできません" -#: commands/tablecmds.c:15705 commands/tablecmds.c:18563 +#: commands/tablecmds.c:15713 commands/tablecmds.c:18571 #, c-format msgid "\"%s\" is already a child of \"%s\"." msgstr "\"%s\"はすでに\"%s\"の子です" -#: commands/tablecmds.c:15718 +#: commands/tablecmds.c:15726 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming an inheritance child" msgstr "トリガ\"%s\"によってテーブル\"%s\"が継承子テーブルになることができません" -#: commands/tablecmds.c:15720 +#: commands/tablecmds.c:15728 #, c-format msgid "ROW triggers with transition tables are not supported in inheritance hierarchies." msgstr "遷移テーブルを使用したROWトリガは継承関係ではサポートされていません。" -#: commands/tablecmds.c:15909 +#: commands/tablecmds.c:15917 #, c-format msgid "column \"%s\" in child table must be marked NOT NULL" msgstr "子テーブルの列\"%s\"はNOT NULLである必要があります" -#: commands/tablecmds.c:15918 +#: commands/tablecmds.c:15926 #, c-format msgid "column \"%s\" in child table must be a generated column" msgstr "子テーブルの列\"%s\"は生成列である必要があります" -#: commands/tablecmds.c:15922 +#: commands/tablecmds.c:15930 #, c-format msgid "column \"%s\" in child table must not be a generated column" msgstr "子テーブルの列\"%s\"は生成列であってはなりません" -#: commands/tablecmds.c:15960 +#: commands/tablecmds.c:15968 #, c-format msgid "child table is missing column \"%s\"" msgstr "子テーブルには列\"%s\"がありません" -#: commands/tablecmds.c:16041 +#: commands/tablecmds.c:16049 #, c-format msgid "child table \"%s\" has different definition for check constraint \"%s\"" msgstr "子テーブル\"%s\"では検査制約\"%s\"に異なった定義がされています" -#: commands/tablecmds.c:16048 +#: commands/tablecmds.c:16056 #, c-format msgid "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"" msgstr "制約\"%s\"は子テーブル\"%s\"上の継承されない制約と競合します" -#: commands/tablecmds.c:16058 +#: commands/tablecmds.c:16066 #, c-format msgid "constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"" msgstr "制約\"%s\"は子テーブル\"%s\"のNOT VALID制約と衝突しています" -#: commands/tablecmds.c:16096 +#: commands/tablecmds.c:16104 #, c-format msgid "child table is missing constraint \"%s\"" msgstr "子テーブルには制約\"%s\"がありません" -#: commands/tablecmds.c:16182 +#: commands/tablecmds.c:16190 #, c-format msgid "partition \"%s\" already pending detach in partitioned table \"%s.%s\"" msgstr "パーティション\"%s\"はすでにパーティションテーブル\"%s.%s\"からの取り外し保留中です" -#: commands/tablecmds.c:16211 commands/tablecmds.c:16257 +#: commands/tablecmds.c:16219 commands/tablecmds.c:16265 #, c-format msgid "relation \"%s\" is not a partition of relation \"%s\"" msgstr "リレーション\"%s\"はリレーション\"%s\"のパーティション子テーブルではありません" -#: commands/tablecmds.c:16263 +#: commands/tablecmds.c:16271 #, c-format msgid "relation \"%s\" is not a parent of relation \"%s\"" msgstr "リレーション\"%s\"はリレーション\"%s\"の親ではありません" -#: commands/tablecmds.c:16490 +#: commands/tablecmds.c:16498 #, c-format msgid "typed tables cannot inherit" msgstr "型付けされたテーブルは継承できません" -#: commands/tablecmds.c:16520 +#: commands/tablecmds.c:16528 #, c-format msgid "table is missing column \"%s\"" msgstr "テーブルには列\"%s\"がありません" -#: commands/tablecmds.c:16531 +#: commands/tablecmds.c:16539 #, c-format msgid "table has column \"%s\" where type requires \"%s\"" msgstr "テーブルには列\"%s\"がありますが型は\"%s\"を必要としています" -#: commands/tablecmds.c:16540 +#: commands/tablecmds.c:16548 #, c-format msgid "table \"%s\" has different type for column \"%s\"" msgstr "テーブル\"%s\"では列\"%s\"の型が異なっています" -#: commands/tablecmds.c:16554 +#: commands/tablecmds.c:16562 #, c-format msgid "table has extra column \"%s\"" msgstr "テーブルに余分な列\"%s\"があります" -#: commands/tablecmds.c:16606 +#: commands/tablecmds.c:16614 #, c-format msgid "\"%s\" is not a typed table" msgstr "\"%s\"は型付けされたテーブルではありません" -#: commands/tablecmds.c:16780 +#: commands/tablecmds.c:16788 #, c-format msgid "cannot use non-unique index \"%s\" as replica identity" msgstr "非ユニークインデックス\"%s\"は複製識別としては使用できません" -#: commands/tablecmds.c:16786 +#: commands/tablecmds.c:16794 #, c-format msgid "cannot use non-immediate index \"%s\" as replica identity" msgstr "一意性を即時検査しないインデックス\"%s\"は複製識別には使用できません" -#: commands/tablecmds.c:16792 +#: commands/tablecmds.c:16800 #, c-format msgid "cannot use expression index \"%s\" as replica identity" msgstr "式インデックス\"%s\"は複製識別としては使用できません" -#: commands/tablecmds.c:16798 +#: commands/tablecmds.c:16806 #, c-format msgid "cannot use partial index \"%s\" as replica identity" msgstr "部分インデックス\"%s\"を複製識別としては使用できません" -#: commands/tablecmds.c:16815 +#: commands/tablecmds.c:16823 #, c-format msgid "index \"%s\" cannot be used as replica identity because column %d is a system column" msgstr "列%2$dはシステム列であるためインデックス\"%1$s\"は複製識別には使えません" -#: commands/tablecmds.c:16822 +#: commands/tablecmds.c:16830 #, c-format msgid "index \"%s\" cannot be used as replica identity because column \"%s\" is nullable" msgstr "列\"%2$s\"はnull可であるためインデックス\"%1$s\"は複製識別には使えません" -#: commands/tablecmds.c:17074 +#: commands/tablecmds.c:17082 #, c-format msgid "cannot change logged status of table \"%s\" because it is temporary" msgstr "テーブル\"%s\"は一時テーブルであるため、ログ出力設定を変更できません" -#: commands/tablecmds.c:17098 +#: commands/tablecmds.c:17106 #, c-format msgid "cannot change table \"%s\" to unlogged because it is part of a publication" msgstr "テーブル\"%s\"はパブリケーションの一部であるため、UNLOGGEDに変更できません" -#: commands/tablecmds.c:17100 +#: commands/tablecmds.c:17108 #, c-format msgid "Unlogged relations cannot be replicated." msgstr "UNLOGGEDリレーションはレプリケーションできません。" -#: commands/tablecmds.c:17145 +#: commands/tablecmds.c:17153 #, c-format msgid "could not change table \"%s\" to logged because it references unlogged table \"%s\"" msgstr "テーブル\"%s\"はUNLOGGEDテーブル\"%s\"を参照しているためLOGGEDには設定できません" -#: commands/tablecmds.c:17155 +#: commands/tablecmds.c:17163 #, c-format msgid "could not change table \"%s\" to unlogged because it references logged table \"%s\"" msgstr "テーブル\"%s\"はLOGGEDテーブル\"%s\"を参照しているためUNLOGGEDには設定できません" -#: commands/tablecmds.c:17213 +#: commands/tablecmds.c:17221 #, c-format msgid "cannot move an owned sequence into another schema" msgstr "所有するシーケンスを他のスキーマに移動することができません" -#: commands/tablecmds.c:17321 +#: commands/tablecmds.c:17329 #, c-format msgid "relation \"%s\" already exists in schema \"%s\"" msgstr "リレーション\"%s\"はスキーマ\"%s\"内にすでに存在します" -#: commands/tablecmds.c:17746 +#: commands/tablecmds.c:17754 #, c-format msgid "\"%s\" is not a table or materialized view" msgstr "\"%s\"はテーブルや実体化ビューではありません" -#: commands/tablecmds.c:17899 +#: commands/tablecmds.c:17907 #, c-format msgid "\"%s\" is not a composite type" msgstr "\"%s\"は複合型ではありません" -#: commands/tablecmds.c:17929 +#: commands/tablecmds.c:17937 #, c-format msgid "cannot change schema of index \"%s\"" msgstr "インデックス\"%s\"のスキーマを変更できません" -#: commands/tablecmds.c:17931 commands/tablecmds.c:17945 +#: commands/tablecmds.c:17939 commands/tablecmds.c:17953 #, c-format msgid "Change the schema of the table instead." msgstr "代わりにこのテーブルのスキーマを変更してください。" -#: commands/tablecmds.c:17935 +#: commands/tablecmds.c:17943 #, c-format msgid "cannot change schema of composite type \"%s\"" msgstr "複合型%sのスキーマは変更できません" -#: commands/tablecmds.c:17943 +#: commands/tablecmds.c:17951 #, c-format msgid "cannot change schema of TOAST table \"%s\"" msgstr "TOASTテーブル\"%s\"のスキーマは変更できません" -#: commands/tablecmds.c:17975 +#: commands/tablecmds.c:17983 #, c-format msgid "cannot use \"list\" partition strategy with more than one column" msgstr "\"list\"パーティションストラテジは2つ以上の列に対しては使えません" -#: commands/tablecmds.c:18041 +#: commands/tablecmds.c:18049 #, c-format msgid "column \"%s\" named in partition key does not exist" msgstr "パーティションキーに指定されている列\"%s\"は存在しません" -#: commands/tablecmds.c:18049 +#: commands/tablecmds.c:18057 #, c-format msgid "cannot use system column \"%s\" in partition key" msgstr "パーティションキーでシステム列\"%s\"は使用できません" -#: commands/tablecmds.c:18060 commands/tablecmds.c:18150 +#: commands/tablecmds.c:18068 commands/tablecmds.c:18158 #, c-format msgid "cannot use generated column in partition key" msgstr "パーティションキーで生成カラムは使用できません" -#: commands/tablecmds.c:18133 +#: commands/tablecmds.c:18141 #, c-format msgid "partition key expressions cannot contain system column references" msgstr "パーティションキー式はシステム列への参照を含むことができません" -#: commands/tablecmds.c:18180 +#: commands/tablecmds.c:18188 #, c-format msgid "functions in partition key expression must be marked IMMUTABLE" msgstr "パーティションキー式で使われる関数はIMMUTABLE指定されている必要があります" -#: commands/tablecmds.c:18189 +#: commands/tablecmds.c:18197 #, c-format msgid "cannot use constant expression as partition key" msgstr "定数式をパーティションキーとして使うことはできません" -#: commands/tablecmds.c:18210 +#: commands/tablecmds.c:18218 #, c-format msgid "could not determine which collation to use for partition expression" msgstr "パーティション式で使用する照合順序を特定できませんでした" -#: commands/tablecmds.c:18245 +#: commands/tablecmds.c:18253 #, c-format msgid "You must specify a hash operator class or define a default hash operator class for the data type." msgstr "ハッシュ演算子クラスを指定するか、もしくはこのデータ型にデフォルトのハッシュ演算子クラスを定義する必要があります。" -#: commands/tablecmds.c:18251 +#: commands/tablecmds.c:18259 #, c-format msgid "You must specify a btree operator class or define a default btree operator class for the data type." msgstr "btree演算子クラスを指定するか、もしくはこのデータ型にデフォルトのbtree演算子クラスを定義するかする必要があります。" -#: commands/tablecmds.c:18502 +#: commands/tablecmds.c:18510 #, c-format msgid "\"%s\" is already a partition" msgstr "\"%s\"はすでパーティションです" -#: commands/tablecmds.c:18508 +#: commands/tablecmds.c:18516 #, c-format msgid "cannot attach a typed table as partition" msgstr "型付けされたテーブルをパーティションにアタッチすることはできません" -#: commands/tablecmds.c:18524 +#: commands/tablecmds.c:18532 #, c-format msgid "cannot attach inheritance child as partition" msgstr "継承子テーブルをパーティションにアタッチすることはできません" -#: commands/tablecmds.c:18538 +#: commands/tablecmds.c:18546 #, c-format msgid "cannot attach inheritance parent as partition" msgstr "継承親テーブルをパーティションにアタッチすることはできません" -#: commands/tablecmds.c:18572 +#: commands/tablecmds.c:18580 #, c-format msgid "cannot attach a temporary relation as partition of permanent relation \"%s\"" msgstr "一時リレーションを永続リレーション \"%s\" のパーティション子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:18580 +#: commands/tablecmds.c:18588 #, c-format msgid "cannot attach a permanent relation as partition of temporary relation \"%s\"" msgstr "永続リレーションを一時リレーション\"%s\"のパーティション子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:18588 +#: commands/tablecmds.c:18596 #, c-format msgid "cannot attach as partition of temporary relation of another session" msgstr "他セッションの一時リレーションのパーティション子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:18595 +#: commands/tablecmds.c:18603 #, c-format msgid "cannot attach temporary relation of another session as partition" msgstr "他セッションの一時リレーションにパーティション子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:18615 +#: commands/tablecmds.c:18623 #, c-format msgid "table \"%s\" being attached contains an identity column \"%s\"" msgstr "アタッチ対象のテーブル\"%s\"には識別列\"%s\"が含まれています" -#: commands/tablecmds.c:18617 +#: commands/tablecmds.c:18625 #, c-format msgid "The new partition may not contain an identity column." msgstr "新しいパーティションは識別列を含むことはできません" -#: commands/tablecmds.c:18625 +#: commands/tablecmds.c:18633 #, c-format msgid "table \"%s\" contains column \"%s\" not found in parent \"%s\"" msgstr "テーブル\"%1$s\"は親テーブル\"%3$s\"にない列\"%2$s\"を含んでいます" -#: commands/tablecmds.c:18628 +#: commands/tablecmds.c:18636 #, c-format msgid "The new partition may contain only the columns present in parent." msgstr "新しいパーティションは親に存在する列のみを含むことができます。" -#: commands/tablecmds.c:18640 +#: commands/tablecmds.c:18648 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming a partition" msgstr "トリガ\"%s\"のため、テーブル\"%s\"はパーティション子テーブルにはなれません" -#: commands/tablecmds.c:18642 +#: commands/tablecmds.c:18650 #, c-format msgid "ROW triggers with transition tables are not supported on partitions." msgstr "遷移テーブルを使用するROWトリガはパーティションではサポートされません。" -#: commands/tablecmds.c:18818 +#: commands/tablecmds.c:18826 #, c-format msgid "cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"" msgstr "外部テーブル\"%s\"はパーティションテーブル\"%s\"の子テーブルとしてアタッチすることはできません" -#: commands/tablecmds.c:18821 +#: commands/tablecmds.c:18829 #, c-format msgid "Partitioned table \"%s\" contains unique indexes." msgstr "パーティション親テーブル\"%s\"はユニークインデックスを持っています。" -#: commands/tablecmds.c:19143 +#: commands/tablecmds.c:19151 #, c-format msgid "cannot detach partitions concurrently when a default partition exists" msgstr "デフォルトパーティションを持つパーティションは並列的に取り外しはできません" -#: commands/tablecmds.c:19252 +#: commands/tablecmds.c:19260 #, c-format msgid "partitioned table \"%s\" was removed concurrently" msgstr "パーティション親テーブル\"%s\"には CREATE INDEX CONCURRENTLY は実行できません" -#: commands/tablecmds.c:19258 +#: commands/tablecmds.c:19266 #, c-format msgid "partition \"%s\" was removed concurrently" msgstr "パーティション子テーブル\\\"%s\\\"は同時に削除されました" -#: commands/tablecmds.c:19872 commands/tablecmds.c:19892 commands/tablecmds.c:19913 commands/tablecmds.c:19932 commands/tablecmds.c:19974 +#: commands/tablecmds.c:19888 commands/tablecmds.c:19908 commands/tablecmds.c:19929 commands/tablecmds.c:19948 commands/tablecmds.c:19990 #, c-format msgid "cannot attach index \"%s\" as a partition of index \"%s\"" msgstr "インデックス\"%s\"をインデックス\"%s\"の子インデックスとしてアタッチすることはできません" -#: commands/tablecmds.c:19875 +#: commands/tablecmds.c:19891 #, c-format msgid "Index \"%s\" is already attached to another index." msgstr "インデックス\"%s\"はすでに別のインデックスにアタッチされています。" -#: commands/tablecmds.c:19895 +#: commands/tablecmds.c:19911 #, c-format msgid "Index \"%s\" is not an index on any partition of table \"%s\"." msgstr "インデックス\"%s\"はテーブル\"%s\"のどの子テーブルのインデックスでもありません。" -#: commands/tablecmds.c:19916 +#: commands/tablecmds.c:19932 #, c-format msgid "The index definitions do not match." msgstr "インデックス定義が合致しません。" -#: commands/tablecmds.c:19935 +#: commands/tablecmds.c:19951 #, c-format msgid "The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\"." msgstr "インデックス\"%s\"はテーブル\"%s\"の制約に属していますが、インデックス\"%s\"には制約がありません。" -#: commands/tablecmds.c:19977 +#: commands/tablecmds.c:19993 #, c-format msgid "Another index is already attached for partition \"%s\"." msgstr "子テーブル\"%s\"にはすでに他のインデックスがアタッチされています。" -#: commands/tablecmds.c:20213 +#: commands/tablecmds.c:20229 #, c-format msgid "column data type %s does not support compression" msgstr "列データ型%sは圧縮をサポートしていません" -#: commands/tablecmds.c:20220 +#: commands/tablecmds.c:20236 #, c-format msgid "invalid compression method \"%s\"" msgstr "無効な圧縮方式\"%s\"" -#: commands/tablecmds.c:20246 +#: commands/tablecmds.c:20262 #, c-format msgid "invalid storage type \"%s\"" msgstr "不正な格納タイプ\"%s\"" -#: commands/tablecmds.c:20256 +#: commands/tablecmds.c:20272 #, c-format msgid "column data type %s can only have storage PLAIN" msgstr "列のデータ型%sは格納タイプPLAINしか取ることができません" @@ -11799,17 +11804,17 @@ msgstr "BEFORE FOR EACH ROWトリガの実行では、他のパーティショ msgid "Before executing trigger \"%s\", the row was to be in partition \"%s.%s\"." msgstr "トリガ\"%s\"の実行前には、この行はパーティション\"%s.%s\"に置かれるはずでした。" -#: commands/trigger.c:3341 executor/nodeModifyTable.c:1541 executor/nodeModifyTable.c:1615 executor/nodeModifyTable.c:2377 executor/nodeModifyTable.c:2468 executor/nodeModifyTable.c:3132 executor/nodeModifyTable.c:3302 +#: commands/trigger.c:3341 executor/nodeModifyTable.c:1562 executor/nodeModifyTable.c:1636 executor/nodeModifyTable.c:2398 executor/nodeModifyTable.c:2489 executor/nodeModifyTable.c:3153 executor/nodeModifyTable.c:3323 #, c-format msgid "Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows." msgstr "他の行への変更を伝搬させるためにBEFOREトリガではなくAFTERトリガの使用を検討してください" -#: commands/trigger.c:3382 executor/nodeLockRows.c:228 executor/nodeLockRows.c:237 executor/nodeModifyTable.c:314 executor/nodeModifyTable.c:1557 executor/nodeModifyTable.c:2394 executor/nodeModifyTable.c:2618 +#: commands/trigger.c:3382 executor/nodeLockRows.c:228 executor/nodeLockRows.c:237 executor/nodeModifyTable.c:335 executor/nodeModifyTable.c:1578 executor/nodeModifyTable.c:2415 executor/nodeModifyTable.c:2639 #, c-format msgid "could not serialize access due to concurrent update" msgstr "更新が同時に行われたためアクセスの直列化ができませんでした" -#: commands/trigger.c:3390 executor/nodeModifyTable.c:1647 executor/nodeModifyTable.c:2485 executor/nodeModifyTable.c:2642 executor/nodeModifyTable.c:3150 +#: commands/trigger.c:3390 executor/nodeModifyTable.c:1668 executor/nodeModifyTable.c:2506 executor/nodeModifyTable.c:2663 executor/nodeModifyTable.c:3171 #, c-format msgid "could not serialize access due to concurrent delete" msgstr "削除が同時に行われたためアクセスの直列化ができませんでした" @@ -12289,7 +12294,7 @@ msgstr "%s属性を持つロールのみがロールを作成できます。" msgid "Only roles with the %s attribute may create roles with the %s attribute." msgstr "%s属性を持つロールのみが%s属性を持つロールを作成できます。" -#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17310 gram.y:17356 utils/adt/acl.c:5574 utils/adt/acl.c:5580 +#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17317 gram.y:17363 utils/adt/acl.c:5574 utils/adt/acl.c:5580 #, c-format msgid "role name \"%s\" is reserved" msgstr "ロール名\"%s\"は予約されています" @@ -12648,42 +12653,42 @@ msgstr "ONLY_DATABASE_STATSは他のVACUUMオプションと一緒に指定す msgid "%s cannot be executed from VACUUM or ANALYZE" msgstr "%sはVACUUMやANALYZEからは実行できません" -#: commands/vacuum.c:730 +#: commands/vacuum.c:732 #, c-format msgid "permission denied to vacuum \"%s\", skipping it" msgstr "列%sのVACUUMを行う権限がありません、スキップします" -#: commands/vacuum.c:743 +#: commands/vacuum.c:745 #, c-format msgid "permission denied to analyze \"%s\", skipping it" msgstr "列%sのANALYZEを行う権限がありません、スキップします" -#: commands/vacuum.c:821 commands/vacuum.c:918 +#: commands/vacuum.c:823 commands/vacuum.c:920 #, c-format msgid "skipping vacuum of \"%s\" --- lock not available" msgstr "\"%s\"のVACUUM処理をスキップしています -- ロックを獲得できませんでした" -#: commands/vacuum.c:826 +#: commands/vacuum.c:828 #, c-format msgid "skipping vacuum of \"%s\" --- relation no longer exists" msgstr "\"%s\"のVACUUM処理をスキップしています -- リレーションはすでに存在しません" -#: commands/vacuum.c:842 commands/vacuum.c:923 +#: commands/vacuum.c:844 commands/vacuum.c:925 #, c-format msgid "skipping analyze of \"%s\" --- lock not available" msgstr "\"%s\"のANALYZEをスキップしています --- ロック獲得できませんでした" -#: commands/vacuum.c:847 +#: commands/vacuum.c:849 #, c-format msgid "skipping analyze of \"%s\" --- relation no longer exists" msgstr "\"%s\"のANALYZEをスキップします --- リレーションはすでに存在しません" -#: commands/vacuum.c:1139 +#: commands/vacuum.c:1141 #, c-format msgid "cutoff for removing and freezing tuples is far in the past" msgstr "タプルの削除およびフリーズのカットオフ値が古すぎます" -#: commands/vacuum.c:1140 commands/vacuum.c:1145 +#: commands/vacuum.c:1142 commands/vacuum.c:1147 #, c-format msgid "" "Close open transactions soon to avoid wraparound problems.\n" @@ -12692,37 +12697,37 @@ msgstr "" "周回問題を回避するためにすぐに実行中のトランザクションを終了してください。\n" "古い準備済みトランザクションのコミットまたはロールバック、もしくは古いレプリケーションスロットの削除が必要な場合もあります。" -#: commands/vacuum.c:1144 +#: commands/vacuum.c:1146 #, c-format msgid "cutoff for freezing multixacts is far in the past" msgstr "マルチトランザクションのフリーズのカットオフ値が古すぎます" -#: commands/vacuum.c:1900 +#: commands/vacuum.c:1902 #, c-format msgid "some databases have not been vacuumed in over 2 billion transactions" msgstr "データベースの一部は20億トランザクション以上の間にVACUUMを実行されていませんでした" -#: commands/vacuum.c:1901 +#: commands/vacuum.c:1903 #, c-format msgid "You might have already suffered transaction-wraparound data loss." msgstr "トランザクションの周回によるデータ損失が発生している可能性があります" -#: commands/vacuum.c:2080 +#: commands/vacuum.c:2082 #, c-format msgid "skipping \"%s\" --- cannot vacuum non-tables or special system tables" msgstr "\"%s\"をスキップしています --- テーブルではないものや、特別なシステムテーブルに対してはVACUUMを実行できません" -#: commands/vacuum.c:2512 +#: commands/vacuum.c:2514 #, c-format msgid "scanned index \"%s\" to remove %lld row versions" msgstr "インデックス\"%s\"をスキャンして%lldの行バージョンを削除しました" -#: commands/vacuum.c:2531 +#: commands/vacuum.c:2533 #, c-format msgid "index \"%s\" now contains %.0f row versions in %u pages" msgstr "現在インデックス\"%s\"は%.0f行バージョンを%uページで含んでいます" -#: commands/vacuum.c:2535 +#: commands/vacuum.c:2537 #, c-format msgid "" "%.0f index row versions were removed.\n" @@ -12970,22 +12975,22 @@ msgstr "パラメータの型%d(%s)が実行計画(%s)を準備する時点と msgid "no value found for parameter %d" msgstr "パラメータ%dの値がありません" -#: executor/execExpr.c:642 executor/execExpr.c:649 executor/execExpr.c:655 executor/execExprInterp.c:4852 executor/execExprInterp.c:4869 executor/execExprInterp.c:4968 executor/nodeModifyTable.c:203 executor/nodeModifyTable.c:214 executor/nodeModifyTable.c:231 executor/nodeModifyTable.c:239 +#: executor/execExpr.c:642 executor/execExpr.c:649 executor/execExpr.c:655 executor/execExprInterp.c:4852 executor/execExprInterp.c:4869 executor/execExprInterp.c:4968 executor/nodeModifyTable.c:204 executor/nodeModifyTable.c:223 executor/nodeModifyTable.c:240 executor/nodeModifyTable.c:250 executor/nodeModifyTable.c:260 #, c-format msgid "table row type and query-specified row type do not match" msgstr "テーブルの行型と問い合わせで指定した行型が一致しません" -#: executor/execExpr.c:643 executor/nodeModifyTable.c:204 +#: executor/execExpr.c:643 executor/nodeModifyTable.c:205 #, c-format msgid "Query has too many columns." msgstr "問い合わせの列が多すぎます" -#: executor/execExpr.c:650 executor/nodeModifyTable.c:232 +#: executor/execExpr.c:650 executor/nodeModifyTable.c:224 #, c-format msgid "Query provides a value for a dropped column at ordinal position %d." msgstr "問い合わせで %d 番目に削除される列の値を指定しています。" -#: executor/execExpr.c:656 executor/execExprInterp.c:4870 executor/nodeModifyTable.c:215 +#: executor/execExpr.c:656 executor/execExprInterp.c:4870 executor/nodeModifyTable.c:251 #, c-format msgid "Table has type %s at ordinal position %d, but query expects %s." msgstr "テーブルでは %2$d 番目の型は %1$s ですが、問い合わせでは %3$s を想定しています。" @@ -13314,7 +13319,7 @@ msgstr "同時更新がありました、リトライします" msgid "concurrent delete, retrying" msgstr "並行する削除がありました、リトライします" -#: executor/execReplication.c:352 parser/parse_cte.c:302 parser/parse_oper.c:221 utils/adt/array_userfuncs.c:1334 utils/adt/array_userfuncs.c:1477 utils/adt/arrayfuncs.c:3852 utils/adt/arrayfuncs.c:4407 utils/adt/arrayfuncs.c:6428 utils/adt/rowtypes.c:1220 +#: executor/execReplication.c:352 parser/parse_cte.c:303 parser/parse_oper.c:221 utils/adt/array_userfuncs.c:1334 utils/adt/array_userfuncs.c:1477 utils/adt/arrayfuncs.c:3852 utils/adt/arrayfuncs.c:4407 utils/adt/arrayfuncs.c:6428 utils/adt/rowtypes.c:1220 #, c-format msgid "could not identify an equality operator for type %s" msgstr "型%sの等価演算子を特定できませんでした" @@ -13552,63 +13557,68 @@ msgstr "RIGHT JOINはマージ結合可能な結合条件でのみサポート msgid "FULL JOIN is only supported with merge-joinable join conditions" msgstr "FULL JOINはマージ結合可能な結合条件でのみサポートされています" -#: executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:241 +#, c-format +msgid "Query provides a value for a generated column at ordinal position %d." +msgstr "問い合わせで %d 番目に生成列の値を指定しています。" + +#: executor/nodeModifyTable.c:261 #, c-format msgid "Query has too few columns." msgstr "問い合わせの列が少なすぎます。" -#: executor/nodeModifyTable.c:1540 executor/nodeModifyTable.c:1614 +#: executor/nodeModifyTable.c:1561 executor/nodeModifyTable.c:1635 #, c-format msgid "tuple to be deleted was already modified by an operation triggered by the current command" msgstr "削除対象のタプルはすでに現在のコマンドによって引き起こされた操作によって変更されています" -#: executor/nodeModifyTable.c:1769 +#: executor/nodeModifyTable.c:1790 #, c-format msgid "invalid ON UPDATE specification" msgstr "不正な ON UPDATE 指定です" -#: executor/nodeModifyTable.c:1770 +#: executor/nodeModifyTable.c:1791 #, c-format msgid "The result tuple would appear in a different partition than the original tuple." msgstr "結果タプルをもとのパーティションではなく異なるパーティションに追加しようとしました。" -#: executor/nodeModifyTable.c:2226 +#: executor/nodeModifyTable.c:2247 #, c-format msgid "cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key" msgstr "ソースパーティションのルート以外の上位パーティションが外部キーで直接参照されている場合はパーティション間でタプルを移動させることができません" -#: executor/nodeModifyTable.c:2227 +#: executor/nodeModifyTable.c:2248 #, c-format msgid "A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\"." msgstr "外部キーがパーティションルートテーブル\"%2$s\"ではなくパーティション親テーブル\"%1$s\"を指しています。" -#: executor/nodeModifyTable.c:2230 +#: executor/nodeModifyTable.c:2251 #, c-format msgid "Consider defining the foreign key on table \"%s\"." msgstr "テーブル\"%s\"上に外部キー制約を定義することを検討してください。" #. translator: %s is a SQL command name -#: executor/nodeModifyTable.c:2596 executor/nodeModifyTable.c:3138 executor/nodeModifyTable.c:3308 +#: executor/nodeModifyTable.c:2617 executor/nodeModifyTable.c:3159 executor/nodeModifyTable.c:3329 #, c-format msgid "%s command cannot affect row a second time" msgstr "%sコマンドは単一の行に2度は適用できません" -#: executor/nodeModifyTable.c:2598 +#: executor/nodeModifyTable.c:2619 #, c-format msgid "Ensure that no rows proposed for insertion within the same command have duplicate constrained values." msgstr "同じコマンドでの挿入候補の行が同じ制約値を持つことがないようにしてください" -#: executor/nodeModifyTable.c:3131 executor/nodeModifyTable.c:3301 +#: executor/nodeModifyTable.c:3152 executor/nodeModifyTable.c:3322 #, c-format msgid "tuple to be updated or deleted was already modified by an operation triggered by the current command" msgstr "更新または削除対象のタプルは、現在のコマンドによって発火した操作トリガーによってすでに更新されています" -#: executor/nodeModifyTable.c:3140 executor/nodeModifyTable.c:3310 +#: executor/nodeModifyTable.c:3161 executor/nodeModifyTable.c:3331 #, c-format msgid "Ensure that not more than one source row matches any one target row." msgstr "ソース行が2行以上ターゲット行に合致しないようにしてください。" -#: executor/nodeModifyTable.c:3209 +#: executor/nodeModifyTable.c:3230 #, c-format msgid "tuple to be merged was already moved to another partition due to concurrent update" msgstr "マージ対象のタプルは同時に行われた更新によってすでに他の子テーブルに移動されています" @@ -13984,199 +13994,204 @@ msgstr "列\"%s\"でNULL / NOT NULL宣言が衝突しているか重複してい msgid "unrecognized column option \"%s\"" msgstr "認識できない列オプション \"%s\"" -#: gram.y:14147 +#: gram.y:14098 +#, c-format +msgid "option name \"%s\" cannot be used in XMLTABLE" +msgstr "オプション名 \"%s\" は XMLTABLE の中では使用できません" + +#: gram.y:14154 #, c-format msgid "only string constants are supported in JSON_TABLE path specification" msgstr "JSON_TABLEパス指定では文字列定数のみがサポートされます" -#: gram.y:14469 +#: gram.y:14476 #, c-format msgid "precision for type float must be at least 1 bit" msgstr "浮動小数点数の型の精度は最低でも1ビット必要です" -#: gram.y:14478 +#: gram.y:14485 #, c-format msgid "precision for type float must be less than 54 bits" msgstr "浮動小数点型の精度は54ビットより低くなければなりません" -#: gram.y:14995 +#: gram.y:15002 #, c-format msgid "wrong number of parameters on left side of OVERLAPS expression" msgstr "OVERLAPS式の左辺のパラメータ数が間違っています" -#: gram.y:15000 +#: gram.y:15007 #, c-format msgid "wrong number of parameters on right side of OVERLAPS expression" msgstr "OVERLAPS式の右辺のパラメータ数が間違っています" -#: gram.y:15177 +#: gram.y:15184 #, c-format msgid "UNIQUE predicate is not yet implemented" msgstr "UNIQUE 述部はまだ実装されていません" -#: gram.y:15591 +#: gram.y:15598 #, c-format msgid "cannot use multiple ORDER BY clauses with WITHIN GROUP" msgstr "複数のORDER BY句はWITHIN GROUPと一緒には使用できません" -#: gram.y:15596 +#: gram.y:15603 #, c-format msgid "cannot use DISTINCT with WITHIN GROUP" msgstr "DISTINCT は WITHIN GROUP と同時には使えません" -#: gram.y:15601 +#: gram.y:15608 #, c-format msgid "cannot use VARIADIC with WITHIN GROUP" msgstr "VARIADIC は WITHIN GROUP と同時には使えません" -#: gram.y:16328 gram.y:16352 +#: gram.y:16335 gram.y:16359 #, c-format msgid "frame start cannot be UNBOUNDED FOLLOWING" msgstr "フレームの開始は UNBOUNDED FOLLOWING であってはなりません" -#: gram.y:16333 +#: gram.y:16340 #, c-format msgid "frame starting from following row cannot end with current row" msgstr "次の行から始まるフレームは、現在行では終了できません" -#: gram.y:16357 +#: gram.y:16364 #, c-format msgid "frame end cannot be UNBOUNDED PRECEDING" msgstr "フレームの終了は UNBOUNDED PRECEDING であってはなりません" -#: gram.y:16363 +#: gram.y:16370 #, c-format msgid "frame starting from current row cannot have preceding rows" msgstr "現在行から始まるフレームは、先行する行を含むことができません" -#: gram.y:16370 +#: gram.y:16377 #, c-format msgid "frame starting from following row cannot have preceding rows" msgstr "次の行から始まるフレームは、先行する行を含むことができません" -#: gram.y:16919 +#: gram.y:16926 #, c-format msgid "unrecognized JSON encoding: %s" msgstr "不明なJSON符号化方式: \"%s\"" -#: gram.y:17243 +#: gram.y:17250 #, c-format msgid "type modifier cannot have parameter name" msgstr "型修正子はパラメータ名を持つことはできません" -#: gram.y:17249 +#: gram.y:17256 #, c-format msgid "type modifier cannot have ORDER BY" msgstr "型修正子はORDER BYを持つことはできません" -#: gram.y:17317 gram.y:17324 gram.y:17331 +#: gram.y:17324 gram.y:17331 gram.y:17338 #, c-format msgid "%s cannot be used as a role name here" msgstr "%sはここではロール名として使用できません" -#: gram.y:17421 gram.y:18906 +#: gram.y:17428 gram.y:18913 #, c-format msgid "WITH TIES cannot be specified without ORDER BY clause" msgstr "WITH TIESはORDER BY句なしでは指定できません" -#: gram.y:18597 gram.y:18772 +#: gram.y:18604 gram.y:18779 msgid "improper use of \"*\"" msgstr "\"*\"の使い方が不適切です" -#: gram.y:18735 gram.y:18752 tsearch/spell.c:964 tsearch/spell.c:981 tsearch/spell.c:998 tsearch/spell.c:1015 tsearch/spell.c:1081 +#: gram.y:18742 gram.y:18759 tsearch/spell.c:964 tsearch/spell.c:981 tsearch/spell.c:998 tsearch/spell.c:1015 tsearch/spell.c:1081 #, c-format msgid "syntax error" msgstr "構文エラー" -#: gram.y:18836 +#: gram.y:18843 #, c-format msgid "an ordered-set aggregate with a VARIADIC direct argument must have one VARIADIC aggregated argument of the same data type" msgstr "VARIADIC直接引数を使った順序集合集約は同じデータタイプのVARIADIC集約引数を一つ持つ必要があります" -#: gram.y:18873 +#: gram.y:18880 #, c-format msgid "multiple ORDER BY clauses not allowed" msgstr "複数のORDER BY句は使用できません" -#: gram.y:18884 +#: gram.y:18891 #, c-format msgid "multiple OFFSET clauses not allowed" msgstr "複数のOFFSET句は使用できません" -#: gram.y:18893 +#: gram.y:18900 #, c-format msgid "multiple LIMIT clauses not allowed" msgstr "複数のLIMIT句は使用できません" -#: gram.y:18902 +#: gram.y:18909 #, c-format msgid "multiple limit options not allowed" msgstr "複数のLIMITオプションは使用できません" -#: gram.y:18929 +#: gram.y:18936 #, c-format msgid "multiple WITH clauses not allowed" msgstr "複数の WITH 句は使用できません" -#: gram.y:19122 +#: gram.y:19129 #, c-format msgid "OUT and INOUT arguments aren't allowed in TABLE functions" msgstr "テーブル関数では OUT と INOUT 引数は使用できません" -#: gram.y:19255 +#: gram.y:19262 #, c-format msgid "multiple COLLATE clauses not allowed" msgstr "複数の COLLATE 句は使用できません" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19293 gram.y:19306 +#: gram.y:19300 gram.y:19313 #, c-format msgid "%s constraints cannot be marked DEFERRABLE" msgstr "%s制約は遅延可能にはできません" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19319 +#: gram.y:19326 #, c-format msgid "%s constraints cannot be marked NOT VALID" msgstr "%s制約をNOT VALIDとマークすることはできません" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19332 +#: gram.y:19339 #, c-format msgid "%s constraints cannot be marked NO INHERIT" msgstr "%s制約をNO INHERITをマークすることはできません" -#: gram.y:19354 +#: gram.y:19361 #, c-format msgid "unrecognized partitioning strategy \"%s\"" msgstr "識別できないパーティションストラテジ \"%s\"" -#: gram.y:19378 +#: gram.y:19385 #, c-format msgid "invalid publication object list" msgstr "不正なパブリケーションオブジェクトリスト" -#: gram.y:19379 +#: gram.y:19386 #, c-format msgid "One of TABLE or TABLES IN SCHEMA must be specified before a standalone table or schema name." msgstr "テーブル名やスキーマ名を単独記述の前にTABLEまたはTABLES IN SCHEMAのいずれかを指定する必要があります。" -#: gram.y:19395 +#: gram.y:19402 #, c-format msgid "invalid table name" msgstr "不正なテーブル名" -#: gram.y:19416 +#: gram.y:19423 #, c-format msgid "WHERE clause not allowed for schema" msgstr "WHERE句はスキーマに対しては使用できません" -#: gram.y:19423 +#: gram.y:19430 #, c-format msgid "column specification not allowed for schema" msgstr "列指定はスキーマに対しては使用できません" -#: gram.y:19437 +#: gram.y:19444 #, c-format msgid "invalid schema name" msgstr "不正なスキーマ名" @@ -14645,7 +14660,7 @@ msgstr "クライアントから空のパスワードが返されました" msgid "could not generate random MD5 salt" msgstr "ランダムなMD5ソルトの生成に失敗しました" -#: libpq/auth.c:943 libpq/be-secure-gssapi.c:540 +#: libpq/auth.c:943 libpq/be-secure-gssapi.c:550 #, c-format msgid "could not set environment: %m" msgstr "環境を設定できません: %m" @@ -15091,44 +15106,39 @@ msgstr "秘密鍵ファイル\"%s\"はグループまたは全員からアクセ msgid "File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root." msgstr "ファイルはデータベースユーザーの所有の場合は u=rw (0600) かそれよりも低いパーミッション、root所有の場合は u=rw,g=r (0640) かそれよりも低いパーミッションである必要があります" -#: libpq/be-secure-gssapi.c:201 +#: libpq/be-secure-gssapi.c:208 msgid "GSSAPI wrap error" msgstr "GSSAPI名ラップエラー" -#: libpq/be-secure-gssapi.c:208 +#: libpq/be-secure-gssapi.c:215 #, c-format msgid "outgoing GSSAPI message would not use confidentiality" msgstr "送出されるGSSAPIメッセージに機密性が適用されません" -#: libpq/be-secure-gssapi.c:215 libpq/be-secure-gssapi.c:634 +#: libpq/be-secure-gssapi.c:222 libpq/be-secure-gssapi.c:644 #, c-format msgid "server tried to send oversize GSSAPI packet (%zu > %zu)" msgstr "サーバーは過大なサイズのGSSAPIパケットを送信しようとしました: (%zu > %zu)" -#: libpq/be-secure-gssapi.c:351 +#: libpq/be-secure-gssapi.c:358 libpq/be-secure-gssapi.c:585 #, c-format msgid "oversize GSSAPI packet sent by the client (%zu > %zu)" msgstr "過大なサイズのGSSAPIパケットがクライアントから送出されました: (%zu > %zu)" -#: libpq/be-secure-gssapi.c:389 +#: libpq/be-secure-gssapi.c:396 msgid "GSSAPI unwrap error" msgstr "GSSAPIアンラップエラー" -#: libpq/be-secure-gssapi.c:396 +#: libpq/be-secure-gssapi.c:403 #, c-format msgid "incoming GSSAPI message did not use confidentiality" msgstr "到着したGSSAPIメッセージには機密性が適用されていません" -#: libpq/be-secure-gssapi.c:575 -#, c-format -msgid "oversize GSSAPI packet sent by the client (%zu > %d)" -msgstr "過大なサイズのGSSAPIパケットがクライアントから送出されました: (%zu > %d)" - -#: libpq/be-secure-gssapi.c:600 +#: libpq/be-secure-gssapi.c:610 msgid "could not accept GSSAPI security context" msgstr "GSSAPIセキュリティコンテキストを受け入れられませんでした" -#: libpq/be-secure-gssapi.c:701 +#: libpq/be-secure-gssapi.c:728 msgid "GSSAPI size check error" msgstr "GSSAPIサイズチェックエラー" @@ -16250,37 +16260,37 @@ msgstr "外部結合のNULL可な側では%sを適用できません" msgid "%s is not allowed with UNION/INTERSECT/EXCEPT" msgstr "UNION/INTERSECT/EXCEPTでは%sを使用できません" -#: optimizer/plan/planner.c:2121 optimizer/plan/planner.c:4108 +#: optimizer/plan/planner.c:2121 optimizer/plan/planner.c:4151 #, c-format msgid "could not implement GROUP BY" msgstr "GROUP BY を実行できませんでした" -#: optimizer/plan/planner.c:2122 optimizer/plan/planner.c:4109 optimizer/plan/planner.c:4790 optimizer/prep/prepunion.c:1320 +#: optimizer/plan/planner.c:2122 optimizer/plan/planner.c:4152 optimizer/plan/planner.c:4833 optimizer/prep/prepunion.c:1320 #, c-format msgid "Some of the datatypes only support hashing, while others only support sorting." msgstr "一部のデータ型がハッシュのみをサポートする一方で、別の型はソートのみをサポートしています。" -#: optimizer/plan/planner.c:4789 +#: optimizer/plan/planner.c:4832 #, c-format msgid "could not implement DISTINCT" msgstr "DISTINCTを実行できませんでした" -#: optimizer/plan/planner.c:6134 +#: optimizer/plan/planner.c:6177 #, c-format msgid "could not implement window PARTITION BY" msgstr "ウィンドウの PARTITION BY を実行できませんでした" -#: optimizer/plan/planner.c:6135 +#: optimizer/plan/planner.c:6178 #, c-format msgid "Window partitioning columns must be of sortable datatypes." msgstr "ウィンドウ分割に使用する列は、ソート可能なデータ型でなければなりません。" -#: optimizer/plan/planner.c:6139 +#: optimizer/plan/planner.c:6182 #, c-format msgid "could not implement window ORDER BY" msgstr "ウィンドウの ORDER BY を実行できませんでした" -#: optimizer/plan/planner.c:6140 +#: optimizer/plan/planner.c:6183 #, c-format msgid "Window ordering columns must be of sortable datatypes." msgstr "ウィンドウの順序付けをする列は、ソート可能なデータ型でなければなりません。" @@ -17333,147 +17343,147 @@ msgstr "問い合わせ\"%s\"への再帰的参照が、INTERSECT内に現れて msgid "recursive reference to query \"%s\" must not appear within EXCEPT" msgstr "問い合わせ\"%s\"への再帰的参照が、EXCEPT内で現れてはなりません" -#: parser/parse_cte.c:136 +#: parser/parse_cte.c:137 #, c-format msgid "WITH query name \"%s\" specified more than once" msgstr "WITH 問い合わせ名\"%s\"が複数回指定されました" -#: parser/parse_cte.c:308 +#: parser/parse_cte.c:309 #, c-format msgid "could not identify an inequality operator for type %s" msgstr "型%sの不等演算子を特定できませんでした" -#: parser/parse_cte.c:335 +#: parser/parse_cte.c:336 #, c-format msgid "WITH clause containing a data-modifying statement must be at the top level" msgstr "データを変更するようなステートメントを含む WITH 句はトップレベルでなければなりません" -#: parser/parse_cte.c:384 +#: parser/parse_cte.c:385 #, c-format msgid "recursive query \"%s\" column %d has type %s in non-recursive term but type %s overall" msgstr "再帰問い合わせ\"%s\"の列%dの型は、非再帰項の内では%sになっていますが全体としては%sです" -#: parser/parse_cte.c:390 +#: parser/parse_cte.c:391 #, c-format msgid "Cast the output of the non-recursive term to the correct type." msgstr "非再帰項の出力を正しい型に変換してください。" -#: parser/parse_cte.c:395 +#: parser/parse_cte.c:396 #, c-format msgid "recursive query \"%s\" column %d has collation \"%s\" in non-recursive term but collation \"%s\" overall" msgstr "再帰問い合わせ\"%s\"の列%dの照合順序は、非再帰項では\"%s\"ですが全体としては\"%s\"です" -#: parser/parse_cte.c:399 +#: parser/parse_cte.c:400 #, c-format msgid "Use the COLLATE clause to set the collation of the non-recursive term." msgstr "COLLATE句を使って非再帰項の照合順序を設定してください。" -#: parser/parse_cte.c:420 +#: parser/parse_cte.c:421 #, c-format msgid "WITH query is not recursive" msgstr "WITH問い合わせは再帰的ではありません" -#: parser/parse_cte.c:451 +#: parser/parse_cte.c:452 #, c-format msgid "with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT" msgstr "SEARCHまたはCYCLE句を指定する場合、UNIONの左辺はSELECTでなければなりません" -#: parser/parse_cte.c:456 +#: parser/parse_cte.c:457 #, c-format msgid "with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT" msgstr "SEARCHまたはCYCLE句を指定する場合、UNIONの右辺はSELECTでなければなりません" -#: parser/parse_cte.c:471 +#: parser/parse_cte.c:472 #, c-format msgid "search column \"%s\" not in WITH query column list" msgstr "検索カラム\\\"%s\\\"はWITH問い合わせの列リストの中にありません" -#: parser/parse_cte.c:478 +#: parser/parse_cte.c:479 #, c-format msgid "search column \"%s\" specified more than once" msgstr "検索列\"%s\"が複数回指定されています" -#: parser/parse_cte.c:487 +#: parser/parse_cte.c:488 #, c-format msgid "search sequence column name \"%s\" already used in WITH query column list" msgstr "検索順序列の名前\\\"%s\\\"はすでにWITH問い合わせの列リストで使われています" -#: parser/parse_cte.c:504 +#: parser/parse_cte.c:505 #, c-format msgid "cycle column \"%s\" not in WITH query column list" msgstr "循環列\"%s\"がWITH問い合わせの列リストに存在しません" -#: parser/parse_cte.c:511 +#: parser/parse_cte.c:512 #, c-format msgid "cycle column \"%s\" specified more than once" msgstr "循環列\"%s\"が複数回指定されています" -#: parser/parse_cte.c:520 +#: parser/parse_cte.c:521 #, c-format msgid "cycle mark column name \"%s\" already used in WITH query column list" msgstr "循環識別列の名前\\\"%s\\\"はすでにWITH問い合わせの列リストで使われています" -#: parser/parse_cte.c:527 +#: parser/parse_cte.c:528 #, c-format msgid "cycle path column name \"%s\" already used in WITH query column list" msgstr "循環経路列の名前\\\"%s\\\"はすでにWITH問い合わせの列リストで使われています" -#: parser/parse_cte.c:535 +#: parser/parse_cte.c:536 #, c-format msgid "cycle mark column name and cycle path column name are the same" msgstr "循環識別列と循環経路列の名前が同一です" -#: parser/parse_cte.c:545 +#: parser/parse_cte.c:546 #, c-format msgid "search sequence column name and cycle mark column name are the same" msgstr "検索順序列と循環識別列の名前が同一です" -#: parser/parse_cte.c:552 +#: parser/parse_cte.c:553 #, c-format msgid "search sequence column name and cycle path column name are the same" msgstr "検索順序列と循環経路列の名前が同一です" -#: parser/parse_cte.c:636 +#: parser/parse_cte.c:637 #, c-format msgid "WITH query \"%s\" has %d columns available but %d columns specified" msgstr "WITH問い合わせ\"%s\"には%d列しかありませんが、%d列指定されています" -#: parser/parse_cte.c:816 +#: parser/parse_cte.c:882 #, c-format msgid "mutual recursion between WITH items is not implemented" msgstr "WITH項目間の再帰は実装されていません" -#: parser/parse_cte.c:868 +#: parser/parse_cte.c:934 #, c-format msgid "recursive query \"%s\" must not contain data-modifying statements" msgstr "再帰問い合わせ\"%s\"はデータを更新するス文を含んでいてはなりません" -#: parser/parse_cte.c:876 +#: parser/parse_cte.c:942 #, c-format msgid "recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term" msgstr "再帰問い合わせ\"%s\"が、<非再帰項> UNION [ALL] <再帰項> の形式になっていません" -#: parser/parse_cte.c:911 +#: parser/parse_cte.c:977 #, c-format msgid "ORDER BY in a recursive query is not implemented" msgstr "再帰問い合わせ内の ORDER BY は実装されていません" -#: parser/parse_cte.c:917 +#: parser/parse_cte.c:983 #, c-format msgid "OFFSET in a recursive query is not implemented" msgstr "再帰問い合わせ内の OFFSET は実装されていません" -#: parser/parse_cte.c:923 +#: parser/parse_cte.c:989 #, c-format msgid "LIMIT in a recursive query is not implemented" msgstr "再帰問い合わせ内の LIMIT は実装されていません" -#: parser/parse_cte.c:929 +#: parser/parse_cte.c:995 #, c-format msgid "FOR UPDATE/SHARE in a recursive query is not implemented" msgstr "再帰問い合わせ内の FOR UPDATE/SHARE は実装されていません" -#: parser/parse_cte.c:1008 +#: parser/parse_cte.c:1074 #, c-format msgid "recursive reference to query \"%s\" must not appear more than once" msgstr "問い合わせ\"%s\"への再帰参照が2回以上現れてはなりません" @@ -19191,32 +19201,32 @@ msgstr "失敗したシステムコールはMapViewOfFileExです。" msgid "autovacuum worker took too long to start; canceled" msgstr "自動VACUUMワーカーの起動に時間がかかりすぎています; キャンセルしました" -#: postmaster/autovacuum.c:2203 +#: postmaster/autovacuum.c:2218 #, c-format msgid "autovacuum: dropping orphan temp table \"%s.%s.%s\"" msgstr "自動VACUUM: 孤立した一時テーブル\"%s.%s.%s\"を削除します" -#: postmaster/autovacuum.c:2439 +#: postmaster/autovacuum.c:2461 #, c-format msgid "automatic vacuum of table \"%s.%s.%s\"" msgstr "テーブル\"%s.%s.%s\"に対する自動VACUUM" -#: postmaster/autovacuum.c:2442 +#: postmaster/autovacuum.c:2464 #, c-format msgid "automatic analyze of table \"%s.%s.%s\"" msgstr "テーブル\"%s.%s.%s\"に対する自動ANALYZE" -#: postmaster/autovacuum.c:2636 +#: postmaster/autovacuum.c:2660 #, c-format msgid "processing work entry for relation \"%s.%s.%s\"" msgstr "リレーション\"%s.%s.%s\"の作業エントリを処理しています" -#: postmaster/autovacuum.c:3254 +#: postmaster/autovacuum.c:3291 #, c-format msgid "autovacuum not started because of misconfiguration" msgstr "誤設定のため自動VACUUMが起動できません" -#: postmaster/autovacuum.c:3255 +#: postmaster/autovacuum.c:3292 #, c-format msgid "Enable the \"track_counts\" option." msgstr "\"track_counts\"オプションを有効にしてください。" @@ -20080,7 +20090,7 @@ msgstr "共有メモリキューにデータを送出できませんでした" msgid "logical replication apply worker will serialize the remaining changes of remote transaction %u to a file" msgstr "論理レプリケーションt起用ワーカーはリモートトランザクション%uの残りの変更をシリアライズしてファイルに格納します" -#: replication/logical/decode.c:177 replication/logical/logical.c:141 +#: replication/logical/decode.c:177 replication/logical/logical.c:143 #, c-format msgid "logical decoding on standby requires \"wal_level\" >= \"logical\" on the primary" msgstr "スタンバイ上で論理デコードを行うためにはプライマリ上で\"wal_level\" >= \"logical\"である必要があります" @@ -20115,92 +20125,92 @@ msgstr "論理レプリケーションワーカースロット%dが空いてい msgid "logical replication worker slot %d is already used by another worker, cannot attach" msgstr "論理レプリケーションワーカースロット%dが既に他のワーカーに使用されているため接続できません" -#: replication/logical/logical.c:121 +#: replication/logical/logical.c:123 #, c-format msgid "logical decoding requires \"wal_level\" >= \"logical\"" msgstr "論理デコードを行うためには\"wal_level\" >= \"logical\" である必要があります" -#: replication/logical/logical.c:126 +#: replication/logical/logical.c:128 #, c-format msgid "logical decoding requires a database connection" msgstr "論理デコードを行うにはデータベース接続が必要です" -#: replication/logical/logical.c:365 replication/logical/logical.c:519 +#: replication/logical/logical.c:367 replication/logical/logical.c:521 #, c-format msgid "cannot use physical replication slot for logical decoding" msgstr "物理レプリケーションスロットを論理デコードに使用するとはできません" -#: replication/logical/logical.c:370 replication/logical/logical.c:529 +#: replication/logical/logical.c:372 replication/logical/logical.c:531 #, c-format msgid "replication slot \"%s\" was not created in this database" msgstr "レプリケーションスロット\"%s\"はこのデータベースでは作成されていません" -#: replication/logical/logical.c:377 +#: replication/logical/logical.c:379 #, c-format msgid "cannot create logical replication slot in transaction that has performed writes" msgstr "論理レプリケーションスロットは書き込みを行ったトランザクションの中で生成することはできません" -#: replication/logical/logical.c:540 +#: replication/logical/logical.c:542 #, c-format msgid "cannot use replication slot \"%s\" for logical decoding" msgstr "レプリケーションスロット\"%s\"は論理デコードには使用できません" -#: replication/logical/logical.c:542 replication/slot.c:798 replication/slot.c:829 +#: replication/logical/logical.c:544 replication/slot.c:798 replication/slot.c:829 #, c-format msgid "This replication slot is being synchronized from the primary server." msgstr "このレプリケーションスロットはプライマリサーバーからの同期中です。" -#: replication/logical/logical.c:543 +#: replication/logical/logical.c:545 #, c-format msgid "Specify another replication slot." msgstr "他のレプリケーションスロットを指定してください。" -#: replication/logical/logical.c:554 replication/logical/logical.c:561 +#: replication/logical/logical.c:556 replication/logical/logical.c:563 #, c-format msgid "can no longer get changes from replication slot \"%s\"" msgstr "すでにレプリケーションスロット\"%s\"から変更を取り出すことはできません" -#: replication/logical/logical.c:556 +#: replication/logical/logical.c:558 #, c-format msgid "This slot has been invalidated because it exceeded the maximum reserved size." msgstr "最大留保量を超えたため、このスロットは無効化されています。" -#: replication/logical/logical.c:563 +#: replication/logical/logical.c:565 #, c-format msgid "This slot has been invalidated because it was conflicting with recovery." msgstr "リカバリとの競合のため、このスロットは無効化されました。" -#: replication/logical/logical.c:628 +#: replication/logical/logical.c:630 #, c-format msgid "starting logical decoding for slot \"%s\"" msgstr "スロット\"%s\"の論理デコードを開始します" -#: replication/logical/logical.c:630 +#: replication/logical/logical.c:632 #, c-format msgid "Streaming transactions committing after %X/%X, reading WAL from %X/%X." msgstr "%3$X/%4$XからWALを読み取って、%1$X/%2$X以降にコミットされるトランザクションをストリーミングします。" -#: replication/logical/logical.c:778 +#: replication/logical/logical.c:780 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X" msgstr "スロット\"%s\", 出力プラグイン\"%s\", %sコールバックの処理中, 関連LSN %X/%X" -#: replication/logical/logical.c:784 +#: replication/logical/logical.c:786 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback" msgstr "スロット\"%s\", 出力プラグイン\"%s\", %sコールバックの処理中" -#: replication/logical/logical.c:955 replication/logical/logical.c:1000 replication/logical/logical.c:1045 replication/logical/logical.c:1091 +#: replication/logical/logical.c:957 replication/logical/logical.c:1002 replication/logical/logical.c:1047 replication/logical/logical.c:1093 #, c-format msgid "logical replication at prepare time requires a %s callback" msgstr "プリペア時の論理レプリケーションを行うには%sコールバックが必要です" -#: replication/logical/logical.c:1323 replication/logical/logical.c:1372 replication/logical/logical.c:1413 replication/logical/logical.c:1499 replication/logical/logical.c:1548 +#: replication/logical/logical.c:1325 replication/logical/logical.c:1374 replication/logical/logical.c:1415 replication/logical/logical.c:1501 replication/logical/logical.c:1550 #, c-format msgid "logical streaming requires a %s callback" msgstr "論理ストリーミングを行うには%sコールバックが必要です" -#: replication/logical/logical.c:1458 +#: replication/logical/logical.c:1460 #, c-format msgid "logical streaming at prepare time requires a %s callback" msgstr "プリペア時の論理ストリーミングを行うには%sコールバックが必要です" @@ -20346,55 +20356,50 @@ msgstr "論理レプリケーションのターゲットリレーション\"%s.% msgid "logical replication target relation \"%s.%s\" does not exist" msgstr "論理レプリケーション先のリレーション\"%s.%s\"は存在しません" -#: replication/logical/reorderbuffer.c:3999 +#: replication/logical/reorderbuffer.c:4129 #, c-format msgid "could not write to data file for XID %u: %m" msgstr "XID%uのためのデータファイルの書き出しに失敗しました: %m" -#: replication/logical/reorderbuffer.c:4345 replication/logical/reorderbuffer.c:4370 +#: replication/logical/reorderbuffer.c:4475 replication/logical/reorderbuffer.c:4500 #, c-format msgid "could not read from reorderbuffer spill file: %m" msgstr "並べ替えバッファのあふれファイルの読み込みに失敗しました: %m" -#: replication/logical/reorderbuffer.c:4349 replication/logical/reorderbuffer.c:4374 +#: replication/logical/reorderbuffer.c:4479 replication/logical/reorderbuffer.c:4504 #, c-format msgid "could not read from reorderbuffer spill file: read %d instead of %u bytes" msgstr "並べ替えバッファのあふれファイルの読み込みに失敗しました: %2$uバイトのはずが%1$dバイトでした" -#: replication/logical/reorderbuffer.c:4624 +#: replication/logical/reorderbuffer.c:4754 #, c-format msgid "could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m" msgstr "pg_replslot/%2$s/xid* の削除中にファイル\"%1$s\"が削除できませんでした: %3$m" -#: replication/logical/reorderbuffer.c:5120 +#: replication/logical/reorderbuffer.c:5250 #, c-format msgid "could not read from file \"%s\": read %d instead of %d bytes" msgstr "ファイル\"%1$s\"の読み込みに失敗しました: %3$dバイトのはずが%2$dバイトでした" -#: replication/logical/slotsync.c:215 +#: replication/logical/slotsync.c:215 replication/logical/slotsync.c:579 #, c-format -msgid "could not synchronize replication slot \"%s\" because remote slot precedes local slot" -msgstr "リモートのスロットがローカルのスロットよりも進んでいるためレプリケーションスロット\"%s\"を同期できませんでした" +msgid "could not synchronize replication slot \"%s\"" +msgstr "レプリケーションスロット\"%s\"を同期できませんでした" #: replication/logical/slotsync.c:217 #, c-format -msgid "The remote slot has LSN %X/%X and catalog xmin %u, but the local slot has LSN %X/%X and catalog xmin %u." -msgstr "リモートスロットのLSNは%X/%Xでカタログxminは%uですが, ローカルスロットのLSNは%X/%Xでカタログxminは%uです。" +msgid "Synchronization could lead to data loss as the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u." +msgstr "リモートスロットでは LSN %X/%X のWALとカタログxmin %u が必要ですが、スタンバイ側では LSN %X/%X、カタログxmin %u となっており、同期を行うとデータが失われる可能性があります。" #: replication/logical/slotsync.c:459 #, c-format msgid "dropped replication slot \"%s\" of database with OID %u" msgstr "OID %2$uのデータベースのレプリケーションスロット\"%1$s\"を削除しました" -#: replication/logical/slotsync.c:579 -#, c-format -msgid "could not synchronize replication slot \"%s\"" -msgstr "レプリケーションスロット\"%s\"を同期できませんでした" - #: replication/logical/slotsync.c:580 #, c-format -msgid "Logical decoding could not find consistent point from local slot's LSN %X/%X." -msgstr "論理デコードが一貫性ポイントをローカルスロットのLSN%X/%Xから見つけられませんでした。" +msgid "Synchronization could lead to data loss as standby could not build a consistent snapshot to decode WALs at LSN %X/%X." +msgstr "スタンバイがLSN %X/%XのWALをデコードするために必要な一貫性のあるスナップショットを作成できないため、同期によってデータが失われる可能性があります。" #: replication/logical/slotsync.c:589 #, c-format @@ -20509,57 +20514,57 @@ msgid "exported logical decoding snapshot: \"%s\" with %u transaction ID" msgid_plural "exported logical decoding snapshot: \"%s\" with %u transaction IDs" msgstr[0] "エクスポートされた論理デコードスナップショット: \"%s\" (%u個のトランザクションID を含む)" -#: replication/logical/snapbuild.c:1404 replication/logical/snapbuild.c:1501 replication/logical/snapbuild.c:2017 +#: replication/logical/snapbuild.c:1451 replication/logical/snapbuild.c:1548 replication/logical/snapbuild.c:2064 #, c-format msgid "logical decoding found consistent point at %X/%X" msgstr "論理デコードは一貫性ポイントを%X/%Xで発見しました" -#: replication/logical/snapbuild.c:1406 +#: replication/logical/snapbuild.c:1453 #, c-format msgid "There are no running transactions." msgstr "実行中のトランザクションはありません。" -#: replication/logical/snapbuild.c:1453 +#: replication/logical/snapbuild.c:1500 #, c-format msgid "logical decoding found initial starting point at %X/%X" msgstr "論理デコードは初期開始点を%X/%Xで発見しました" -#: replication/logical/snapbuild.c:1455 replication/logical/snapbuild.c:1479 +#: replication/logical/snapbuild.c:1502 replication/logical/snapbuild.c:1526 #, c-format msgid "Waiting for transactions (approximately %d) older than %u to end." msgstr "%2$uより古いトランザクション(おおよそ%1$d個)の完了を待っています" -#: replication/logical/snapbuild.c:1477 +#: replication/logical/snapbuild.c:1524 #, c-format msgid "logical decoding found initial consistent point at %X/%X" msgstr "論理デコードは初期の一貫性ポイントを%X/%Xで発見しました" -#: replication/logical/snapbuild.c:1503 +#: replication/logical/snapbuild.c:1550 #, c-format msgid "There are no old transactions anymore." msgstr "古いトランザクションはこれ以上はありません" -#: replication/logical/snapbuild.c:1904 +#: replication/logical/snapbuild.c:1951 #, c-format msgid "snapbuild state file \"%s\" has wrong magic number: %u instead of %u" msgstr "スナップショット構築状態ファイル\"%1$s\"のマジックナンバーが不正です: %3$uのはずが%2$uでした" -#: replication/logical/snapbuild.c:1910 +#: replication/logical/snapbuild.c:1957 #, c-format msgid "snapbuild state file \"%s\" has unsupported version: %u instead of %u" msgstr "スナップショット状態ファイル\"%1$s\"のバージョン%2$uはサポート外です: %3$uのはずが%2$uでした" -#: replication/logical/snapbuild.c:1951 +#: replication/logical/snapbuild.c:1998 #, c-format msgid "checksum mismatch for snapbuild state file \"%s\": is %u, should be %u" msgstr "スナップショット生成状態ファイル\"%s\"のチェックサムが一致しません: %uですが、%uであるべきです" -#: replication/logical/snapbuild.c:2019 +#: replication/logical/snapbuild.c:2066 #, c-format msgid "Logical decoding will begin using saved snapshot." msgstr "論理デコードは保存されたスナップショットを使って開始します。" -#: replication/logical/snapbuild.c:2126 +#: replication/logical/snapbuild.c:2173 #, c-format msgid "could not parse file name \"%s\"" msgstr "ファイル名\"%s\"をパースできませんでした" @@ -20609,7 +20614,7 @@ msgstr "テーブルコピー中にパブリッシャ上でのトランザクシ msgid "replication origin \"%s\" already exists" msgstr "レプリケーション基点\"%s\"はすでに存在します" -#: replication/logical/tablesync.c:1505 replication/logical/worker.c:2361 +#: replication/logical/tablesync.c:1505 replication/logical/worker.c:2363 #, c-format msgid "user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"" msgstr "ユーザー\"%s\"は行レベルセキュリティが有効なリレーションへのレプリケーションはできません: \"%s\"" @@ -20619,157 +20624,157 @@ msgstr "ユーザー\"%s\"は行レベルセキュリティが有効なリレー msgid "table copy could not finish transaction on publisher: %s" msgstr "テーブルコピー中にパブリッシャ上でのトランザクション終了に失敗しました: %s" -#: replication/logical/worker.c:481 +#: replication/logical/worker.c:483 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop" msgstr "サブスクリプション\"%s\"に対応する論理レプリケーション並列適用ワーカーが停止します" -#: replication/logical/worker.c:483 +#: replication/logical/worker.c:485 #, c-format msgid "Cannot handle streamed replication transactions using parallel apply workers until all tables have been synchronized." msgstr "すべてのテーブルの同期が完了するまでは、ストリームされたトランザクションを適用ワーカーで扱うことはできません。" -#: replication/logical/worker.c:852 replication/logical/worker.c:967 +#: replication/logical/worker.c:854 replication/logical/worker.c:969 #, c-format msgid "incorrect binary data format in logical replication column %d" msgstr "論理レプリケーション列%dのバイナリデータ書式が不正です" -#: replication/logical/worker.c:2504 +#: replication/logical/worker.c:2506 #, c-format msgid "publisher did not send replica identity column expected by the logical replication target relation \"%s.%s\"" msgstr "論理レプリケーション先のリレーション\"%s.%s\"は複製の識別列を期待していましたが、パブリッシャは送信しませんでした" -#: replication/logical/worker.c:2511 +#: replication/logical/worker.c:2513 #, c-format msgid "logical replication target relation \"%s.%s\" has neither REPLICA IDENTITY index nor PRIMARY KEY and published relation does not have REPLICA IDENTITY FULL" msgstr "論理レプリケーション先のリレーション\"%s.%s\"が識別列インデックスも主キーをもっておらず、かつパブリッシュされたリレーションがREPLICA IDENTITY FULLとなっていません" -#: replication/logical/worker.c:3382 +#: replication/logical/worker.c:3384 #, c-format msgid "invalid logical replication message type \"??? (%d)\"" msgstr "不正な論理レプリケーションのメッセージタイプ \"??? (%d)\"" -#: replication/logical/worker.c:3554 +#: replication/logical/worker.c:3556 #, c-format msgid "data stream from publisher has ended" msgstr "パブリッシャからのデータストリームが終了しました" -#: replication/logical/worker.c:3708 +#: replication/logical/worker.c:3710 #, c-format msgid "terminating logical replication worker due to timeout" msgstr "タイムアウトにより論理レプリケーションワーカーを終了しています" -#: replication/logical/worker.c:3902 +#: replication/logical/worker.c:3904 #, c-format msgid "logical replication worker for subscription \"%s\" will stop because the subscription was removed" msgstr "サブスクリプション\"%s\"が削除されたため、このサブスクリプションに対応する論理レプリケーションワーカーが停止します" -#: replication/logical/worker.c:3916 +#: replication/logical/worker.c:3918 #, c-format msgid "logical replication worker for subscription \"%s\" will stop because the subscription was disabled" msgstr "サブスクリプション\"%s\"が無効化されたため、このサブスクリプションに対応する論理レプリケーションワーカーが停止します" -#: replication/logical/worker.c:3947 +#: replication/logical/worker.c:3949 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop because of a parameter change" msgstr "パラメータの変更があったため、サブスクリプション\"%s\"に対応する論理レプリケーション並列適用ワーカーが停止します" -#: replication/logical/worker.c:3951 +#: replication/logical/worker.c:3953 #, c-format msgid "logical replication worker for subscription \"%s\" will restart because of a parameter change" msgstr "パラメータの変更があったため、サブスクリプション\"%s\"に対応する論理レプリケーションワーカーが再起動します" -#: replication/logical/worker.c:3965 +#: replication/logical/worker.c:3967 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop because the subscription owner's superuser privileges have been revoked" msgstr "サブスクリプション\"%s\"の所有者のスーパーユーザー権限が剥奪されたため、このサブスクリプションに対応する論理レプリケーション並列適用ワーカーが停止します" -#: replication/logical/worker.c:3969 +#: replication/logical/worker.c:3971 #, c-format msgid "logical replication worker for subscription \"%s\" will restart because the subscription owner's superuser privileges have been revoked" msgstr "サブスクリプション\"%s\"の所有者のスーパーユーザー権限が剥奪されたため、このサブスクリプションに対応する論理レプリケーションワーカーが再起動します" -#: replication/logical/worker.c:4489 +#: replication/logical/worker.c:4499 #, c-format msgid "subscription has no replication slot set" msgstr "サブスクリプションにレプリケーションスロットが設定されていません" -#: replication/logical/worker.c:4602 +#: replication/logical/worker.c:4620 #, c-format msgid "logical replication worker for subscription %u will not start because the subscription was removed during startup" msgstr "サブスクリプション%uが起動中に削除されたため、このサブスクリプションに対応する論理レプリケーションワーカーは起動しません" -#: replication/logical/worker.c:4618 +#: replication/logical/worker.c:4636 #, c-format msgid "logical replication worker for subscription \"%s\" will not start because the subscription was disabled during startup" msgstr "サブスクリプション\"%s\"が起動中に無効化されたため、このサブスクリプションに対応する論理レプリケーションワーカーは起動しません" -#: replication/logical/worker.c:4642 +#: replication/logical/worker.c:4660 #, c-format msgid "logical replication table synchronization worker for subscription \"%s\", table \"%s\" has started" msgstr "サブスクリプション\"%s\"、テーブル\"%s\"に対応する論理レプリケーションテーブル同期ワーカーが起動しました" -#: replication/logical/worker.c:4647 +#: replication/logical/worker.c:4665 #, c-format msgid "logical replication apply worker for subscription \"%s\" has started" msgstr "サブスクリプション\"%s\"に対応する論理レプリケーション適用ワーカーが起動しました" -#: replication/logical/worker.c:4769 +#: replication/logical/worker.c:4795 #, c-format msgid "subscription \"%s\" has been disabled because of an error" msgstr "サブスクリプション\"%s\"はエラーのため無効化されました" -#: replication/logical/worker.c:4817 +#: replication/logical/worker.c:4843 #, c-format msgid "logical replication starts skipping transaction at LSN %X/%X" msgstr "論理レプリケーションは%X/%Xででトランザクションのスキップを開始します" -#: replication/logical/worker.c:4831 +#: replication/logical/worker.c:4857 #, c-format msgid "logical replication completed skipping transaction at LSN %X/%X" msgstr "論理レプリケーションは%X/%Xでトランザクションのスキップを完了しました" -#: replication/logical/worker.c:4913 +#: replication/logical/worker.c:4945 #, c-format msgid "skip-LSN of subscription \"%s\" cleared" msgstr "サブスクリプションの\"%s\"スキップLSNをクリアしました" -#: replication/logical/worker.c:4914 +#: replication/logical/worker.c:4946 #, c-format msgid "Remote transaction's finish WAL location (LSN) %X/%X did not match skip-LSN %X/%X." msgstr "リモートトランザクションの完了WAL位置(LSN) %X/%XがスキップLSN %X/%X と一致しません。" -#: replication/logical/worker.c:4951 +#: replication/logical/worker.c:4974 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\"" msgstr "メッセージタイプ \"%2$s\"でレプリケーション基点\"%1$s\"のリモートからのデータを処理中" -#: replication/logical/worker.c:4955 +#: replication/logical/worker.c:4978 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u" msgstr "トランザクション%3$u中、メッセージタイプ\"%2$s\"でレプリケーション基点\"%1$s\"のリモートからのデータを処理中" -#: replication/logical/worker.c:4960 +#: replication/logical/worker.c:4983 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u, finished at %X/%X" msgstr "%4$X/%5$Xで終了したトランザクション%3$u中、メッセージタイプ\"%2$s\"でレプリケーション基点\"%1$s\"のリモートからのデータを処理中" -#: replication/logical/worker.c:4971 +#: replication/logical/worker.c:4994 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u" msgstr "レプリケーション起点\"%1$s\"のリモートデータ処理中、トランザクション%5$uのレプリケーション対象リレーション\"%3$s.%4$s\"に対するメッセージタイプ\"%2$s\"内" -#: replication/logical/worker.c:4978 +#: replication/logical/worker.c:5001 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u, finished at %X/%X" msgstr "%6$X/%7$Xで終了したトランザクション%5$u中、レプリケーション先リレーション\"%3$s.%4$s\"に対するメッセージタイプ\"%2$s\"でレプリケーション基点\"%1$s\"のリモートからのデータを処理中" -#: replication/logical/worker.c:4989 +#: replication/logical/worker.c:5012 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u" msgstr "レプリケーション起点\"%1$s\"のリモートデータ処理中、トランザクション%6$uのレプリケーション対象リレーション\"%3$s.%4$s\"、列\"%5$s\"に対するメッセージタイプ\"%2$s\"内" -#: replication/logical/worker.c:4997 +#: replication/logical/worker.c:5020 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u, finished at %X/%X" msgstr "%7$X/%8$Xで終了したトランザクション%6$u中、レプリケーション先リレーション\"%3$s.%4$s\"、列\"%5$s\"に対するメッセージタイプ\"%2$s\"でレプリケーション基点\"%1$s\"のリモートからのデータを処理中" @@ -21159,38 +21164,38 @@ msgstr "コピー元のレプリケーションスロットがコピー処理中 msgid "replication slots can only be synchronized to a standby server" msgstr "レプリケーションスロットはスタンバイサーバーへのみ同期可能です" -#: replication/syncrep.c:261 +#: replication/syncrep.c:304 #, c-format msgid "canceling the wait for synchronous replication and terminating connection due to administrator command" msgstr "管理者コマンドにより同期レプリケーションの待ち状態をキャンセルし、接続を終了しています" -#: replication/syncrep.c:262 replication/syncrep.c:279 +#: replication/syncrep.c:305 replication/syncrep.c:322 #, c-format msgid "The transaction has already committed locally, but might not have been replicated to the standby." msgstr "トランザクションはローカルではすでにコミット済みですが、スタンバイ側にはレプリケーションされていない可能性があります。" -#: replication/syncrep.c:278 +#: replication/syncrep.c:321 #, c-format msgid "canceling wait for synchronous replication due to user request" msgstr "ユーザーからの要求により同期レプリケーションの待ち状態をキャンセルしています" # y, c-format -#: replication/syncrep.c:485 +#: replication/syncrep.c:528 #, c-format msgid "standby \"%s\" is now a synchronous standby with priority %d" msgstr "スタンバイ\"%s\"は優先度%dの同期スタンバイになりました" -#: replication/syncrep.c:489 +#: replication/syncrep.c:532 #, c-format msgid "standby \"%s\" is now a candidate for quorum synchronous standby" msgstr "スタンバイ\"%s\"は定足数同期スタンバイの候補になりました" -#: replication/syncrep.c:1013 +#: replication/syncrep.c:1080 #, c-format msgid "\"synchronous_standby_names\" parser failed" msgstr "\"synchronous_standby_names\" のパースに失敗しました" -#: replication/syncrep.c:1019 +#: replication/syncrep.c:1086 #, c-format msgid "number of synchronous standbys (%d) must be greater than zero" msgstr "同期スタンバイの数(%d)は1以上である必要があります" @@ -21265,123 +21270,123 @@ msgstr "プライマリサーバーからライムライン%u用のタイムラ msgid "could not write to WAL segment %s at offset %d, length %lu: %m" msgstr "WALファイルセグメント%sのオフセット%d、長さ%luの書き込みが失敗しました: %m" -#: replication/walsender.c:531 +#: replication/walsender.c:535 #, c-format msgid "cannot use %s with a logical replication slot" msgstr "%sは論理レプリケーションスロットでは使用できません" -#: replication/walsender.c:635 storage/smgr/md.c:1735 +#: replication/walsender.c:639 storage/smgr/md.c:1735 #, c-format msgid "could not seek to end of file \"%s\": %m" msgstr "ファイル\"%s\"の終端へシークできませんでした: %m" -#: replication/walsender.c:639 +#: replication/walsender.c:643 #, c-format msgid "could not seek to beginning of file \"%s\": %m" msgstr "ファイル\"%s\"の先頭にシークできませんでした: %m" -#: replication/walsender.c:853 +#: replication/walsender.c:857 #, c-format msgid "cannot use a logical replication slot for physical replication" msgstr "論理レプリケーションスロットは物理レプリケーションには使用できません" -#: replication/walsender.c:919 +#: replication/walsender.c:923 #, c-format msgid "requested starting point %X/%X on timeline %u is not in this server's history" msgstr "タイムライン%3$u上の要求された開始ポイント%1$X/%2$Xはサーバーの履歴にありません" -#: replication/walsender.c:922 +#: replication/walsender.c:926 #, c-format msgid "This server's history forked from timeline %u at %X/%X." msgstr "サーバーの履歴はタイムライン%uの%X/%Xからフォークしました。" -#: replication/walsender.c:966 +#: replication/walsender.c:970 #, c-format msgid "requested starting point %X/%X is ahead of the WAL flush position of this server %X/%X" msgstr "要求された開始ポイント%X/%XはサーバーのWALフラッシュ位置%X/%Xより進んでいます" -#: replication/walsender.c:1160 +#: replication/walsender.c:1164 #, c-format msgid "unrecognized value for CREATE_REPLICATION_SLOT option \"%s\": \"%s\"" msgstr "CREATE_REPLICATION_SLOTのオプション\"%s\"に対する認識できない値: \"%s\"" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1266 +#: replication/walsender.c:1270 #, c-format msgid "%s must not be called inside a transaction" msgstr "%sはトランザクション内では呼び出せません" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1276 +#: replication/walsender.c:1280 #, c-format msgid "%s must be called inside a transaction" msgstr "%sはトランザクション内で呼び出さなければなりません" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1282 +#: replication/walsender.c:1286 #, c-format msgid "%s must be called in REPEATABLE READ isolation mode transaction" msgstr "%s は REPEATABLE READ 分離レベルのトランザクションで呼び出されなければなりません" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1287 +#: replication/walsender.c:1291 #, c-format msgid "%s must be called in a read-only transaction" msgstr "%sは読み取り専用トランザクションの中で呼び出さなければなりません" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1293 +#: replication/walsender.c:1297 #, c-format msgid "%s must be called before any query" msgstr "%s は問い合わせの実行前に呼び出されなければなりません" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1299 +#: replication/walsender.c:1303 #, c-format msgid "%s must not be called in a subtransaction" msgstr "%s はサブトランザクション内では呼び出せません" -#: replication/walsender.c:1472 +#: replication/walsender.c:1476 #, c-format msgid "terminating walsender process after promotion" msgstr "昇格後にWAL送信プロセスを終了します" -#: replication/walsender.c:2000 +#: replication/walsender.c:2015 #, c-format msgid "cannot execute new commands while WAL sender is in stopping mode" msgstr "WAL送信プロセスが停止モードの間は新しいコマンドを実行できません" -#: replication/walsender.c:2035 +#: replication/walsender.c:2050 #, c-format msgid "cannot execute SQL commands in WAL sender for physical replication" msgstr "物理レプリケーション用のWAL送信プロセスでSQLコマンドは実行できません" -#: replication/walsender.c:2068 +#: replication/walsender.c:2083 #, c-format msgid "received replication command: %s" msgstr "レプリケーションコマンドを受信しました: %s" -#: replication/walsender.c:2076 tcop/fastpath.c:209 tcop/postgres.c:1142 tcop/postgres.c:1500 tcop/postgres.c:1752 tcop/postgres.c:2234 tcop/postgres.c:2672 tcop/postgres.c:2749 +#: replication/walsender.c:2091 tcop/fastpath.c:209 tcop/postgres.c:1142 tcop/postgres.c:1500 tcop/postgres.c:1752 tcop/postgres.c:2234 tcop/postgres.c:2672 tcop/postgres.c:2749 #, c-format msgid "current transaction is aborted, commands ignored until end of transaction block" msgstr "現在のトランザクションがアボートしました。トランザクションブロックが終わるまでコマンドは無視されます" -#: replication/walsender.c:2233 replication/walsender.c:2268 +#: replication/walsender.c:2248 replication/walsender.c:2283 #, c-format msgid "unexpected EOF on standby connection" msgstr "スタンバイ接続で想定外のEOFがありました" -#: replication/walsender.c:2256 +#: replication/walsender.c:2271 #, c-format msgid "invalid standby message type \"%c\"" msgstr "スタンバイのメッセージタイプ\"%c\"は不正です" -#: replication/walsender.c:2345 +#: replication/walsender.c:2360 #, c-format msgid "unexpected message type \"%c\"" msgstr "想定しないメッセージタイプ\"%c\"" -#: replication/walsender.c:2759 +#: replication/walsender.c:2778 #, c-format msgid "terminating walsender process due to replication timeout" msgstr "レプリケーションタイムアウトにより WAL 送信プロセスを終了しています" @@ -22359,7 +22364,7 @@ msgstr "共有メモリキュー経由で大きさ%zuのメッセージは送信 msgid "invalid message size %zu in shared memory queue" msgstr "共有メモリキュー内の不正なメッセージ長%zu" -#: storage/ipc/shm_toc.c:118 storage/ipc/shm_toc.c:200 storage/lmgr/lock.c:984 storage/lmgr/lock.c:1022 storage/lmgr/lock.c:2835 storage/lmgr/lock.c:4220 storage/lmgr/lock.c:4285 storage/lmgr/lock.c:4635 storage/lmgr/predicate.c:2468 storage/lmgr/predicate.c:2483 storage/lmgr/predicate.c:3880 storage/lmgr/predicate.c:4927 utils/hash/dynahash.c:1095 +#: storage/ipc/shm_toc.c:118 storage/ipc/shm_toc.c:200 storage/lmgr/lock.c:984 storage/lmgr/lock.c:1022 storage/lmgr/lock.c:2835 storage/lmgr/lock.c:4220 storage/lmgr/lock.c:4285 storage/lmgr/lock.c:4635 storage/lmgr/predicate.c:2468 storage/lmgr/predicate.c:2483 storage/lmgr/predicate.c:3880 storage/lmgr/predicate.c:4927 utils/hash/dynahash.c:1096 #, c-format msgid "out of shared memory" msgstr "共有メモリが足りません" @@ -22532,107 +22537,107 @@ msgstr "デッドロックを検出しました" msgid "See server log for query details." msgstr "問い合わせの詳細はサーバーログを参照してください" -#: storage/lmgr/lmgr.c:848 +#: storage/lmgr/lmgr.c:854 #, c-format msgid "while updating tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプル(%1$u,%2$u)の更新中" -#: storage/lmgr/lmgr.c:851 +#: storage/lmgr/lmgr.c:857 #, c-format msgid "while deleting tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプル(%1$u,%2$u)の削除中" -#: storage/lmgr/lmgr.c:854 +#: storage/lmgr/lmgr.c:860 #, c-format msgid "while locking tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプル(%1$u,%2$u)のロック中" -#: storage/lmgr/lmgr.c:857 +#: storage/lmgr/lmgr.c:863 #, c-format msgid "while locking updated version (%u,%u) of tuple in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプルの更新後バージョン(%1$u,%2$u)のロック中" -#: storage/lmgr/lmgr.c:860 +#: storage/lmgr/lmgr.c:866 #, c-format msgid "while inserting index tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のインデックスタプル(%1$u,%2$u)の挿入中" -#: storage/lmgr/lmgr.c:863 +#: storage/lmgr/lmgr.c:869 #, c-format msgid "while checking uniqueness of tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプル(%1$u,%2$u)の一意性の確認中" -#: storage/lmgr/lmgr.c:866 +#: storage/lmgr/lmgr.c:872 #, c-format msgid "while rechecking updated tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"の更新されたタプル(%1$u,%2$u)の再チェック中" -#: storage/lmgr/lmgr.c:869 +#: storage/lmgr/lmgr.c:875 #, c-format msgid "while checking exclusion constraint on tuple (%u,%u) in relation \"%s\"" msgstr "リレーション\"%3$s\"のタプル(%1$u,%2$u)に対する排除制約のチェック中" -#: storage/lmgr/lmgr.c:1239 +#: storage/lmgr/lmgr.c:1245 #, c-format msgid "relation %u of database %u" msgstr "データベース%2$uのリレーション%1$u" -#: storage/lmgr/lmgr.c:1245 +#: storage/lmgr/lmgr.c:1251 #, c-format msgid "extension of relation %u of database %u" msgstr "データベース%2$uのリレーション%1$uの拡張" -#: storage/lmgr/lmgr.c:1251 +#: storage/lmgr/lmgr.c:1257 #, c-format msgid "pg_database.datfrozenxid of database %u" msgstr "データベース%uのpg_database.datfrozenxid" -#: storage/lmgr/lmgr.c:1256 +#: storage/lmgr/lmgr.c:1262 #, c-format msgid "page %u of relation %u of database %u" msgstr "データベース%3$uのリレーション%2$uのページ%1$u" -#: storage/lmgr/lmgr.c:1263 +#: storage/lmgr/lmgr.c:1269 #, c-format msgid "tuple (%u,%u) of relation %u of database %u" msgstr "データベース%4$uのリレーション%3$uのタプル(%2$u,%1$u)" -#: storage/lmgr/lmgr.c:1271 +#: storage/lmgr/lmgr.c:1277 #, c-format msgid "transaction %u" msgstr "トランザクション %u" -#: storage/lmgr/lmgr.c:1276 +#: storage/lmgr/lmgr.c:1282 #, c-format msgid "virtual transaction %d/%u" msgstr "仮想トランザクション %d/%u" -#: storage/lmgr/lmgr.c:1282 +#: storage/lmgr/lmgr.c:1288 #, c-format msgid "speculative token %u of transaction %u" msgstr "トランザクション%2$uの投機的書き込みトークン%1$u" -#: storage/lmgr/lmgr.c:1288 +#: storage/lmgr/lmgr.c:1294 #, c-format msgid "object %u of class %u of database %u" msgstr "データベース%3$uのリレーション%2$uのオブジェクト%1$u" -#: storage/lmgr/lmgr.c:1296 +#: storage/lmgr/lmgr.c:1302 #, c-format msgid "user lock [%u,%u,%u]" msgstr "ユーザーロック[%u,%u,%u]" -#: storage/lmgr/lmgr.c:1303 +#: storage/lmgr/lmgr.c:1309 #, c-format msgid "advisory lock [%u,%u,%u,%u]" msgstr "アドバイザリ・ロック[%u,%u,%u,%u]" -#: storage/lmgr/lmgr.c:1311 +#: storage/lmgr/lmgr.c:1317 #, c-format msgid "remote transaction %u of subscription %u of database %u" msgstr "データベース%3$uのサブスクリプション%2$uのリモートトランザクション%1$u" -#: storage/lmgr/lmgr.c:1318 +#: storage/lmgr/lmgr.c:1324 #, c-format msgid "unrecognized locktag type %d" msgstr "ロックタグタイプ%dは不明です" @@ -23614,37 +23619,37 @@ msgstr "%sは0以上でなければなりません" msgid "could not unlink permanent statistics file \"%s\": %m" msgstr "永続統計情報ファイル\"%s\"をunlinkできませんでした: %m" -#: utils/activity/pgstat.c:1255 +#: utils/activity/pgstat.c:1254 #, c-format msgid "invalid statistics kind: \"%s\"" msgstr "不正な統計情報種別: \"%s\"" -#: utils/activity/pgstat.c:1335 +#: utils/activity/pgstat.c:1334 #, c-format msgid "could not open temporary statistics file \"%s\": %m" msgstr "一時統計情報ファイル\"%s\"をオープンできませんでした: %m" -#: utils/activity/pgstat.c:1455 +#: utils/activity/pgstat.c:1454 #, c-format msgid "could not write temporary statistics file \"%s\": %m" msgstr "一時統計情報ファイル\"%s\"に書き込みできませんでした: %m" -#: utils/activity/pgstat.c:1464 +#: utils/activity/pgstat.c:1463 #, c-format msgid "could not close temporary statistics file \"%s\": %m" msgstr "一時統計情報ファイル\"%s\"をクローズできませんでした: %m" -#: utils/activity/pgstat.c:1472 +#: utils/activity/pgstat.c:1471 #, c-format msgid "could not rename temporary statistics file \"%s\" to \"%s\": %m" msgstr "一時統計情報ファイル\"%s\"の名前を\"%s\"に変更できませんでした: %m" -#: utils/activity/pgstat.c:1521 +#: utils/activity/pgstat.c:1520 #, c-format msgid "could not open statistics file \"%s\": %m" msgstr "統計情報ファイル\"%s\"をオープンできませんでした: %m" -#: utils/activity/pgstat.c:1683 +#: utils/activity/pgstat.c:1682 #, c-format msgid "corrupted statistics file \"%s\"" msgstr "統計情報ファイル\"%s\"が破損しています" @@ -24154,7 +24159,7 @@ msgstr "TIME(%d)%sの位取りを許容最大値%dまで減らしました" msgid "date out of range: \"%s\"" msgstr "日付が範囲外です: \"%s\"" -#: utils/adt/date.c:222 utils/adt/date.c:520 utils/adt/date.c:544 utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2552 +#: utils/adt/date.c:222 utils/adt/date.c:520 utils/adt/date.c:544 utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2565 #, c-format msgid "date out of range" msgstr "日付が範囲外です" @@ -24192,7 +24197,7 @@ msgstr "単位\"%s\"は型%sに対しては認識できません" #: utils/adt/date.c:1314 utils/adt/date.c:1360 utils/adt/date.c:1919 utils/adt/date.c:1950 utils/adt/date.c:1979 utils/adt/date.c:2869 utils/adt/date.c:3101 utils/adt/datetime.c:422 utils/adt/datetime.c:1807 utils/adt/formatting.c:4269 utils/adt/formatting.c:4305 utils/adt/formatting.c:4392 utils/adt/formatting.c:4514 utils/adt/json.c:366 utils/adt/json.c:405 utils/adt/timestamp.c:248 utils/adt/timestamp.c:280 utils/adt/timestamp.c:716 utils/adt/timestamp.c:725 #: utils/adt/timestamp.c:803 utils/adt/timestamp.c:836 utils/adt/timestamp.c:3066 utils/adt/timestamp.c:3075 utils/adt/timestamp.c:3092 utils/adt/timestamp.c:3097 utils/adt/timestamp.c:3116 utils/adt/timestamp.c:3129 utils/adt/timestamp.c:3140 utils/adt/timestamp.c:3146 utils/adt/timestamp.c:3152 utils/adt/timestamp.c:3157 utils/adt/timestamp.c:3210 utils/adt/timestamp.c:3219 utils/adt/timestamp.c:3240 utils/adt/timestamp.c:3245 utils/adt/timestamp.c:3266 #: utils/adt/timestamp.c:3279 utils/adt/timestamp.c:3293 utils/adt/timestamp.c:3301 utils/adt/timestamp.c:3307 utils/adt/timestamp.c:3312 utils/adt/timestamp.c:4380 utils/adt/timestamp.c:4532 utils/adt/timestamp.c:4608 utils/adt/timestamp.c:4644 utils/adt/timestamp.c:4734 utils/adt/timestamp.c:4813 utils/adt/timestamp.c:4849 utils/adt/timestamp.c:4952 utils/adt/timestamp.c:5407 utils/adt/timestamp.c:5681 utils/adt/timestamp.c:6199 utils/adt/timestamp.c:6209 -#: utils/adt/timestamp.c:6214 utils/adt/timestamp.c:6220 utils/adt/timestamp.c:6260 utils/adt/timestamp.c:6347 utils/adt/timestamp.c:6388 utils/adt/timestamp.c:6392 utils/adt/timestamp.c:6446 utils/adt/timestamp.c:6450 utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2574 utils/adt/xml.c:2581 utils/adt/xml.c:2601 utils/adt/xml.c:2608 +#: utils/adt/timestamp.c:6214 utils/adt/timestamp.c:6220 utils/adt/timestamp.c:6260 utils/adt/timestamp.c:6347 utils/adt/timestamp.c:6388 utils/adt/timestamp.c:6392 utils/adt/timestamp.c:6446 utils/adt/timestamp.c:6450 utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2587 utils/adt/xml.c:2594 utils/adt/xml.c:2614 utils/adt/xml.c:2621 #, c-format msgid "timestamp out of range" msgstr "timestampの範囲外です" @@ -25401,12 +25406,12 @@ msgstr "非決定的照合順序はILIKEではサポートされません" msgid "LIKE pattern must not end with escape character" msgstr "LIKE パターンはエスケープ文字で終わってはなりません" -#: utils/adt/like_match.c:293 utils/adt/regexp.c:800 +#: utils/adt/like_match.c:293 utils/adt/regexp.c:803 #, c-format msgid "invalid escape string" msgstr "不正なエスケープ文字列" -#: utils/adt/like_match.c:294 utils/adt/regexp.c:801 +#: utils/adt/like_match.c:294 utils/adt/regexp.c:804 #, c-format msgid "Escape string must be empty or one character." msgstr "エスケープ文字は空か1文字でなければなりません。" @@ -26028,7 +26033,7 @@ msgstr "カンマが多すぎます" msgid "Junk after right parenthesis or bracket." msgstr "右括弧または右角括弧の後にごみがあります" -#: utils/adt/regexp.c:304 utils/adt/regexp.c:1996 utils/adt/varlena.c:4273 +#: utils/adt/regexp.c:304 utils/adt/regexp.c:2022 utils/adt/varlena.c:4273 #, c-format msgid "regular expression failed: %s" msgstr "正規表現が失敗しました: %s" @@ -26043,28 +26048,28 @@ msgstr "不正な正規表現オプション: \"%.*s\"" msgid "If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly." msgstr "regexp_replace()でパラメータstartを指定したいのであれば、4番目のパラメータを明示的に整数にキャストしてください。" -#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1082 utils/adt/regexp.c:1146 utils/adt/regexp.c:1155 utils/adt/regexp.c:1164 utils/adt/regexp.c:1173 utils/adt/regexp.c:1853 utils/adt/regexp.c:1862 utils/adt/regexp.c:1871 utils/misc/guc.c:6820 utils/misc/guc.c:6854 +#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1108 utils/adt/regexp.c:1172 utils/adt/regexp.c:1181 utils/adt/regexp.c:1190 utils/adt/regexp.c:1199 utils/adt/regexp.c:1879 utils/adt/regexp.c:1888 utils/adt/regexp.c:1897 utils/misc/guc.c:6820 utils/misc/guc.c:6854 #, c-format msgid "invalid value for parameter \"%s\": %d" msgstr "パラメータ\"%s\"の値が無効です: %d" -#: utils/adt/regexp.c:936 +#: utils/adt/regexp.c:939 #, c-format msgid "SQL regular expression may not contain more than two escape-double-quote separators" msgstr "SQL正規表現はエスケープされたダブルクオートを2つより多く含むことはできません" #. translator: %s is a SQL function name -#: utils/adt/regexp.c:1093 utils/adt/regexp.c:1184 utils/adt/regexp.c:1271 utils/adt/regexp.c:1310 utils/adt/regexp.c:1698 utils/adt/regexp.c:1753 utils/adt/regexp.c:1882 +#: utils/adt/regexp.c:1119 utils/adt/regexp.c:1210 utils/adt/regexp.c:1297 utils/adt/regexp.c:1336 utils/adt/regexp.c:1724 utils/adt/regexp.c:1779 utils/adt/regexp.c:1908 #, c-format msgid "%s does not support the \"global\" option" msgstr "%sは\"global\"オプションをサポートしません" -#: utils/adt/regexp.c:1312 +#: utils/adt/regexp.c:1338 #, c-format msgid "Use the regexp_matches function instead." msgstr "代わりにregexp_matchesを使ってください。" -#: utils/adt/regexp.c:1500 +#: utils/adt/regexp.c:1526 #, c-format msgid "too many regular expression matches" msgstr "正規表現のマッチが多過ぎます" @@ -26079,7 +26084,7 @@ msgstr "\"%s\"という名前の関数が複数あります" msgid "more than one operator named %s" msgstr "%sという名前の演算子が複数あります" -#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10516 utils/adt/ruleutils.c:10729 +#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10526 utils/adt/ruleutils.c:10739 #, c-format msgid "too many arguments" msgstr "引数が多すぎます" @@ -26755,136 +26760,136 @@ msgstr "無効なXMLコメント" msgid "not an XML document" msgstr "XML文書ではありません" -#: utils/adt/xml.c:1008 utils/adt/xml.c:1031 +#: utils/adt/xml.c:1015 utils/adt/xml.c:1038 #, c-format msgid "invalid XML processing instruction" msgstr "無効なXML処理命令です" -#: utils/adt/xml.c:1009 +#: utils/adt/xml.c:1016 #, c-format msgid "XML processing instruction target name cannot be \"%s\"." msgstr "XML処理命令の対象名を\"%s\"とすることができませんでした。" -#: utils/adt/xml.c:1032 +#: utils/adt/xml.c:1039 #, c-format msgid "XML processing instruction cannot contain \"?>\"." msgstr "XML処理命令には\"?>\"を含めることはできません。" -#: utils/adt/xml.c:1111 +#: utils/adt/xml.c:1118 #, c-format msgid "xmlvalidate is not implemented" msgstr "XML の妥当性検査は実装されていません" -#: utils/adt/xml.c:1167 +#: utils/adt/xml.c:1174 #, c-format msgid "could not initialize XML library" msgstr "XMLライブラリを初期化できませんでした" -#: utils/adt/xml.c:1168 +#: utils/adt/xml.c:1175 #, c-format msgid "libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu." msgstr "libxml2が互換性がない文字型を持ちます: sizeof(char)=%zu、sizeof(xmlChar)=%zu" -#: utils/adt/xml.c:1254 +#: utils/adt/xml.c:1261 #, c-format msgid "could not set up XML error handler" msgstr "XMLエラーハンドラを設定できませんでした" -#: utils/adt/xml.c:1255 +#: utils/adt/xml.c:1262 #, c-format msgid "This probably indicates that the version of libxml2 being used is not compatible with the libxml2 header files that PostgreSQL was built with." msgstr "これはおそらく使用するlibxml2のバージョンがPostgreSQLを構築する時に使用したlibxml2ヘッダと互換性がないことを示します。" -#: utils/adt/xml.c:2281 +#: utils/adt/xml.c:2294 msgid "Invalid character value." msgstr "文字の値が有効ではありません" -#: utils/adt/xml.c:2284 +#: utils/adt/xml.c:2297 msgid "Space required." msgstr "スペースをあけてください。" -#: utils/adt/xml.c:2287 +#: utils/adt/xml.c:2300 msgid "standalone accepts only 'yes' or 'no'." msgstr "standalone には 'yes' か 'no' だけが有効です。" -#: utils/adt/xml.c:2290 +#: utils/adt/xml.c:2303 msgid "Malformed declaration: missing version." msgstr "不正な形式の宣言: バージョンがありません。" -#: utils/adt/xml.c:2293 +#: utils/adt/xml.c:2306 msgid "Missing encoding in text declaration." msgstr "テキスト宣言にエンコーディングの指定がありません。" -#: utils/adt/xml.c:2296 +#: utils/adt/xml.c:2309 msgid "Parsing XML declaration: '?>' expected." msgstr "XML 宣言のパース中: '>?' が必要です。" -#: utils/adt/xml.c:2299 +#: utils/adt/xml.c:2312 #, c-format msgid "Unrecognized libxml error code: %d." msgstr "認識できないlibxml のエラーコード: %d" -#: utils/adt/xml.c:2553 +#: utils/adt/xml.c:2566 #, c-format msgid "XML does not support infinite date values." msgstr "XMLはデータ値として無限をサポートしません。" -#: utils/adt/xml.c:2575 utils/adt/xml.c:2602 +#: utils/adt/xml.c:2588 utils/adt/xml.c:2615 #, c-format msgid "XML does not support infinite timestamp values." msgstr "XMLタイムスタンプ値としては無限をサポートしません。" -#: utils/adt/xml.c:3018 +#: utils/adt/xml.c:3031 #, c-format msgid "invalid query" msgstr "不正な無効な問い合わせ" -#: utils/adt/xml.c:3110 +#: utils/adt/xml.c:3123 #, c-format msgid "portal \"%s\" does not return tuples" msgstr "ポータル\"%s\"はタプルを返却しません" -#: utils/adt/xml.c:4362 +#: utils/adt/xml.c:4375 #, c-format msgid "invalid array for XML namespace mapping" msgstr "XML名前空間マッピングに対する不正な配列" -#: utils/adt/xml.c:4363 +#: utils/adt/xml.c:4376 #, c-format msgid "The array must be two-dimensional with length of the second axis equal to 2." msgstr "この配列は第2軸の長さが2である2次元配列でなければなりません。" -#: utils/adt/xml.c:4387 +#: utils/adt/xml.c:4400 #, c-format msgid "empty XPath expression" msgstr "空のXPath式" -#: utils/adt/xml.c:4439 +#: utils/adt/xml.c:4452 #, c-format msgid "neither namespace name nor URI may be null" msgstr "名前空間名もURIもnullにはできません" -#: utils/adt/xml.c:4446 +#: utils/adt/xml.c:4459 #, c-format msgid "could not register XML namespace with name \"%s\" and URI \"%s\"" msgstr "\"%s\"という名前のXML名前空間およびURI\"%s\"を登録できませんでした" -#: utils/adt/xml.c:4795 +#: utils/adt/xml.c:4808 #, c-format msgid "DEFAULT namespace is not supported" msgstr "デフォルト名前空間は実装されていません" -#: utils/adt/xml.c:4824 +#: utils/adt/xml.c:4837 #, c-format msgid "row path filter must not be empty string" msgstr "行パスフィルタは空文字列であってはなりません" -#: utils/adt/xml.c:4858 +#: utils/adt/xml.c:4871 #, c-format msgid "column path filter must not be empty string" msgstr "列パスフィルタ空文字列であってはなりません" -#: utils/adt/xml.c:5005 +#: utils/adt/xml.c:5018 #, c-format msgid "more than one value returned by column XPath expression" msgstr "列XPath式が2つ以上の値を返却しました" @@ -27615,7 +27620,7 @@ msgstr "bind_textdomain_codesetが失敗しました" msgid "invalid byte sequence for encoding \"%s\": %s" msgstr "符号化方式\"%s\"に対する不正なバイト列です: %s" -#: utils/mb/mbutils.c:1751 +#: utils/mb/mbutils.c:1759 #, c-format msgid "character with byte sequence %s in encoding \"%s\" has no equivalent in encoding \"%s\"" msgstr "符号化方式\"%2$s\"においてバイト列%1$sである文字は符号化方式\"%3$s\"で等価な文字を持ちません" diff --git a/src/backend/po/ru.po b/src/backend/po/ru.po index 089347fb7ebd6..5a92311aca062 100644 --- a/src/backend/po/ru.po +++ b/src/backend/po/ru.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: postgres (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:05+0300\n" -"PO-Revision-Date: 2025-05-03 16:34+0300\n" +"POT-Creation-Date: 2025-08-09 07:11+0300\n" +"PO-Revision-Date: 2025-08-09 07:31+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" "Language: ru\n" @@ -96,15 +96,15 @@ msgstr "не удалось открыть файл \"%s\" для чтения: #: ../common/controldata_utils.c:108 ../common/controldata_utils.c:110 #: access/transam/timeline.c:143 access/transam/timeline.c:362 -#: access/transam/twophase.c:1353 access/transam/xlog.c:3477 -#: access/transam/xlog.c:4341 access/transam/xlogrecovery.c:1238 +#: access/transam/twophase.c:1353 access/transam/xlog.c:3459 +#: access/transam/xlog.c:4323 access/transam/xlogrecovery.c:1238 #: access/transam/xlogrecovery.c:1336 access/transam/xlogrecovery.c:1373 #: access/transam/xlogrecovery.c:1440 backup/basebackup.c:2123 #: backup/walsummary.c:283 commands/extension.c:3548 libpq/hba.c:764 #: replication/logical/origin.c:745 replication/logical/origin.c:781 -#: replication/logical/reorderbuffer.c:5113 -#: replication/logical/snapbuild.c:2091 replication/slot.c:2236 -#: replication/slot.c:2277 replication/walsender.c:659 +#: replication/logical/reorderbuffer.c:5243 +#: replication/logical/snapbuild.c:2099 replication/slot.c:2232 +#: replication/slot.c:2273 replication/walsender.c:659 #: storage/file/buffile.c:470 storage/file/copydir.c:185 #: utils/adt/genfile.c:197 utils/adt/misc.c:1028 utils/cache/relmapper.c:829 #, c-format @@ -112,10 +112,10 @@ msgid "could not read file \"%s\": %m" msgstr "не удалось прочитать файл \"%s\": %m" #: ../common/controldata_utils.c:116 ../common/controldata_utils.c:119 -#: access/transam/xlog.c:3482 access/transam/xlog.c:4346 +#: access/transam/xlog.c:3464 access/transam/xlog.c:4328 #: replication/logical/origin.c:750 replication/logical/origin.c:789 -#: replication/logical/snapbuild.c:2096 replication/slot.c:2240 -#: replication/slot.c:2281 replication/walsender.c:664 +#: replication/logical/snapbuild.c:2104 replication/slot.c:2236 +#: replication/slot.c:2277 replication/walsender.c:664 #: utils/cache/relmapper.c:833 #, c-format msgid "could not read file \"%s\": read %d of %zu" @@ -126,17 +126,17 @@ msgstr "не удалось прочитать файл \"%s\" (прочитан #: access/heap/rewriteheap.c:1141 access/heap/rewriteheap.c:1246 #: access/transam/timeline.c:392 access/transam/timeline.c:438 #: access/transam/timeline.c:512 access/transam/twophase.c:1365 -#: access/transam/twophase.c:1784 access/transam/xlog.c:3323 -#: access/transam/xlog.c:3517 access/transam/xlog.c:3522 -#: access/transam/xlog.c:3658 access/transam/xlog.c:4311 -#: access/transam/xlog.c:5246 commands/copyfrom.c:1799 commands/copyto.c:325 +#: access/transam/twophase.c:1784 access/transam/xlog.c:3305 +#: access/transam/xlog.c:3499 access/transam/xlog.c:3504 +#: access/transam/xlog.c:3640 access/transam/xlog.c:4293 +#: access/transam/xlog.c:5228 commands/copyfrom.c:1799 commands/copyto.c:325 #: libpq/be-fsstubs.c:470 libpq/be-fsstubs.c:540 #: replication/logical/origin.c:683 replication/logical/origin.c:822 -#: replication/logical/reorderbuffer.c:5165 -#: replication/logical/snapbuild.c:1858 replication/logical/snapbuild.c:1982 -#: replication/slot.c:2126 replication/slot.c:2288 replication/walsender.c:674 -#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:828 -#: storage/file/fd.c:3753 storage/file/fd.c:3859 utils/cache/relmapper.c:841 +#: replication/logical/reorderbuffer.c:5295 +#: replication/logical/snapbuild.c:1866 replication/logical/snapbuild.c:1990 +#: replication/slot.c:2122 replication/slot.c:2284 replication/walsender.c:674 +#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:825 +#: storage/file/fd.c:3750 storage/file/fd.c:3856 utils/cache/relmapper.c:841 #: utils/cache/relmapper.c:956 #, c-format msgid "could not close file \"%s\": %m" @@ -165,31 +165,31 @@ msgstr "" #: ../common/file_utils.c:406 ../common/file_utils.c:480 #: access/heap/rewriteheap.c:1229 access/transam/timeline.c:111 #: access/transam/timeline.c:251 access/transam/timeline.c:348 -#: access/transam/twophase.c:1309 access/transam/xlog.c:3230 -#: access/transam/xlog.c:3393 access/transam/xlog.c:3432 -#: access/transam/xlog.c:3625 access/transam/xlog.c:4331 +#: access/transam/twophase.c:1309 access/transam/xlog.c:3212 +#: access/transam/xlog.c:3375 access/transam/xlog.c:3414 +#: access/transam/xlog.c:3607 access/transam/xlog.c:4313 #: access/transam/xlogrecovery.c:4264 access/transam/xlogrecovery.c:4367 #: access/transam/xlogutils.c:836 backup/basebackup.c:547 #: backup/basebackup.c:1598 backup/walsummary.c:220 libpq/hba.c:624 #: postmaster/syslogger.c:1511 replication/logical/origin.c:735 -#: replication/logical/reorderbuffer.c:3766 -#: replication/logical/reorderbuffer.c:4320 -#: replication/logical/reorderbuffer.c:5093 -#: replication/logical/snapbuild.c:1813 replication/logical/snapbuild.c:1923 -#: replication/slot.c:2208 replication/walsender.c:632 -#: replication/walsender.c:3081 storage/file/copydir.c:151 -#: storage/file/fd.c:803 storage/file/fd.c:3510 storage/file/fd.c:3740 -#: storage/file/fd.c:3830 storage/smgr/md.c:661 utils/cache/relmapper.c:818 +#: replication/logical/reorderbuffer.c:3896 +#: replication/logical/reorderbuffer.c:4450 +#: replication/logical/reorderbuffer.c:5223 +#: replication/logical/snapbuild.c:1821 replication/logical/snapbuild.c:1931 +#: replication/slot.c:2204 replication/walsender.c:632 +#: replication/walsender.c:3085 storage/file/copydir.c:151 +#: storage/file/fd.c:800 storage/file/fd.c:3507 storage/file/fd.c:3737 +#: storage/file/fd.c:3827 storage/smgr/md.c:661 utils/cache/relmapper.c:818 #: utils/cache/relmapper.c:935 utils/error/elog.c:2124 #: utils/init/miscinit.c:1580 utils/init/miscinit.c:1714 -#: utils/init/miscinit.c:1791 utils/misc/guc.c:4777 utils/misc/guc.c:4827 +#: utils/init/miscinit.c:1791 utils/misc/guc.c:4782 utils/misc/guc.c:4832 #, c-format msgid "could not open file \"%s\": %m" msgstr "не удалось открыть файл \"%s\": %m" #: ../common/controldata_utils.c:246 ../common/controldata_utils.c:249 #: access/transam/twophase.c:1757 access/transam/twophase.c:1766 -#: access/transam/xlog.c:9280 access/transam/xlogfuncs.c:698 +#: access/transam/xlog.c:9306 access/transam/xlogfuncs.c:698 #: backup/basebackup_server.c:173 backup/basebackup_server.c:266 #: backup/walsummary.c:304 postmaster/postmaster.c:4127 #: postmaster/syslogger.c:1522 postmaster/syslogger.c:1535 @@ -203,12 +203,12 @@ msgstr "не удалось записать файл \"%s\": %m" #: access/heap/rewriteheap.c:925 access/heap/rewriteheap.c:1135 #: access/heap/rewriteheap.c:1240 access/transam/timeline.c:432 #: access/transam/timeline.c:506 access/transam/twophase.c:1778 -#: access/transam/xlog.c:3316 access/transam/xlog.c:3511 -#: access/transam/xlog.c:4304 access/transam/xlog.c:8655 -#: access/transam/xlog.c:8700 backup/basebackup_server.c:207 -#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1851 -#: replication/slot.c:2112 replication/slot.c:2218 storage/file/fd.c:820 -#: storage/file/fd.c:3851 storage/smgr/md.c:1331 storage/smgr/md.c:1376 +#: access/transam/xlog.c:3298 access/transam/xlog.c:3493 +#: access/transam/xlog.c:4286 access/transam/xlog.c:8681 +#: access/transam/xlog.c:8726 backup/basebackup_server.c:207 +#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1859 +#: replication/slot.c:2108 replication/slot.c:2214 storage/file/fd.c:817 +#: storage/file/fd.c:3848 storage/smgr/md.c:1331 storage/smgr/md.c:1376 #: storage/sync/sync.c:446 utils/misc/guc.c:4530 #, c-format msgid "could not fsync file \"%s\": %m" @@ -222,15 +222,16 @@ msgstr "не удалось синхронизировать с ФС файл \" #: ../common/parse_manifest.c:852 ../common/psprintf.c:143 #: ../common/scram-common.c:268 ../common/stringinfo.c:314 ../port/path.c:828 #: ../port/path.c:865 ../port/path.c:882 access/transam/twophase.c:1418 -#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1352 -#: libpq/auth.c:1396 libpq/auth.c:1953 libpq/be-secure-gssapi.c:524 -#: postmaster/bgworker.c:355 postmaster/bgworker.c:945 -#: postmaster/postmaster.c:3560 postmaster/postmaster.c:4021 -#: postmaster/postmaster.c:4383 postmaster/walsummarizer.c:935 +#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1360 +#: libpq/auth.c:1404 libpq/auth.c:1961 libpq/be-secure-gssapi.c:534 +#: libpq/be-secure-gssapi.c:714 postmaster/bgworker.c:355 +#: postmaster/bgworker.c:945 postmaster/postmaster.c:3560 +#: postmaster/postmaster.c:4021 postmaster/postmaster.c:4383 +#: postmaster/walsummarizer.c:935 #: replication/libpqwalreceiver/libpqwalreceiver.c:387 -#: replication/logical/logical.c:210 replication/walsender.c:839 -#: storage/buffer/localbuf.c:606 storage/file/fd.c:912 storage/file/fd.c:1443 -#: storage/file/fd.c:1604 storage/file/fd.c:2531 storage/ipc/procarray.c:1465 +#: replication/logical/logical.c:212 replication/walsender.c:839 +#: storage/buffer/localbuf.c:606 storage/file/fd.c:909 storage/file/fd.c:1440 +#: storage/file/fd.c:1601 storage/file/fd.c:2528 storage/ipc/procarray.c:1465 #: storage/ipc/procarray.c:2219 storage/ipc/procarray.c:2226 #: storage/ipc/procarray.c:2731 storage/ipc/procarray.c:3435 #: utils/adt/formatting.c:1725 utils/adt/formatting.c:1873 @@ -320,7 +321,7 @@ msgstr "нехватка памяти\n" msgid "cannot duplicate null pointer (internal error)\n" msgstr "попытка дублирования нулевого указателя (внутренняя ошибка)\n" -#: ../common/file_utils.c:76 storage/file/fd.c:3516 +#: ../common/file_utils.c:76 storage/file/fd.c:3513 #, c-format msgid "could not synchronize file system for file \"%s\": %m" msgstr "не удалось синхронизировать с ФС файл \"%s\": %m" @@ -332,8 +333,8 @@ msgstr "не удалось синхронизировать с ФС файл \" #: backup/walsummary.c:247 backup/walsummary.c:254 commands/copyfrom.c:1749 #: commands/copyto.c:700 commands/extension.c:3527 commands/tablespace.c:804 #: commands/tablespace.c:893 postmaster/pgarch.c:680 -#: replication/logical/snapbuild.c:1709 replication/logical/snapbuild.c:2212 -#: storage/file/fd.c:1968 storage/file/fd.c:2054 storage/file/fd.c:3564 +#: replication/logical/snapbuild.c:1717 replication/logical/snapbuild.c:2220 +#: storage/file/fd.c:1965 storage/file/fd.c:2051 storage/file/fd.c:3561 #: utils/adt/dbsize.c:105 utils/adt/dbsize.c:257 utils/adt/dbsize.c:337 #: utils/adt/genfile.c:437 utils/adt/genfile.c:612 utils/adt/misc.c:340 #, c-format @@ -348,22 +349,22 @@ msgstr "эта сборка программы не поддерживает м #: ../common/file_utils.c:151 ../common/file_utils.c:281 #: ../common/pgfnames.c:48 ../common/rmtree.c:63 commands/tablespace.c:728 #: commands/tablespace.c:738 postmaster/postmaster.c:1470 -#: storage/file/fd.c:2933 storage/file/reinit.c:126 utils/adt/misc.c:256 +#: storage/file/fd.c:2930 storage/file/reinit.c:126 utils/adt/misc.c:256 #: utils/misc/tzparser.c:339 #, c-format msgid "could not open directory \"%s\": %m" msgstr "не удалось открыть каталог \"%s\": %m" #: ../common/file_utils.c:169 ../common/file_utils.c:315 -#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2945 +#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2942 #, c-format msgid "could not read directory \"%s\": %m" msgstr "не удалось прочитать каталог \"%s\": %m" #: ../common/file_utils.c:498 access/transam/xlogarchive.c:389 #: postmaster/pgarch.c:834 postmaster/syslogger.c:1559 -#: replication/logical/snapbuild.c:1870 replication/slot.c:936 -#: replication/slot.c:1998 replication/slot.c:2140 storage/file/fd.c:838 +#: replication/logical/snapbuild.c:1878 replication/slot.c:936 +#: replication/slot.c:1994 replication/slot.c:2136 storage/file/fd.c:835 #: utils/time/snapmgr.c:1255 #, c-format msgid "could not rename file \"%s\" to \"%s\": %m" @@ -373,92 +374,92 @@ msgstr "не удалось переименовать файл \"%s\" в \"%s\" msgid "internal error" msgstr "внутренняя ошибка" -#: ../common/jsonapi.c:2121 +#: ../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "" "Инкрементальный лексический анализатор не подходит для нисходящего " "рекурсивного разбора." -#: ../common/jsonapi.c:2123 +#: ../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "" "Для инкрементального разбора требуется инкрементальный лексический " "анализатор." -#: ../common/jsonapi.c:2125 +#: ../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "" "Слишком большая вложенность JSON, максимальная допустимая глубина: 6400." -#: ../common/jsonapi.c:2127 +#: ../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "Неверная спецпоследовательность: \"\\%.*s\"." -#: ../common/jsonapi.c:2131 +#: ../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "Символ с кодом 0x%02x необходимо экранировать." -#: ../common/jsonapi.c:2135 +#: ../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "Ожидался конец текста, но обнаружено продолжение \"%.*s\"." -#: ../common/jsonapi.c:2138 +#: ../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "Ожидался элемент массива или \"]\", но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2141 +#: ../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"]\", но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2144 +#: ../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "Ожидалось \":\", но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2147 +#: ../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "Ожидалось значение JSON, но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2150 +#: ../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "Неожиданный конец входной строки." -#: ../common/jsonapi.c:2152 +#: ../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "Ожидалась строка или \"}\", но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2155 +#: ../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"}\", но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2158 +#: ../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "Ожидалась строка, но обнаружено \"%.*s\"." -#: ../common/jsonapi.c:2161 +#: ../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "Ошибочный элемент \"%.*s\"." -#: ../common/jsonapi.c:2164 jsonpath_scan.l:608 +#: ../common/jsonapi.c:2167 jsonpath_scan.l:608 #, c-format msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 нельзя преобразовать в текст." -#: ../common/jsonapi.c:2166 +#: ../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "За \"\\u\" должны следовать четыре шестнадцатеричные цифры." -#: ../common/jsonapi.c:2169 +#: ../common/jsonapi.c:2172 msgid "" "Unicode escape values cannot be used for code point values above 007F when " "the encoding is not UTF8." @@ -466,19 +467,19 @@ msgstr "" "Спецкоды Unicode для значений выше 007F можно использовать только с " "кодировкой UTF8." -#: ../common/jsonapi.c:2178 +#: ../common/jsonapi.c:2181 #, c-format msgid "" "Unicode escape value could not be translated to the server's encoding %s." msgstr "Спецкод Unicode нельзя преобразовать в серверную кодировку %s." -#: ../common/jsonapi.c:2185 jsonpath_scan.l:641 +#: ../common/jsonapi.c:2188 jsonpath_scan.l:641 #, c-format msgid "Unicode high surrogate must not follow a high surrogate." msgstr "" "Старшее слово суррогата Unicode не может следовать за другим старшим словом." -#: ../common/jsonapi.c:2187 jsonpath_scan.l:652 jsonpath_scan.l:662 +#: ../common/jsonapi.c:2190 jsonpath_scan.l:652 jsonpath_scan.l:662 #: jsonpath_scan.l:713 #, c-format msgid "Unicode low surrogate must follow a high surrogate." @@ -664,8 +665,8 @@ msgstr "не удалось разобрать манифест копии: %s" #: ../common/percentrepl.c:79 ../common/percentrepl.c:85 #: ../common/percentrepl.c:118 ../common/percentrepl.c:124 #: tcop/backend_startup.c:741 utils/misc/guc.c:3167 utils/misc/guc.c:3208 -#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6931 -#: utils/misc/guc.c:6972 +#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6942 +#: utils/misc/guc.c:6983 #, c-format msgid "invalid value for parameter \"%s\": \"%s\"" msgstr "неверное значение для параметра \"%s\": \"%s\"" @@ -730,10 +731,10 @@ msgstr "не удалось получить код выхода от подпр #: access/transam/twophase.c:1717 access/transam/xlogarchive.c:119 #: access/transam/xlogarchive.c:399 postmaster/postmaster.c:1048 #: postmaster/syslogger.c:1488 replication/logical/origin.c:591 -#: replication/logical/reorderbuffer.c:4589 -#: replication/logical/snapbuild.c:1751 replication/logical/snapbuild.c:2185 -#: replication/slot.c:2192 storage/file/fd.c:878 storage/file/fd.c:3378 -#: storage/file/fd.c:3440 storage/file/reinit.c:261 storage/ipc/dsm.c:343 +#: replication/logical/reorderbuffer.c:4719 +#: replication/logical/snapbuild.c:1759 replication/logical/snapbuild.c:2193 +#: replication/slot.c:2188 storage/file/fd.c:875 storage/file/fd.c:3375 +#: storage/file/fd.c:3437 storage/file/reinit.c:261 storage/ipc/dsm.c:343 #: storage/smgr/md.c:381 storage/smgr/md.c:440 storage/sync/sync.c:243 #: utils/time/snapmgr.c:1591 #, c-format @@ -741,8 +742,8 @@ msgid "could not remove file \"%s\": %m" msgstr "не удалось стереть файл \"%s\": %m" #: ../common/rmtree.c:124 commands/tablespace.c:767 commands/tablespace.c:780 -#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3370 -#: storage/file/fd.c:3779 +#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3367 +#: storage/file/fd.c:3776 #, c-format msgid "could not remove directory \"%s\": %m" msgstr "ошибка при удалении каталога \"%s\": %m" @@ -782,7 +783,7 @@ msgstr "" msgid "could not look up effective user ID %ld: %s" msgstr "выяснить эффективный идентификатор пользователя (%ld) не удалось: %s" -#: ../common/username.c:45 libpq/auth.c:1888 +#: ../common/username.c:45 libpq/auth.c:1896 msgid "user does not exist" msgstr "пользователь не существует" @@ -1145,67 +1146,72 @@ msgstr "превышен предел пользовательских типо msgid "RESET must not include values for parameters" msgstr "В RESET не должно передаваться значение параметров" -#: access/common/reloptions.c:1263 +#: access/common/reloptions.c:1264 #, c-format msgid "unrecognized parameter namespace \"%s\"" msgstr "нераспознанное пространство имён параметров \"%s\"" -#: access/common/reloptions.c:1300 commands/variable.c:1214 +#: access/common/reloptions.c:1294 commands/foreigncmds.c:86 +#, c-format +msgid "invalid option name \"%s\": must not contain \"=\"" +msgstr "некорректное имя параметра \"%s\": имя не может содержать \"=\"" + +#: access/common/reloptions.c:1309 commands/variable.c:1214 #, c-format msgid "tables declared WITH OIDS are not supported" msgstr "таблицы со свойством WITH OIDS не поддерживаются" -#: access/common/reloptions.c:1468 +#: access/common/reloptions.c:1477 #, c-format msgid "unrecognized parameter \"%s\"" msgstr "нераспознанный параметр \"%s\"" -#: access/common/reloptions.c:1580 +#: access/common/reloptions.c:1589 #, c-format msgid "parameter \"%s\" specified more than once" msgstr "параметр \"%s\" указан неоднократно" -#: access/common/reloptions.c:1596 +#: access/common/reloptions.c:1605 #, c-format msgid "invalid value for boolean option \"%s\": %s" msgstr "неверное значение для логического параметра \"%s\": %s" -#: access/common/reloptions.c:1608 +#: access/common/reloptions.c:1617 #, c-format msgid "invalid value for integer option \"%s\": %s" msgstr "неверное значение для целочисленного параметра \"%s\": %s" -#: access/common/reloptions.c:1614 access/common/reloptions.c:1634 +#: access/common/reloptions.c:1623 access/common/reloptions.c:1643 #, c-format msgid "value %s out of bounds for option \"%s\"" msgstr "значение %s вне допустимых пределов параметра \"%s\"" -#: access/common/reloptions.c:1616 +#: access/common/reloptions.c:1625 #, c-format msgid "Valid values are between \"%d\" and \"%d\"." msgstr "Допускаются значения только от \"%d\" до \"%d\"." -#: access/common/reloptions.c:1628 +#: access/common/reloptions.c:1637 #, c-format msgid "invalid value for floating point option \"%s\": %s" msgstr "неверное значение для численного параметра \"%s\": %s" -#: access/common/reloptions.c:1636 +#: access/common/reloptions.c:1645 #, c-format msgid "Valid values are between \"%f\" and \"%f\"." msgstr "Допускаются значения только от \"%f\" до \"%f\"." -#: access/common/reloptions.c:1658 +#: access/common/reloptions.c:1667 #, c-format msgid "invalid value for enum option \"%s\": %s" msgstr "неверное значение для параметра-перечисления \"%s\": %s" -#: access/common/reloptions.c:1989 +#: access/common/reloptions.c:1998 #, c-format msgid "cannot specify storage parameters for a partitioned table" msgstr "задать параметры хранения для секционированной таблицы нельзя" -#: access/common/reloptions.c:1990 +#: access/common/reloptions.c:1999 #, c-format msgid "Specify storage parameters for its leaf partitions instead." msgstr "Задайте параметры хранения для её конечных секций." @@ -1375,7 +1381,7 @@ msgstr "" #: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:673 #: catalog/heap.c:679 commands/createas.c:201 commands/createas.c:508 -#: commands/indexcmds.c:2021 commands/tablecmds.c:18219 commands/view.c:81 +#: commands/indexcmds.c:2021 commands/tablecmds.c:18242 commands/view.c:81 #: regex/regc_pg_locale.c:245 utils/adt/formatting.c:1653 #: utils/adt/formatting.c:1801 utils/adt/formatting.c:1991 utils/adt/like.c:189 #: utils/adt/like_support.c:1024 utils/adt/varchar.c:738 @@ -1436,39 +1442,39 @@ msgid "" msgstr "" "в семействе операторов \"%s\" метода доступа %s нет межтипового оператора(ов)" -#: access/heap/heapam.c:2206 +#: access/heap/heapam.c:2241 #, c-format msgid "cannot insert tuples in a parallel worker" msgstr "вставлять кортежи в параллельном исполнителе нельзя" -#: access/heap/heapam.c:2725 +#: access/heap/heapam.c:2764 #, c-format msgid "cannot delete tuples during a parallel operation" msgstr "удалять кортежи во время параллельных операций нельзя" -#: access/heap/heapam.c:2772 +#: access/heap/heapam.c:2811 #, c-format msgid "attempted to delete invisible tuple" msgstr "попытка удаления невидимого кортежа" -#: access/heap/heapam.c:3220 access/heap/heapam.c:6501 access/index/genam.c:818 +#: access/heap/heapam.c:3261 access/heap/heapam.c:6542 access/index/genam.c:818 #, c-format msgid "cannot update tuples during a parallel operation" msgstr "изменять кортежи во время параллельных операций нельзя" -#: access/heap/heapam.c:3397 +#: access/heap/heapam.c:3438 #, c-format msgid "attempted to update invisible tuple" msgstr "попытка изменения невидимого кортежа" -#: access/heap/heapam.c:4908 access/heap/heapam.c:4946 -#: access/heap/heapam.c:5211 access/heap/heapam_handler.c:468 +#: access/heap/heapam.c:4949 access/heap/heapam.c:4987 +#: access/heap/heapam.c:5252 access/heap/heapam_handler.c:468 #, c-format msgid "could not obtain lock on row in relation \"%s\"" msgstr "не удалось получить блокировку строки в таблице \"%s\"" -#: access/heap/heapam.c:6314 commands/trigger.c:3340 -#: executor/nodeModifyTable.c:2396 executor/nodeModifyTable.c:2487 +#: access/heap/heapam.c:6355 commands/trigger.c:3427 +#: executor/nodeModifyTable.c:2399 executor/nodeModifyTable.c:2490 #, c-format msgid "" "tuple to be updated was already modified by an operation triggered by the " @@ -1498,13 +1504,13 @@ msgstr "не удалось записать в файл \"%s\" (записан #: access/heap/rewriteheap.c:977 access/heap/rewriteheap.c:1094 #: access/transam/timeline.c:329 access/transam/timeline.c:481 -#: access/transam/xlog.c:3255 access/transam/xlog.c:3446 -#: access/transam/xlog.c:4283 access/transam/xlog.c:9269 +#: access/transam/xlog.c:3237 access/transam/xlog.c:3428 +#: access/transam/xlog.c:4265 access/transam/xlog.c:9295 #: access/transam/xlogfuncs.c:692 backup/basebackup_server.c:149 #: backup/basebackup_server.c:242 commands/dbcommands.c:494 #: postmaster/launch_backend.c:340 postmaster/postmaster.c:4114 #: postmaster/walsummarizer.c:1212 replication/logical/origin.c:603 -#: replication/slot.c:2059 storage/file/copydir.c:157 storage/smgr/md.c:230 +#: replication/slot.c:2055 storage/file/copydir.c:157 storage/smgr/md.c:230 #: utils/time/snapmgr.c:1234 #, c-format msgid "could not create file \"%s\": %m" @@ -1517,15 +1523,15 @@ msgstr "не удалось обрезать файл \"%s\" до нужного #: access/heap/rewriteheap.c:1122 access/transam/timeline.c:384 #: access/transam/timeline.c:424 access/transam/timeline.c:498 -#: access/transam/xlog.c:3305 access/transam/xlog.c:3502 -#: access/transam/xlog.c:4295 commands/dbcommands.c:506 +#: access/transam/xlog.c:3287 access/transam/xlog.c:3484 +#: access/transam/xlog.c:4277 commands/dbcommands.c:506 #: postmaster/launch_backend.c:351 postmaster/launch_backend.c:363 #: replication/logical/origin.c:615 replication/logical/origin.c:657 -#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1827 -#: replication/slot.c:2094 storage/file/buffile.c:545 +#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1835 +#: replication/slot.c:2090 storage/file/buffile.c:545 #: storage/file/copydir.c:197 utils/init/miscinit.c:1655 #: utils/init/miscinit.c:1666 utils/init/miscinit.c:1674 utils/misc/guc.c:4491 -#: utils/misc/guc.c:4522 utils/misc/guc.c:5675 utils/misc/guc.c:5693 +#: utils/misc/guc.c:4522 utils/misc/guc.c:5680 utils/misc/guc.c:5698 #: utils/time/snapmgr.c:1239 utils/time/snapmgr.c:1246 #, c-format msgid "could not write to file \"%s\": %m" @@ -1699,14 +1705,14 @@ msgstr "" msgid "system usage: %s" msgstr "нагрузка системы: %s" -#: access/heap/vacuumlazy.c:2173 +#: access/heap/vacuumlazy.c:2178 #, c-format msgid "table \"%s\": removed %lld dead item identifiers in %u pages" msgstr "" "таблица \"%s\": удалено мёртвых идентификаторов элементов: %lld, на " "страницах: %u" -#: access/heap/vacuumlazy.c:2327 +#: access/heap/vacuumlazy.c:2332 #, c-format msgid "" "bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after " @@ -1715,12 +1721,12 @@ msgstr "" "несущественная операция обслуживания таблицы \"%s.%s.%s\" пропускается в " "качестве меры защиты после %d сканирований индекса" -#: access/heap/vacuumlazy.c:2330 +#: access/heap/vacuumlazy.c:2335 #, c-format msgid "The table's relfrozenxid or relminmxid is too far in the past." msgstr "Значение relfrozenxid или relminmxid таблицы слишком далеко в прошлом." -#: access/heap/vacuumlazy.c:2331 +#: access/heap/vacuumlazy.c:2336 #, c-format msgid "" "Consider increasing configuration parameter \"maintenance_work_mem\" or " @@ -1733,23 +1739,23 @@ msgstr "" "Также можно рассмотреть другие способы обеспечения производительности " "VACUUM, соответствующей скорости выделения идентификаторов транзакций." -#: access/heap/vacuumlazy.c:2593 +#: access/heap/vacuumlazy.c:2598 #, c-format msgid "\"%s\": stopping truncate due to conflicting lock request" msgstr "\"%s\": остановка усечения из-за конфликтующего запроса блокировки" -#: access/heap/vacuumlazy.c:2663 +#: access/heap/vacuumlazy.c:2668 #, c-format msgid "table \"%s\": truncated %u to %u pages" msgstr "таблица \"%s\": усечение (было страниц: %u, стало: %u)" -#: access/heap/vacuumlazy.c:2725 +#: access/heap/vacuumlazy.c:2730 #, c-format msgid "table \"%s\": suspending truncate due to conflicting lock request" msgstr "" "таблица \"%s\": приостановка усечения из-за конфликтующего запроса блокировки" -#: access/heap/vacuumlazy.c:2844 +#: access/heap/vacuumlazy.c:2849 #, c-format msgid "" "disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary " @@ -1758,47 +1764,47 @@ msgstr "" "отключение параллельного режима очистки \"%s\" --- создавать временные " "таблицы в параллельном режиме нельзя" -#: access/heap/vacuumlazy.c:3111 +#: access/heap/vacuumlazy.c:3116 #, c-format msgid "while scanning block %u offset %u of relation \"%s.%s\"" msgstr "при сканировании блока %u (смещение %u) отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3114 +#: access/heap/vacuumlazy.c:3119 #, c-format msgid "while scanning block %u of relation \"%s.%s\"" msgstr "при сканировании блока %u отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3118 +#: access/heap/vacuumlazy.c:3123 #, c-format msgid "while scanning relation \"%s.%s\"" msgstr "при сканировании отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3126 +#: access/heap/vacuumlazy.c:3131 #, c-format msgid "while vacuuming block %u offset %u of relation \"%s.%s\"" msgstr "при очистке блока %u (смещение %u) отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3129 +#: access/heap/vacuumlazy.c:3134 #, c-format msgid "while vacuuming block %u of relation \"%s.%s\"" msgstr "при очистке блока %u отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3133 +#: access/heap/vacuumlazy.c:3138 #, c-format msgid "while vacuuming relation \"%s.%s\"" msgstr "при очистке отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3138 commands/vacuumparallel.c:1112 +#: access/heap/vacuumlazy.c:3143 commands/vacuumparallel.c:1112 #, c-format msgid "while vacuuming index \"%s\" of relation \"%s.%s\"" msgstr "при очистке индекса \"%s\" отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3143 commands/vacuumparallel.c:1118 +#: access/heap/vacuumlazy.c:3148 commands/vacuumparallel.c:1118 #, c-format msgid "while cleaning up index \"%s\" of relation \"%s.%s\"" msgstr "при уборке индекса \"%s\" отношения \"%s.%s\"" -#: access/heap/vacuumlazy.c:3149 +#: access/heap/vacuumlazy.c:3154 #, c-format msgid "while truncating relation \"%s.%s\" to %u blocks" msgstr "при усечении отношения \"%s.%s\" до %u блок." @@ -1824,8 +1830,8 @@ msgid "cannot access index \"%s\" while it is being reindexed" msgstr "индекс \"%s\" перестраивается, обращаться к нему нельзя" #: access/index/indexam.c:203 catalog/objectaddress.c:1356 -#: commands/indexcmds.c:2851 commands/tablecmds.c:281 commands/tablecmds.c:305 -#: commands/tablecmds.c:17914 commands/tablecmds.c:19803 +#: commands/indexcmds.c:2885 commands/tablecmds.c:281 commands/tablecmds.c:305 +#: commands/tablecmds.c:17937 commands/tablecmds.c:19834 #, c-format msgid "\"%s\" is not an index" msgstr "\"%s\" - это не индекс" @@ -1879,7 +1885,7 @@ msgstr "" "Причиной тому могло быть прерывание операции VACUUM в версии 9.3 или старее, " "до обновления. Этот индекс нужно перестроить (REINDEX)." -#: access/nbtree/nbtutils.c:5108 +#: access/nbtree/nbtutils.c:5114 #, c-format msgid "" "index row size %zu exceeds btree version %u maximum %zu for index \"%s\"" @@ -1887,12 +1893,12 @@ msgstr "" "размер строки индекса (%zu) больше предельного для btree версии %u размера " "(%zu) (индекс \"%s\")" -#: access/nbtree/nbtutils.c:5114 +#: access/nbtree/nbtutils.c:5120 #, c-format msgid "Index row references tuple (%u,%u) in relation \"%s\"." msgstr "Строка индекса ссылается на кортеж (%u,%u) в отношении \"%s\"." -#: access/nbtree/nbtutils.c:5118 +#: access/nbtree/nbtutils.c:5124 #, c-format msgid "" "Values larger than 1/3 of a buffer page cannot be indexed.\n" @@ -2496,7 +2502,7 @@ msgstr "" "в файле \"%s\"" #: access/transam/twophase.c:1419 access/transam/xlogrecovery.c:565 -#: postmaster/walsummarizer.c:936 replication/logical/logical.c:211 +#: postmaster/walsummarizer.c:936 replication/logical/logical.c:213 #: replication/walsender.c:840 #, c-format msgid "Failed while allocating a WAL reading processor." @@ -2586,7 +2592,7 @@ msgstr "" "Для WAL-записи %X/%X найден файл состояния двухфазной фиксации, но эта " "транзакция уже была восстановлена с диска." -#: access/transam/twophase.c:2524 storage/file/fd.c:514 utils/fmgr/dfmgr.c:209 +#: access/transam/twophase.c:2524 storage/file/fd.c:511 utils/fmgr/dfmgr.c:209 #, c-format msgid "could not access file \"%s\": %m" msgstr "ошибка при обращении к файлу \"%s\": %m" @@ -2773,7 +2779,7 @@ msgstr "откатиться к точке сохранения во время msgid "cannot have more than 2^32-1 subtransactions in a transaction" msgstr "в одной транзакции не может быть больше 2^32-1 подтранзакций" -#: access/transam/xlog.c:1542 +#: access/transam/xlog.c:1543 #, c-format msgid "" "request to flush past end of generated WAL; request %X/%X, current position " @@ -2782,7 +2788,7 @@ msgstr "" "запрос на сброс данных за концом сгенерированного WAL; запрошена позиция %X/" "%X, текущая позиция %X/%X" -#: access/transam/xlog.c:1769 +#: access/transam/xlog.c:1770 #, c-format msgid "" "cannot read past end of generated WAL: requested %X/%X, current position %X/" @@ -2791,69 +2797,64 @@ msgstr "" "чтение за концом сгенерированного WAL невозможно: запрошена позиция %X/%X, " "текущая позиция %X/%X" -#: access/transam/xlog.c:2210 access/transam/xlog.c:4501 +#: access/transam/xlog.c:2211 access/transam/xlog.c:4483 #, c-format msgid "The WAL segment size must be a power of two between 1 MB and 1 GB." msgstr "" "Размер сегмента WAL должен задаваться степенью 2 в интервале от 1 МБ до 1 ГБ." -#: access/transam/xlog.c:2228 -#, c-format -msgid "\"%s\" must be set to -1 during binary upgrade mode." -msgstr "Параметр \"%s\" должен быть равен -1 в режиме двоичного обновления." - -#: access/transam/xlog.c:2477 +#: access/transam/xlog.c:2459 #, c-format msgid "could not write to log file \"%s\" at offset %u, length %zu: %m" msgstr "" "не удалось записать в файл журнала \"%s\" (смещение: %u, длина: %zu): %m" -#: access/transam/xlog.c:3739 access/transam/xlogutils.c:831 -#: replication/walsender.c:3075 +#: access/transam/xlog.c:3721 access/transam/xlogutils.c:831 +#: replication/walsender.c:3079 #, c-format msgid "requested WAL segment %s has already been removed" msgstr "запрошенный сегмент WAL %s уже удалён" -#: access/transam/xlog.c:4061 +#: access/transam/xlog.c:4043 #, c-format msgid "could not rename file \"%s\": %m" msgstr "не удалось переименовать файл \"%s\": %m" -#: access/transam/xlog.c:4104 access/transam/xlog.c:4115 -#: access/transam/xlog.c:4136 +#: access/transam/xlog.c:4086 access/transam/xlog.c:4097 +#: access/transam/xlog.c:4118 #, c-format msgid "required WAL directory \"%s\" does not exist" msgstr "требуемый каталог WAL \"%s\" не существует" -#: access/transam/xlog.c:4121 access/transam/xlog.c:4142 +#: access/transam/xlog.c:4103 access/transam/xlog.c:4124 #, c-format msgid "creating missing WAL directory \"%s\"" msgstr "создаётся отсутствующий каталог WAL \"%s\"" -#: access/transam/xlog.c:4125 access/transam/xlog.c:4145 +#: access/transam/xlog.c:4107 access/transam/xlog.c:4127 #: commands/dbcommands.c:3262 #, c-format msgid "could not create missing directory \"%s\": %m" msgstr "не удалось создать отсутствующий каталог \"%s\": %m" -#: access/transam/xlog.c:4212 +#: access/transam/xlog.c:4194 #, c-format msgid "could not generate secret authorization token" msgstr "не удалось сгенерировать случайное число для аутентификации" -#: access/transam/xlog.c:4363 access/transam/xlog.c:4373 -#: access/transam/xlog.c:4399 access/transam/xlog.c:4407 -#: access/transam/xlog.c:4415 access/transam/xlog.c:4421 -#: access/transam/xlog.c:4429 access/transam/xlog.c:4437 -#: access/transam/xlog.c:4445 access/transam/xlog.c:4453 +#: access/transam/xlog.c:4345 access/transam/xlog.c:4355 +#: access/transam/xlog.c:4381 access/transam/xlog.c:4389 +#: access/transam/xlog.c:4397 access/transam/xlog.c:4403 +#: access/transam/xlog.c:4411 access/transam/xlog.c:4419 +#: access/transam/xlog.c:4427 access/transam/xlog.c:4435 +#: access/transam/xlog.c:4443 access/transam/xlog.c:4451 #: access/transam/xlog.c:4461 access/transam/xlog.c:4469 -#: access/transam/xlog.c:4479 access/transam/xlog.c:4487 #: utils/init/miscinit.c:1812 #, c-format msgid "database files are incompatible with server" msgstr "файлы базы данных несовместимы с сервером" -#: access/transam/xlog.c:4364 +#: access/transam/xlog.c:4346 #, c-format msgid "" "The database cluster was initialized with PG_CONTROL_VERSION %d (0x%08x), " @@ -2862,7 +2863,7 @@ msgstr "" "Кластер баз данных был инициализирован с PG_CONTROL_VERSION %d (0x%08x), но " "сервер скомпилирован с PG_CONTROL_VERSION %d (0x%08x)." -#: access/transam/xlog.c:4368 +#: access/transam/xlog.c:4350 #, c-format msgid "" "This could be a problem of mismatched byte ordering. It looks like you need " @@ -2871,7 +2872,7 @@ msgstr "" "Возможно, проблема вызвана разным порядком байт. Кажется, вам надо выполнить " "initdb." -#: access/transam/xlog.c:4374 +#: access/transam/xlog.c:4356 #, c-format msgid "" "The database cluster was initialized with PG_CONTROL_VERSION %d, but the " @@ -2880,18 +2881,18 @@ msgstr "" "Кластер баз данных был инициализирован с PG_CONTROL_VERSION %d, но сервер " "скомпилирован с PG_CONTROL_VERSION %d." -#: access/transam/xlog.c:4377 access/transam/xlog.c:4403 -#: access/transam/xlog.c:4411 access/transam/xlog.c:4417 +#: access/transam/xlog.c:4359 access/transam/xlog.c:4385 +#: access/transam/xlog.c:4393 access/transam/xlog.c:4399 #, c-format msgid "It looks like you need to initdb." msgstr "Кажется, вам надо выполнить initdb." -#: access/transam/xlog.c:4389 +#: access/transam/xlog.c:4371 #, c-format msgid "incorrect checksum in control file" msgstr "ошибка контрольной суммы в файле pg_control" -#: access/transam/xlog.c:4400 +#: access/transam/xlog.c:4382 #, c-format msgid "" "The database cluster was initialized with CATALOG_VERSION_NO %d, but the " @@ -2900,7 +2901,7 @@ msgstr "" "Кластер баз данных был инициализирован с CATALOG_VERSION_NO %d, но сервер " "скомпилирован с CATALOG_VERSION_NO %d." -#: access/transam/xlog.c:4408 +#: access/transam/xlog.c:4390 #, c-format msgid "" "The database cluster was initialized with MAXALIGN %d, but the server was " @@ -2909,7 +2910,7 @@ msgstr "" "Кластер баз данных был инициализирован с MAXALIGN %d, но сервер " "скомпилирован с MAXALIGN %d." -#: access/transam/xlog.c:4416 +#: access/transam/xlog.c:4398 #, c-format msgid "" "The database cluster appears to use a different floating-point number format " @@ -2918,7 +2919,7 @@ msgstr "" "Кажется, в кластере баз данных и в программе сервера используются разные " "форматы чисел с плавающей точкой." -#: access/transam/xlog.c:4422 +#: access/transam/xlog.c:4404 #, c-format msgid "" "The database cluster was initialized with BLCKSZ %d, but the server was " @@ -2927,16 +2928,16 @@ msgstr "" "Кластер баз данных был инициализирован с BLCKSZ %d, но сервер скомпилирован " "с BLCKSZ %d." -#: access/transam/xlog.c:4425 access/transam/xlog.c:4433 -#: access/transam/xlog.c:4441 access/transam/xlog.c:4449 -#: access/transam/xlog.c:4457 access/transam/xlog.c:4465 -#: access/transam/xlog.c:4473 access/transam/xlog.c:4482 -#: access/transam/xlog.c:4490 +#: access/transam/xlog.c:4407 access/transam/xlog.c:4415 +#: access/transam/xlog.c:4423 access/transam/xlog.c:4431 +#: access/transam/xlog.c:4439 access/transam/xlog.c:4447 +#: access/transam/xlog.c:4455 access/transam/xlog.c:4464 +#: access/transam/xlog.c:4472 #, c-format msgid "It looks like you need to recompile or initdb." msgstr "Кажется, вам надо перекомпилировать сервер или выполнить initdb." -#: access/transam/xlog.c:4430 +#: access/transam/xlog.c:4412 #, c-format msgid "" "The database cluster was initialized with RELSEG_SIZE %d, but the server was " @@ -2945,7 +2946,7 @@ msgstr "" "Кластер баз данных был инициализирован с RELSEG_SIZE %d, но сервер " "скомпилирован с RELSEG_SIZE %d." -#: access/transam/xlog.c:4438 +#: access/transam/xlog.c:4420 #, c-format msgid "" "The database cluster was initialized with XLOG_BLCKSZ %d, but the server was " @@ -2954,7 +2955,7 @@ msgstr "" "Кластер баз данных был инициализирован с XLOG_BLCKSZ %d, но сервер " "скомпилирован с XLOG_BLCKSZ %d." -#: access/transam/xlog.c:4446 +#: access/transam/xlog.c:4428 #, c-format msgid "" "The database cluster was initialized with NAMEDATALEN %d, but the server was " @@ -2963,7 +2964,7 @@ msgstr "" "Кластер баз данных был инициализирован с NAMEDATALEN %d, но сервер " "скомпилирован с NAMEDATALEN %d." -#: access/transam/xlog.c:4454 +#: access/transam/xlog.c:4436 #, c-format msgid "" "The database cluster was initialized with INDEX_MAX_KEYS %d, but the server " @@ -2972,7 +2973,7 @@ msgstr "" "Кластер баз данных был инициализирован с INDEX_MAX_KEYS %d, но сервер " "скомпилирован с INDEX_MAX_KEYS %d." -#: access/transam/xlog.c:4462 +#: access/transam/xlog.c:4444 #, c-format msgid "" "The database cluster was initialized with TOAST_MAX_CHUNK_SIZE %d, but the " @@ -2981,7 +2982,7 @@ msgstr "" "Кластер баз данных был инициализирован с TOAST_MAX_CHUNK_SIZE %d, но сервер " "скомпилирован с TOAST_MAX_CHUNK_SIZE %d." -#: access/transam/xlog.c:4470 +#: access/transam/xlog.c:4452 #, c-format msgid "" "The database cluster was initialized with LOBLKSIZE %d, but the server was " @@ -2990,7 +2991,7 @@ msgstr "" "Кластер баз данных был инициализирован с LOBLKSIZE %d, но сервер " "скомпилирован с LOBLKSIZE %d." -#: access/transam/xlog.c:4480 +#: access/transam/xlog.c:4462 #, c-format msgid "" "The database cluster was initialized without USE_FLOAT8_BYVAL but the server " @@ -2999,7 +3000,7 @@ msgstr "" "Кластер баз данных был инициализирован без USE_FLOAT8_BYVAL, но сервер " "скомпилирован с USE_FLOAT8_BYVAL." -#: access/transam/xlog.c:4488 +#: access/transam/xlog.c:4470 #, c-format msgid "" "The database cluster was initialized with USE_FLOAT8_BYVAL but the server " @@ -3008,7 +3009,7 @@ msgstr "" "Кластер баз данных был инициализирован с USE_FLOAT8_BYVAL, но сервер был " "скомпилирован без USE_FLOAT8_BYVAL." -#: access/transam/xlog.c:4497 +#: access/transam/xlog.c:4479 #, c-format msgid "invalid WAL segment size in control file (%d byte)" msgid_plural "invalid WAL segment size in control file (%d bytes)" @@ -3016,45 +3017,45 @@ msgstr[0] "управляющий файл содержит неверный р msgstr[1] "управляющий файл содержит неверный размер сегмента WAL (%d Б)" msgstr[2] "управляющий файл содержит неверный размер сегмента WAL (%d Б)" -#: access/transam/xlog.c:4510 +#: access/transam/xlog.c:4492 #, c-format msgid "\"min_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"min_wal_size\" должен быть минимум вдвое больше \"wal_segment_size\"" -#: access/transam/xlog.c:4514 +#: access/transam/xlog.c:4496 #, c-format msgid "\"max_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"max_wal_size\" должен быть минимум вдвое больше \"wal_segment_size\"" -#: access/transam/xlog.c:4662 catalog/namespace.c:4696 +#: access/transam/xlog.c:4644 catalog/namespace.c:4696 #: commands/tablespace.c:1210 commands/user.c:2529 commands/variable.c:72 -#: replication/slot.c:2446 tcop/postgres.c:3715 utils/error/elog.c:2247 +#: replication/slot.c:2442 tcop/postgres.c:3715 utils/error/elog.c:2247 #, c-format msgid "List syntax is invalid." msgstr "Ошибка синтаксиса в списке." -#: access/transam/xlog.c:4708 commands/user.c:2545 commands/variable.c:173 +#: access/transam/xlog.c:4690 commands/user.c:2545 commands/variable.c:173 #: tcop/postgres.c:3731 utils/error/elog.c:2273 #, c-format msgid "Unrecognized key word: \"%s\"." msgstr "нераспознанное ключевое слово: \"%s\"." -#: access/transam/xlog.c:5129 +#: access/transam/xlog.c:5111 #, c-format msgid "could not write bootstrap write-ahead log file: %m" msgstr "не удалось записать начальный файл журнала предзаписи: %m" -#: access/transam/xlog.c:5137 +#: access/transam/xlog.c:5119 #, c-format msgid "could not fsync bootstrap write-ahead log file: %m" msgstr "не удалось сбросить на диск начальный файл журнала предзаписи: %m" -#: access/transam/xlog.c:5143 +#: access/transam/xlog.c:5125 #, c-format msgid "could not close bootstrap write-ahead log file: %m" msgstr "не удалось закрыть начальный файл журнала предзаписи: %m" -#: access/transam/xlog.c:5368 +#: access/transam/xlog.c:5350 #, c-format msgid "" "WAL was generated with \"wal_level=minimal\", cannot continue recovering" @@ -3062,13 +3063,13 @@ msgstr "" "WAL был создан с параметром \"wal_level=minimal\", продолжение " "восстановления невозможно" -#: access/transam/xlog.c:5369 +#: access/transam/xlog.c:5351 #, c-format msgid "" "This happens if you temporarily set \"wal_level=minimal\" on the server." msgstr "Это происходит, если вы на время устанавливали \"wal_level=minimal\"." -#: access/transam/xlog.c:5370 +#: access/transam/xlog.c:5352 #, c-format msgid "" "Use a backup taken after setting \"wal_level\" to higher than \"minimal\"." @@ -3076,32 +3077,32 @@ msgstr "" "Используйте резервную копию, сделанную после переключения \"wal_level\" на " "любой уровень выше \"minimal\"." -#: access/transam/xlog.c:5435 +#: access/transam/xlog.c:5417 #, c-format msgid "control file contains invalid checkpoint location" msgstr "файл pg_control содержит неправильную позицию контрольной точки" -#: access/transam/xlog.c:5446 +#: access/transam/xlog.c:5428 #, c-format msgid "database system was shut down at %s" msgstr "система БД была выключена: %s" -#: access/transam/xlog.c:5452 +#: access/transam/xlog.c:5434 #, c-format msgid "database system was shut down in recovery at %s" msgstr "система БД была выключена в процессе восстановления: %s" -#: access/transam/xlog.c:5458 +#: access/transam/xlog.c:5440 #, c-format msgid "database system shutdown was interrupted; last known up at %s" msgstr "выключение системы БД было прервано; последний момент работы: %s" -#: access/transam/xlog.c:5464 +#: access/transam/xlog.c:5446 #, c-format msgid "database system was interrupted while in recovery at %s" msgstr "работа системы БД была прервана во время восстановления: %s" -#: access/transam/xlog.c:5466 +#: access/transam/xlog.c:5448 #, c-format msgid "" "This probably means that some data is corrupted and you will have to use the " @@ -3110,14 +3111,14 @@ msgstr "" "Это скорее всего означает, что некоторые данные повреждены и вам придётся " "восстановить БД из последней резервной копии." -#: access/transam/xlog.c:5472 +#: access/transam/xlog.c:5454 #, c-format msgid "database system was interrupted while in recovery at log time %s" msgstr "" "работа системы БД была прервана в процессе восстановления, время в журнале: " "%s" -#: access/transam/xlog.c:5474 +#: access/transam/xlog.c:5456 #, c-format msgid "" "If this has occurred more than once some data might be corrupted and you " @@ -3126,22 +3127,22 @@ msgstr "" "Если это происходит постоянно, возможно, какие-то данные были испорчены и " "для восстановления стоит выбрать более раннюю точку." -#: access/transam/xlog.c:5480 +#: access/transam/xlog.c:5462 #, c-format msgid "database system was interrupted; last known up at %s" msgstr "работа системы БД была прервана; последний момент работы: %s" -#: access/transam/xlog.c:5487 +#: access/transam/xlog.c:5469 #, c-format msgid "control file contains invalid database cluster state" msgstr "файл pg_control содержит неверный код состояния кластера" -#: access/transam/xlog.c:5875 +#: access/transam/xlog.c:5857 #, c-format msgid "WAL ends before end of online backup" msgstr "WAL закончился без признака окончания копирования" -#: access/transam/xlog.c:5876 +#: access/transam/xlog.c:5858 #, c-format msgid "" "All WAL generated while online backup was taken must be available at " @@ -3150,40 +3151,40 @@ msgstr "" "Все журналы WAL, созданные во время резервного копирования \"на ходу\", " "должны быть в наличии для восстановления." -#: access/transam/xlog.c:5880 +#: access/transam/xlog.c:5862 #, c-format msgid "WAL ends before consistent recovery point" msgstr "WAL закончился до согласованной точки восстановления" -#: access/transam/xlog.c:5926 +#: access/transam/xlog.c:5908 #, c-format msgid "selected new timeline ID: %u" msgstr "выбранный ID новой линии времени: %u" -#: access/transam/xlog.c:5959 +#: access/transam/xlog.c:5941 #, c-format msgid "archive recovery complete" msgstr "восстановление архива завершено" -#: access/transam/xlog.c:6612 +#: access/transam/xlog.c:6594 #, c-format msgid "shutting down" msgstr "выключение" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6651 +#: access/transam/xlog.c:6633 #, c-format msgid "restartpoint starting:%s%s%s%s%s%s%s%s" msgstr "начата точка перезапуска:%s%s%s%s%s%s%s%s" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6663 +#: access/transam/xlog.c:6645 #, c-format msgid "checkpoint starting:%s%s%s%s%s%s%s%s" msgstr "начата контрольная точка:%s%s%s%s%s%s%s%s" # well-spelled: синхр -#: access/transam/xlog.c:6728 +#: access/transam/xlog.c:6710 #, c-format msgid "" "restartpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d " @@ -3198,7 +3199,7 @@ msgstr "" "lsn=%X/%X, lsn redo=%X/%X" # well-spelled: синхр -#: access/transam/xlog.c:6751 +#: access/transam/xlog.c:6733 #, c-format msgid "" "checkpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d " @@ -3212,7 +3213,7 @@ msgstr "" "=%ld.%03d сек., средняя=%ld.%03d сек.; расстояние=%d kB, ожидалось=%d kB; " "lsn=%X/%X, lsn redo=%X/%X" -#: access/transam/xlog.c:7233 +#: access/transam/xlog.c:7225 #, c-format msgid "" "concurrent write-ahead log activity while database system is shutting down" @@ -3220,64 +3221,64 @@ msgstr "" "во время выключения системы баз данных отмечена активность в журнале " "предзаписи" -#: access/transam/xlog.c:7818 +#: access/transam/xlog.c:7840 #, c-format msgid "recovery restart point at %X/%X" msgstr "точка перезапуска восстановления в позиции %X/%X" -#: access/transam/xlog.c:7820 +#: access/transam/xlog.c:7842 #, c-format msgid "Last completed transaction was at log time %s." msgstr "Последняя завершённая транзакция была выполнена в %s." -#: access/transam/xlog.c:8082 +#: access/transam/xlog.c:8108 #, c-format msgid "restore point \"%s\" created at %X/%X" msgstr "точка восстановления \"%s\" создана в позиции %X/%X" -#: access/transam/xlog.c:8289 +#: access/transam/xlog.c:8315 #, c-format msgid "online backup was canceled, recovery cannot continue" msgstr "" "резервное копирование \"на ходу\" было отменено, продолжить восстановление " "нельзя" -#: access/transam/xlog.c:8347 +#: access/transam/xlog.c:8373 #, c-format msgid "unexpected timeline ID %u (should be %u) in shutdown checkpoint record" msgstr "" "неожиданный ID линии времени %u (должен быть %u) в записи точки выключения" -#: access/transam/xlog.c:8405 +#: access/transam/xlog.c:8431 #, c-format msgid "unexpected timeline ID %u (should be %u) in online checkpoint record" msgstr "" "неожиданный ID линии времени %u (должен быть %u) в записи точки активности" -#: access/transam/xlog.c:8434 +#: access/transam/xlog.c:8460 #, c-format msgid "unexpected timeline ID %u (should be %u) in end-of-recovery record" msgstr "" "неожиданный ID линии времени %u (должен быть %u) в записи конец-" "восстановления" -#: access/transam/xlog.c:8705 +#: access/transam/xlog.c:8731 #, c-format msgid "could not fsync write-through file \"%s\": %m" msgstr "не удалось синхронизировать с ФС файл сквозной записи %s: %m" -#: access/transam/xlog.c:8710 +#: access/transam/xlog.c:8736 #, c-format msgid "could not fdatasync file \"%s\": %m" msgstr "не удалось синхронизировать с ФС данные (fdatasync) файла \"%s\": %m" -#: access/transam/xlog.c:8797 access/transam/xlog.c:9133 +#: access/transam/xlog.c:8823 access/transam/xlog.c:9159 #, c-format msgid "WAL level not sufficient for making an online backup" msgstr "" "Выбранный уровень WAL недостаточен для резервного копирования \"на ходу\"" -#: access/transam/xlog.c:8798 access/transam/xlogfuncs.c:248 +#: access/transam/xlog.c:8824 access/transam/xlogfuncs.c:248 #, c-format msgid "" "\"wal_level\" must be set to \"replica\" or \"logical\" at server start." @@ -3285,12 +3286,12 @@ msgstr "" "Параметр \"wal_level\" должен иметь значение \"replica\" или \"logical\" при " "запуске сервера." -#: access/transam/xlog.c:8803 +#: access/transam/xlog.c:8829 #, c-format msgid "backup label too long (max %d bytes)" msgstr "длина метки резервной копии превышает предел (%d байт)" -#: access/transam/xlog.c:8924 +#: access/transam/xlog.c:8950 #, c-format msgid "" "WAL generated with \"full_page_writes=off\" was replayed since last " @@ -3299,7 +3300,7 @@ msgstr "" "после последней точки перезапуска был воспроизведён WAL, созданный в режиме " "\"full_page_writes=off\"" -#: access/transam/xlog.c:8926 access/transam/xlog.c:9222 +#: access/transam/xlog.c:8952 access/transam/xlog.c:9248 #, c-format msgid "" "This means that the backup being taken on the standby is corrupt and should " @@ -3311,28 +3312,28 @@ msgstr "" "CHECKPOINT на ведущем сервере, а затем попробуйте резервное копирование \"на " "ходу\" ещё раз." -#: access/transam/xlog.c:9006 backup/basebackup.c:1417 utils/adt/misc.c:354 +#: access/transam/xlog.c:9032 backup/basebackup.c:1417 utils/adt/misc.c:354 #, c-format msgid "could not read symbolic link \"%s\": %m" msgstr "не удалось прочитать символическую ссылку \"%s\": %m" -#: access/transam/xlog.c:9013 backup/basebackup.c:1422 utils/adt/misc.c:359 +#: access/transam/xlog.c:9039 backup/basebackup.c:1422 utils/adt/misc.c:359 #, c-format msgid "symbolic link \"%s\" target is too long" msgstr "целевой путь символической ссылки \"%s\" слишком длинный" -#: access/transam/xlog.c:9134 +#: access/transam/xlog.c:9160 #, c-format msgid "wal_level must be set to \"replica\" or \"logical\" at server start." msgstr "Установите wal_level \"replica\" или \"logical\" при запуске сервера." -#: access/transam/xlog.c:9172 backup/basebackup.c:1281 +#: access/transam/xlog.c:9198 backup/basebackup.c:1281 #, c-format msgid "the standby was promoted during online backup" msgstr "" "ведомый сервер был повышен в процессе резервного копирования \"на ходу\"" -#: access/transam/xlog.c:9173 backup/basebackup.c:1282 +#: access/transam/xlog.c:9199 backup/basebackup.c:1282 #, c-format msgid "" "This means that the backup being taken is corrupt and should not be used. " @@ -3341,7 +3342,7 @@ msgstr "" "Это означает, что создаваемая резервная копия испорчена и использовать её не " "следует. Попробуйте резервное копирование \"на ходу\" ещё раз." -#: access/transam/xlog.c:9220 +#: access/transam/xlog.c:9246 #, c-format msgid "" "WAL generated with \"full_page_writes=off\" was replayed during online backup" @@ -3349,13 +3350,13 @@ msgstr "" "в процессе резервного копирования \"на ходу\" был воспроизведён WAL, " "созданный в режиме \"full_page_writes=off\"" -#: access/transam/xlog.c:9336 +#: access/transam/xlog.c:9362 #, c-format msgid "base backup done, waiting for required WAL segments to be archived" msgstr "" "базовое копирование выполнено, ожидается архивация нужных сегментов WAL" -#: access/transam/xlog.c:9350 +#: access/transam/xlog.c:9376 #, c-format msgid "" "still waiting for all required WAL segments to be archived (%d seconds " @@ -3363,7 +3364,7 @@ msgid "" msgstr "" "продолжается ожидание архивации всех нужных сегментов WAL (прошло %d сек.)" -#: access/transam/xlog.c:9352 +#: access/transam/xlog.c:9378 #, c-format msgid "" "Check that your \"archive_command\" is executing properly. You can safely " @@ -3374,12 +3375,12 @@ msgstr "" "копирования можно отменить безопасно, но резервная копия базы будет " "непригодна без всех сегментов WAL." -#: access/transam/xlog.c:9359 +#: access/transam/xlog.c:9385 #, c-format msgid "all required WAL segments have been archived" msgstr "все нужные сегменты WAL заархивированы" -#: access/transam/xlog.c:9363 +#: access/transam/xlog.c:9389 #, c-format msgid "" "WAL archiving is not enabled; you must ensure that all required WAL segments " @@ -3388,7 +3389,7 @@ msgstr "" "архивация WAL не настроена; вы должны обеспечить копирование всех требуемых " "сегментов WAL другими средствами для получения резервной копии" -#: access/transam/xlog.c:9402 +#: access/transam/xlog.c:9428 #, c-format msgid "aborting backup due to backend exiting before pg_backup_stop was called" msgstr "" @@ -3561,52 +3562,52 @@ msgstr "" msgid "contrecord is requested by %X/%X" msgstr "в позиции %X/%X запрошено продолжение записи" -#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1134 +#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1144 #, c-format msgid "invalid record length at %X/%X: expected at least %u, got %u" msgstr "" "неверная длина записи в позиции %X/%X: ожидалось минимум %u, получено %u" -#: access/transam/xlogreader.c:758 +#: access/transam/xlogreader.c:759 #, c-format msgid "there is no contrecord flag at %X/%X" msgstr "нет флага contrecord в позиции %X/%X" -#: access/transam/xlogreader.c:771 +#: access/transam/xlogreader.c:772 #, c-format msgid "invalid contrecord length %u (expected %lld) at %X/%X" msgstr "неверная длина contrecord: %u (ожидалась %lld) в позиции %X/%X" -#: access/transam/xlogreader.c:1142 +#: access/transam/xlogreader.c:1152 #, c-format msgid "invalid resource manager ID %u at %X/%X" msgstr "неверный ID менеджера ресурсов %u в позиции %X/%X" -#: access/transam/xlogreader.c:1155 access/transam/xlogreader.c:1171 +#: access/transam/xlogreader.c:1165 access/transam/xlogreader.c:1181 #, c-format msgid "record with incorrect prev-link %X/%X at %X/%X" msgstr "запись с неверной ссылкой назад %X/%X в позиции %X/%X" -#: access/transam/xlogreader.c:1209 +#: access/transam/xlogreader.c:1219 #, c-format msgid "incorrect resource manager data checksum in record at %X/%X" msgstr "" "некорректная контрольная сумма данных менеджера ресурсов в записи в позиции " "%X/%X" -#: access/transam/xlogreader.c:1243 +#: access/transam/xlogreader.c:1253 #, c-format msgid "invalid magic number %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверное магическое число %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: access/transam/xlogreader.c:1258 access/transam/xlogreader.c:1300 +#: access/transam/xlogreader.c:1268 access/transam/xlogreader.c:1310 #, c-format msgid "invalid info bits %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверные информационные биты %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: access/transam/xlogreader.c:1274 +#: access/transam/xlogreader.c:1284 #, c-format msgid "" "WAL file is from different database system: WAL file database system " @@ -3615,7 +3616,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: в нём указан идентификатор системы БД " "%llu, а идентификатор системы pg_control: %llu" -#: access/transam/xlogreader.c:1282 +#: access/transam/xlogreader.c:1292 #, c-format msgid "" "WAL file is from different database system: incorrect segment size in page " @@ -3624,7 +3625,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный размер сегмента в заголовке " "страницы" -#: access/transam/xlogreader.c:1288 +#: access/transam/xlogreader.c:1298 #, c-format msgid "" "WAL file is from different database system: incorrect XLOG_BLCKSZ in page " @@ -3633,12 +3634,12 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный XLOG_BLCKSZ в заголовке " "страницы" -#: access/transam/xlogreader.c:1320 +#: access/transam/xlogreader.c:1330 #, c-format msgid "unexpected pageaddr %X/%X in WAL segment %s, LSN %X/%X, offset %u" msgstr "неожиданный pageaddr %X/%X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: access/transam/xlogreader.c:1346 +#: access/transam/xlogreader.c:1356 #, c-format msgid "" "out-of-sequence timeline ID %u (after %u) in WAL segment %s, LSN %X/%X, " @@ -3647,23 +3648,23 @@ msgstr "" "нарушение последовательности ID линии времени %u (после %u) в сегменте WAL " "%s, LSN %X/%X, смещение %u" -#: access/transam/xlogreader.c:1749 +#: access/transam/xlogreader.c:1759 #, c-format msgid "out-of-order block_id %u at %X/%X" msgstr "идентификатор блока %u идёт не по порядку в позиции %X/%X" -#: access/transam/xlogreader.c:1773 +#: access/transam/xlogreader.c:1783 #, c-format msgid "BKPBLOCK_HAS_DATA set, but no data included at %X/%X" msgstr "BKPBLOCK_HAS_DATA установлен, но данных в позиции %X/%X нет" -#: access/transam/xlogreader.c:1780 +#: access/transam/xlogreader.c:1790 #, c-format msgid "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X" msgstr "" "BKPBLOCK_HAS_DATA не установлен, но длина данных равна %u в позиции %X/%X" -#: access/transam/xlogreader.c:1816 +#: access/transam/xlogreader.c:1826 #, c-format msgid "" "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at " @@ -3672,21 +3673,21 @@ msgstr "" "BKPIMAGE_HAS_HOLE установлен, но для пропуска заданы смещение %u и длина %u " "при длине образа блока %u в позиции %X/%X" -#: access/transam/xlogreader.c:1832 +#: access/transam/xlogreader.c:1842 #, c-format msgid "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X" msgstr "" "BKPIMAGE_HAS_HOLE не установлен, но для пропуска заданы смещение %u и длина " "%u в позиции %X/%X" -#: access/transam/xlogreader.c:1846 +#: access/transam/xlogreader.c:1856 #, c-format msgid "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X" msgstr "" "BKPIMAGE_COMPRESSED установлен, но длина образа блока равна %u в позиции %X/" "%X" -#: access/transam/xlogreader.c:1861 +#: access/transam/xlogreader.c:1871 #, c-format msgid "" "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image " @@ -3695,41 +3696,41 @@ msgstr "" "ни BKPIMAGE_HAS_HOLE, ни BKPIMAGE_COMPRESSED не установлены, но длина образа " "блока равна %u в позиции %X/%X" -#: access/transam/xlogreader.c:1877 +#: access/transam/xlogreader.c:1887 #, c-format msgid "BKPBLOCK_SAME_REL set but no previous rel at %X/%X" msgstr "" "BKPBLOCK_SAME_REL установлен, но предыдущее значение не задано в позиции %X/" "%X" -#: access/transam/xlogreader.c:1889 +#: access/transam/xlogreader.c:1899 #, c-format msgid "invalid block_id %u at %X/%X" msgstr "неверный идентификатор блока %u в позиции %X/%X" -#: access/transam/xlogreader.c:1956 +#: access/transam/xlogreader.c:1966 #, c-format msgid "record with invalid length at %X/%X" msgstr "запись с неверной длиной в позиции %X/%X" -#: access/transam/xlogreader.c:1982 +#: access/transam/xlogreader.c:1992 #, c-format msgid "could not locate backup block with ID %d in WAL record" msgstr "не удалось найти копию блока с ID %d в записи журнала WAL" -#: access/transam/xlogreader.c:2066 +#: access/transam/xlogreader.c:2076 #, c-format msgid "could not restore image at %X/%X with invalid block %d specified" msgstr "" "не удалось восстановить образ в позиции %X/%X с указанным неверным блоком %d" -#: access/transam/xlogreader.c:2073 +#: access/transam/xlogreader.c:2083 #, c-format msgid "could not restore image at %X/%X with invalid state, block %d" msgstr "" "не удалось восстановить образ в позиции %X/%X с неверным состоянием, блок %d" -#: access/transam/xlogreader.c:2100 access/transam/xlogreader.c:2117 +#: access/transam/xlogreader.c:2110 access/transam/xlogreader.c:2127 #, c-format msgid "" "could not restore image at %X/%X compressed with %s not supported by build, " @@ -3738,7 +3739,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый методом %s, который не " "поддерживается этой сборкой, блок %d" -#: access/transam/xlogreader.c:2126 +#: access/transam/xlogreader.c:2136 #, c-format msgid "" "could not restore image at %X/%X compressed with unknown method, block %d" @@ -3746,7 +3747,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый неизвестным методом, " "блок %d" -#: access/transam/xlogreader.c:2134 +#: access/transam/xlogreader.c:2144 #, c-format msgid "could not decompress image at %X/%X, block %d" msgstr "не удалось развернуть образ в позиции %X/%X, блок %d" @@ -4679,7 +4680,7 @@ msgstr "" #: backup/basebackup_server.c:102 commands/dbcommands.c:477 #: commands/tablespace.c:157 commands/tablespace.c:173 -#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1986 +#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1982 #: storage/file/copydir.c:47 #, c-format msgid "could not create directory \"%s\": %m" @@ -4926,10 +4927,10 @@ msgstr "предложение IN SCHEMA нельзя использовать #: commands/tablecmds.c:8768 commands/tablecmds.c:8902 #: commands/tablecmds.c:9014 commands/tablecmds.c:12838 #: commands/tablecmds.c:13030 commands/tablecmds.c:13191 -#: commands/tablecmds.c:14380 commands/tablecmds.c:17007 commands/trigger.c:942 +#: commands/tablecmds.c:14403 commands/tablecmds.c:17030 commands/trigger.c:943 #: parser/analyze.c:2530 parser/parse_relation.c:737 parser/parse_target.c:1067 #: parser/parse_type.c:144 parser/parse_utilcmd.c:3409 -#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2923 +#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2940 #: utils/adt/ruleutils.c:2812 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist" @@ -4940,15 +4941,15 @@ msgstr "столбец \"%s\" в таблице \"%s\" не существует msgid "\"%s\" is an index" msgstr "\"%s\" - это индекс" -#: catalog/aclchk.c:1869 commands/tablecmds.c:14537 commands/tablecmds.c:17923 +#: catalog/aclchk.c:1869 commands/tablecmds.c:14560 commands/tablecmds.c:17946 #, c-format msgid "\"%s\" is a composite type" msgstr "\"%s\" - это составной тип" #: catalog/aclchk.c:1877 catalog/objectaddress.c:1363 commands/tablecmds.c:263 -#: commands/tablecmds.c:17887 utils/adt/acl.c:2107 utils/adt/acl.c:2137 -#: utils/adt/acl.c:2170 utils/adt/acl.c:2206 utils/adt/acl.c:2237 -#: utils/adt/acl.c:2268 +#: commands/tablecmds.c:17910 utils/adt/acl.c:2124 utils/adt/acl.c:2154 +#: utils/adt/acl.c:2187 utils/adt/acl.c:2223 utils/adt/acl.c:2254 +#: utils/adt/acl.c:2285 #, c-format msgid "\"%s\" is not a sequence" msgstr "\"%s\" - это не последовательность" @@ -5494,14 +5495,14 @@ msgstr "удалить объект %s нельзя, так как от него #: catalog/dependency.c:1153 catalog/dependency.c:1160 #: catalog/dependency.c:1171 commands/tablecmds.c:1459 -#: commands/tablecmds.c:15129 commands/tablespace.c:460 commands/user.c:1302 -#: commands/vacuum.c:211 commands/view.c:441 executor/execExprInterp.c:4655 -#: executor/execExprInterp.c:4663 libpq/auth.c:324 +#: commands/tablecmds.c:15152 commands/tablespace.c:460 commands/user.c:1302 +#: commands/vacuum.c:212 commands/view.c:441 executor/execExprInterp.c:4655 +#: executor/execExprInterp.c:4663 libpq/auth.c:332 #: replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1078 #: storage/lmgr/deadlock.c:1134 storage/lmgr/proc.c:1432 utils/misc/guc.c:3169 -#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6825 -#: utils/misc/guc.c:6859 utils/misc/guc.c:6893 utils/misc/guc.c:6936 -#: utils/misc/guc.c:6978 +#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6836 +#: utils/misc/guc.c:6870 utils/misc/guc.c:6904 utils/misc/guc.c:6947 +#: utils/misc/guc.c:6989 #, c-format msgid "%s" msgstr "%s" @@ -5679,7 +5680,7 @@ msgstr "слияние ограничения \"%s\" с унаследованн #: catalog/heap.c:2683 catalog/pg_constraint.c:854 commands/tablecmds.c:3074 #: commands/tablecmds.c:3377 commands/tablecmds.c:7089 -#: commands/tablecmds.c:15948 commands/tablecmds.c:16079 +#: commands/tablecmds.c:15971 commands/tablecmds.c:16102 #, c-format msgid "too many inheritance parents" msgstr "слишком много родителей в иерархии наследования" @@ -5839,12 +5840,12 @@ msgstr "DROP INDEX CONCURRENTLY должен быть первым действ msgid "cannot reindex temporary tables of other sessions" msgstr "переиндексировать временные таблицы других сеансов нельзя" -#: catalog/index.c:3679 commands/indexcmds.c:3626 +#: catalog/index.c:3679 commands/indexcmds.c:3660 #, c-format msgid "cannot reindex invalid index on TOAST table" msgstr "перестроить нерабочий индекс в таблице TOAST нельзя" -#: catalog/index.c:3695 commands/indexcmds.c:3504 commands/indexcmds.c:3650 +#: catalog/index.c:3695 commands/indexcmds.c:3538 commands/indexcmds.c:3684 #: commands/tablecmds.c:3581 #, c-format msgid "cannot move system relation \"%s\"" @@ -5863,7 +5864,7 @@ msgstr "" "пропускается" #: catalog/namespace.c:462 catalog/namespace.c:666 catalog/namespace.c:758 -#: commands/trigger.c:5729 +#: commands/trigger.c:5817 #, c-format msgid "cross-database references are not implemented: \"%s.%s.%s\"" msgstr "ссылки между базами не реализованы: \"%s.%s.%s\"" @@ -5947,7 +5948,7 @@ msgid "cross-database references are not implemented: %s" msgstr "ссылки между базами не реализованы: %s" #: catalog/namespace.c:3335 parser/parse_expr.c:875 parser/parse_target.c:1266 -#: gram.y:19181 gram.y:19221 +#: gram.y:19188 gram.y:19228 #, c-format msgid "improper qualified name (too many dotted names): %s" msgstr "неверное полное имя (слишком много компонентов): %s" @@ -6006,19 +6007,19 @@ msgid "\"%s\" is not a table" msgstr "\"%s\" - это не таблица" #: catalog/objectaddress.c:1378 commands/tablecmds.c:269 -#: commands/tablecmds.c:17892 commands/view.c:114 +#: commands/tablecmds.c:17915 commands/view.c:114 #, c-format msgid "\"%s\" is not a view" msgstr "\"%s\" - это не представление" #: catalog/objectaddress.c:1385 commands/matview.c:199 commands/tablecmds.c:275 -#: commands/tablecmds.c:17897 +#: commands/tablecmds.c:17920 #, c-format msgid "\"%s\" is not a materialized view" msgstr "\"%s\" - это не материализованное представление" #: catalog/objectaddress.c:1392 commands/tablecmds.c:293 -#: commands/tablecmds.c:17902 +#: commands/tablecmds.c:17925 #, c-format msgid "\"%s\" is not a foreign table" msgstr "\"%s\" - это не сторонняя таблица" @@ -6042,7 +6043,7 @@ msgstr "" #: catalog/objectaddress.c:1618 commands/functioncmds.c:132 #: commands/tablecmds.c:285 commands/typecmds.c:278 commands/typecmds.c:3843 #: parser/parse_type.c:243 parser/parse_type.c:272 parser/parse_type.c:801 -#: utils/adt/acl.c:4560 +#: utils/adt/acl.c:4577 #, c-format msgid "type \"%s\" does not exist" msgstr "тип \"%s\" не существует" @@ -6062,8 +6063,9 @@ msgstr "функция %d (%s, %s) из семейства %s не сущест msgid "user mapping for user \"%s\" on server \"%s\" does not exist" msgstr "сопоставление для пользователя \"%s\" на сервере \"%s\" не существует" -#: catalog/objectaddress.c:1834 commands/foreigncmds.c:430 -#: commands/foreigncmds.c:993 commands/foreigncmds.c:1356 foreign/foreign.c:713 +#: catalog/objectaddress.c:1834 commands/foreigncmds.c:441 +#: commands/foreigncmds.c:1004 commands/foreigncmds.c:1367 +#: foreign/foreign.c:713 #, c-format msgid "server \"%s\" does not exist" msgstr "сервер \"%s\" не существует" @@ -6841,7 +6843,7 @@ msgstr "" "отсоединения." #: catalog/pg_inherits.c:595 commands/tablecmds.c:4800 -#: commands/tablecmds.c:16194 +#: commands/tablecmds.c:16217 #, c-format msgid "" "Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending " @@ -7199,12 +7201,12 @@ msgstr "" "изменить владельца объектов, принадлежащих роли %s, нельзя, так как они " "нужны системе баз данных" -#: catalog/pg_subscription.c:438 +#: catalog/pg_subscription.c:463 #, c-format msgid "could not drop relation mapping for subscription \"%s\"" msgstr "удалить сопоставление отношений для подписки \"%s\" не получилось" -#: catalog/pg_subscription.c:440 +#: catalog/pg_subscription.c:465 #, c-format msgid "" "Table synchronization for relation \"%s\" is in progress and is in state " @@ -7214,7 +7216,7 @@ msgstr "Выполняется синхронизация отношения \"% #. translator: first %s is a SQL ALTER command and second %s is a #. SQL DROP command #. -#: catalog/pg_subscription.c:447 +#: catalog/pg_subscription.c:472 #, c-format msgid "" "Use %s to enable subscription if not already enabled or use %s to drop the " @@ -7378,12 +7380,12 @@ msgstr "" msgid "event trigger \"%s\" already exists" msgstr "событийный триггер \"%s\" уже существует" -#: commands/alter.c:86 commands/foreigncmds.c:593 +#: commands/alter.c:86 commands/foreigncmds.c:604 #, c-format msgid "foreign-data wrapper \"%s\" already exists" msgstr "обёртка сторонних данных \"%s\" уже существует" -#: commands/alter.c:89 commands/foreigncmds.c:884 +#: commands/alter.c:89 commands/foreigncmds.c:895 #, c-format msgid "server \"%s\" already exists" msgstr "сервер \"%s\" уже существует" @@ -7488,7 +7490,7 @@ msgid "handler function is not specified" msgstr "не указана функция-обработчик" #: commands/amcmds.c:264 commands/event_trigger.c:200 -#: commands/foreigncmds.c:489 commands/proclang.c:78 commands/trigger.c:702 +#: commands/foreigncmds.c:500 commands/proclang.c:78 commands/trigger.c:703 #: parser/parse_clause.c:943 #, c-format msgid "function %s must return type %s" @@ -7619,7 +7621,7 @@ msgstr "кластеризовать временные таблицы друг msgid "there is no previously clustered index for table \"%s\"" msgstr "таблица \"%s\" ранее не кластеризовалась по какому-либо индексу" -#: commands/cluster.c:191 commands/tablecmds.c:14838 commands/tablecmds.c:16770 +#: commands/cluster.c:191 commands/tablecmds.c:14861 commands/tablecmds.c:16793 #, c-format msgid "index \"%s\" for table \"%s\" does not exist" msgstr "индекс \"%s\" для таблицы \"%s\" не существует" @@ -7634,7 +7636,7 @@ msgstr "кластеризовать разделяемый каталог не msgid "cannot vacuum temporary tables of other sessions" msgstr "очищать временные таблицы других сеансов нельзя" -#: commands/cluster.c:513 commands/tablecmds.c:16780 +#: commands/cluster.c:513 commands/tablecmds.c:16803 #, c-format msgid "\"%s\" is not an index for table \"%s\"" msgstr "\"%s\" не является индексом таблицы \"%s\"" @@ -7784,8 +7786,8 @@ msgstr "нельзя обновить версию правила сортиро #. translator: %s is an SQL ALTER command #: commands/collationcmds.c:447 commands/subscriptioncmds.c:1376 #: commands/tablecmds.c:7938 commands/tablecmds.c:7948 -#: commands/tablecmds.c:7950 commands/tablecmds.c:14540 -#: commands/tablecmds.c:17925 commands/tablecmds.c:17946 +#: commands/tablecmds.c:7950 commands/tablecmds.c:14563 +#: commands/tablecmds.c:17948 commands/tablecmds.c:17969 #: commands/typecmds.c:3787 commands/typecmds.c:3872 commands/typecmds.c:4226 #, c-format msgid "Use %s instead." @@ -8081,7 +8083,7 @@ msgstr "Генерируемые столбцы нельзя использов msgid "column \"%s\" does not exist" msgstr "столбец \"%s\" не существует" -#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:951 +#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:952 #: parser/parse_target.c:1083 parser/parse_target.c:1094 #, c-format msgid "column \"%s\" specified more than once" @@ -9028,7 +9030,7 @@ msgstr "Используйте DROP AGGREGATE для удаления агрег #: commands/dropcmds.c:153 commands/sequence.c:462 commands/tablecmds.c:3892 #: commands/tablecmds.c:4050 commands/tablecmds.c:4102 -#: commands/tablecmds.c:17202 tcop/utility.c:1325 +#: commands/tablecmds.c:17225 tcop/utility.c:1325 #, c-format msgid "relation \"%s\" does not exist, skipping" msgstr "отношение \"%s\" не существует, пропускается" @@ -9153,7 +9155,7 @@ msgstr "правило \"%s\" для отношения \"%s\" не сущест msgid "foreign-data wrapper \"%s\" does not exist, skipping" msgstr "обёртка сторонних данных \"%s\" не существует, пропускается" -#: commands/dropcmds.c:448 commands/foreigncmds.c:1360 +#: commands/dropcmds.c:448 commands/foreigncmds.c:1371 #, c-format msgid "server \"%s\" does not exist, skipping" msgstr "сервер \"%s\" не существует, пропускается" @@ -9606,68 +9608,68 @@ msgstr "тип мультидиапазона для типа данных %s н msgid "file \"%s\" is too large" msgstr "файл \"%s\" слишком большой" -#: commands/foreigncmds.c:148 commands/foreigncmds.c:157 +#: commands/foreigncmds.c:159 commands/foreigncmds.c:168 #, c-format msgid "option \"%s\" not found" msgstr "нераспознанный параметр \"%s\"" -#: commands/foreigncmds.c:167 +#: commands/foreigncmds.c:178 #, c-format msgid "option \"%s\" provided more than once" msgstr "параметр \"%s\" указан неоднократно" -#: commands/foreigncmds.c:221 commands/foreigncmds.c:229 +#: commands/foreigncmds.c:232 commands/foreigncmds.c:240 #, c-format msgid "permission denied to change owner of foreign-data wrapper \"%s\"" msgstr "нет прав для изменения владельца обёртки сторонних данных \"%s\"" -#: commands/foreigncmds.c:223 +#: commands/foreigncmds.c:234 #, c-format msgid "Must be superuser to change owner of a foreign-data wrapper." msgstr "" "Для смены владельца обёртки сторонних данных нужно быть суперпользователем." -#: commands/foreigncmds.c:231 +#: commands/foreigncmds.c:242 #, c-format msgid "The owner of a foreign-data wrapper must be a superuser." msgstr "Владельцем обёртки сторонних данных должен быть суперпользователь." -#: commands/foreigncmds.c:291 commands/foreigncmds.c:707 foreign/foreign.c:691 +#: commands/foreigncmds.c:302 commands/foreigncmds.c:718 foreign/foreign.c:691 #, c-format msgid "foreign-data wrapper \"%s\" does not exist" msgstr "обёртка сторонних данных \"%s\" не существует" -#: commands/foreigncmds.c:325 +#: commands/foreigncmds.c:336 #, c-format msgid "foreign-data wrapper with OID %u does not exist" msgstr "обёртка сторонних данных с OID %u не существует" -#: commands/foreigncmds.c:462 +#: commands/foreigncmds.c:473 #, c-format msgid "foreign server with OID %u does not exist" msgstr "сторонний сервер с OID %u не существует" -#: commands/foreigncmds.c:580 +#: commands/foreigncmds.c:591 #, c-format msgid "permission denied to create foreign-data wrapper \"%s\"" msgstr "нет прав для создания обёртки сторонних данных \"%s\"" -#: commands/foreigncmds.c:582 +#: commands/foreigncmds.c:593 #, c-format msgid "Must be superuser to create a foreign-data wrapper." msgstr "Для создания обёртки сторонних данных нужно быть суперпользователем." -#: commands/foreigncmds.c:697 +#: commands/foreigncmds.c:708 #, c-format msgid "permission denied to alter foreign-data wrapper \"%s\"" msgstr "нет прав для изменения обёртки сторонних данных \"%s\"" -#: commands/foreigncmds.c:699 +#: commands/foreigncmds.c:710 #, c-format msgid "Must be superuser to alter a foreign-data wrapper." msgstr "Для изменения обёртки сторонних данных нужно быть суперпользователем." -#: commands/foreigncmds.c:730 +#: commands/foreigncmds.c:741 #, c-format msgid "" "changing the foreign-data wrapper handler can change behavior of existing " @@ -9676,7 +9678,7 @@ msgstr "" "при изменении обработчика в обёртке сторонних данных может измениться " "поведение существующих сторонних таблиц" -#: commands/foreigncmds.c:745 +#: commands/foreigncmds.c:756 #, c-format msgid "" "changing the foreign-data wrapper validator can cause the options for " @@ -9685,46 +9687,46 @@ msgstr "" "при изменении функции проверки в обёртке сторонних данных параметры " "зависимых объектов могут стать неверными" -#: commands/foreigncmds.c:876 +#: commands/foreigncmds.c:887 #, c-format msgid "server \"%s\" already exists, skipping" msgstr "сервер \"%s\" уже существует, пропускается" -#: commands/foreigncmds.c:1144 +#: commands/foreigncmds.c:1155 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\", skipping" msgstr "" "сопоставление пользователя \"%s\" для сервера \"%s\" уже существует, " "пропускается" -#: commands/foreigncmds.c:1154 +#: commands/foreigncmds.c:1165 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\"" msgstr "сопоставление пользователя \"%s\" для сервера \"%s\" уже существует" -#: commands/foreigncmds.c:1254 commands/foreigncmds.c:1374 +#: commands/foreigncmds.c:1265 commands/foreigncmds.c:1385 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\"" msgstr "сопоставление пользователя \"%s\" для сервера \"%s\" не существует" -#: commands/foreigncmds.c:1379 +#: commands/foreigncmds.c:1390 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\", skipping" msgstr "" "сопоставление пользователя \"%s\" для сервера \"%s\" не существует, " "пропускается" -#: commands/foreigncmds.c:1507 foreign/foreign.c:404 +#: commands/foreigncmds.c:1518 foreign/foreign.c:404 #, c-format msgid "foreign-data wrapper \"%s\" has no handler" msgstr "обёртка сторонних данных \"%s\" не имеет обработчика" -#: commands/foreigncmds.c:1513 +#: commands/foreigncmds.c:1524 #, c-format msgid "foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA" msgstr "обёртка сторонних данных \"%s\" не поддерживает IMPORT FOREIGN SCHEMA" -#: commands/foreigncmds.c:1615 +#: commands/foreigncmds.c:1626 #, c-format msgid "importing foreign table \"%s\"" msgstr "импорт сторонней таблицы \"%s\"" @@ -10290,7 +10292,7 @@ msgstr "включаемые столбцы не поддерживают ука msgid "could not determine which collation to use for index expression" msgstr "не удалось определить правило сортировки для индексного выражения" -#: commands/indexcmds.c:2028 commands/tablecmds.c:18226 commands/typecmds.c:811 +#: commands/indexcmds.c:2028 commands/tablecmds.c:18249 commands/typecmds.c:811 #: parser/parse_expr.c:2793 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 #: utils/adt/misc.c:630 #, c-format @@ -10333,8 +10335,8 @@ msgstr "метод доступа \"%s\" не поддерживает сорт msgid "access method \"%s\" does not support NULLS FIRST/LAST options" msgstr "метод доступа \"%s\" не поддерживает параметр NULLS FIRST/LAST" -#: commands/indexcmds.c:2210 commands/tablecmds.c:18251 -#: commands/tablecmds.c:18257 commands/typecmds.c:2311 +#: commands/indexcmds.c:2210 commands/tablecmds.c:18274 +#: commands/tablecmds.c:18280 commands/typecmds.c:2311 #, c-format msgid "data type %s has no default operator class for access method \"%s\"" msgstr "" @@ -10367,72 +10369,72 @@ msgid "there are multiple default operator classes for data type %s" msgstr "" "для типа данных %s определено несколько классов операторов по умолчанию" -#: commands/indexcmds.c:2681 +#: commands/indexcmds.c:2715 #, c-format msgid "unrecognized REINDEX option \"%s\"" msgstr "нераспознанный параметр REINDEX: \"%s\"" -#: commands/indexcmds.c:2913 +#: commands/indexcmds.c:2947 #, c-format msgid "table \"%s\" has no indexes that can be reindexed concurrently" msgstr "" "в таблице \"%s\" нет индексов, которые можно переиндексировать неблокирующим " "способом" -#: commands/indexcmds.c:2927 +#: commands/indexcmds.c:2961 #, c-format msgid "table \"%s\" has no indexes to reindex" msgstr "в таблице \"%s\" нет индексов для переиндексации" -#: commands/indexcmds.c:2974 commands/indexcmds.c:3485 -#: commands/indexcmds.c:3615 +#: commands/indexcmds.c:3008 commands/indexcmds.c:3519 +#: commands/indexcmds.c:3649 #, c-format msgid "cannot reindex system catalogs concurrently" msgstr "Переиндексировать системные каталоги неблокирующим способом нельзя" -#: commands/indexcmds.c:2998 +#: commands/indexcmds.c:3032 #, c-format msgid "can only reindex the currently open database" msgstr "переиндексировать можно только текущую базу данных" -#: commands/indexcmds.c:3090 +#: commands/indexcmds.c:3124 #, c-format msgid "cannot reindex system catalogs concurrently, skipping all" msgstr "" "все системные каталоги пропускаются, так как их нельзя переиндексировать " "неблокирующим способом" -#: commands/indexcmds.c:3123 +#: commands/indexcmds.c:3157 #, c-format msgid "cannot move system relations, skipping all" msgstr "переместить системные отношения нельзя, все они пропускаются" -#: commands/indexcmds.c:3169 +#: commands/indexcmds.c:3203 #, c-format msgid "while reindexing partitioned table \"%s.%s\"" msgstr "при переиндексировании секционированной таблицы \"%s.%s\"" -#: commands/indexcmds.c:3172 +#: commands/indexcmds.c:3206 #, c-format msgid "while reindexing partitioned index \"%s.%s\"" msgstr "при перестроении секционированного индекса \"%s.%s\"" -#: commands/indexcmds.c:3365 commands/indexcmds.c:4241 +#: commands/indexcmds.c:3399 commands/indexcmds.c:4283 #, c-format msgid "table \"%s.%s\" was reindexed" msgstr "таблица \"%s.%s\" переиндексирована" -#: commands/indexcmds.c:3517 commands/indexcmds.c:3570 +#: commands/indexcmds.c:3551 commands/indexcmds.c:3604 #, c-format msgid "skipping reindex of invalid index \"%s.%s\"" msgstr "индекс \"%s.%s\" — нерабочий, переиндексация пропускается" -#: commands/indexcmds.c:3520 commands/indexcmds.c:3573 +#: commands/indexcmds.c:3554 commands/indexcmds.c:3607 #, c-format msgid "Use DROP INDEX or REINDEX INDEX." msgstr "Выполните DROP INDEX или REINDEX INDEX." -#: commands/indexcmds.c:3524 +#: commands/indexcmds.c:3558 #, c-format msgid "" "cannot reindex exclusion constraint index \"%s.%s\" concurrently, skipping" @@ -10440,24 +10442,24 @@ msgstr "" "перестроить индекс ограничения-исключения \"%s.%s\" неблокирующим способом " "нельзя, он пропускается" -#: commands/indexcmds.c:3680 +#: commands/indexcmds.c:3714 #, c-format msgid "cannot reindex this type of relation concurrently" msgstr "переиндексировать отношение такого типа неблокирующим способом нельзя" -#: commands/indexcmds.c:3698 +#: commands/indexcmds.c:3732 #, c-format msgid "cannot move non-shared relation to tablespace \"%s\"" msgstr "" "переместить отношение, не являющееся разделяемым, в табличное пространство " "\"%s\" нельзя" -#: commands/indexcmds.c:4222 commands/indexcmds.c:4234 +#: commands/indexcmds.c:4264 commands/indexcmds.c:4276 #, c-format msgid "index \"%s.%s\" was reindexed" msgstr "индекс \"%s.%s\" был перестроен" -#: commands/indexcmds.c:4224 commands/indexcmds.c:4243 +#: commands/indexcmds.c:4266 commands/indexcmds.c:4285 #, c-format msgid "%s." msgstr "%s." @@ -10474,7 +10476,7 @@ msgstr "" "CONCURRENTLY нельзя использовать, когда материализованное представление не " "наполнено" -#: commands/matview.c:212 gram.y:18918 +#: commands/matview.c:212 gram.y:18925 #, c-format msgid "%s and %s options cannot be used together" msgstr "параметры %s и %s исключают друг друга" @@ -10820,9 +10822,9 @@ msgstr "атрибут оператора \"%s\" нельзя изменить, #: commands/policy.c:86 commands/policy.c:379 commands/statscmds.c:146 #: commands/tablecmds.c:1740 commands/tablecmds.c:2340 #: commands/tablecmds.c:3702 commands/tablecmds.c:6605 -#: commands/tablecmds.c:9670 commands/tablecmds.c:17813 -#: commands/tablecmds.c:17848 commands/trigger.c:316 commands/trigger.c:1332 -#: commands/trigger.c:1442 rewrite/rewriteDefine.c:268 +#: commands/tablecmds.c:9670 commands/tablecmds.c:17836 +#: commands/tablecmds.c:17871 commands/trigger.c:317 commands/trigger.c:1333 +#: commands/trigger.c:1443 rewrite/rewriteDefine.c:268 #: rewrite/rewriteDefine.c:779 rewrite/rewriteRemove.c:74 #, c-format msgid "permission denied: \"%s\" is a system catalog" @@ -10876,7 +10878,7 @@ msgstr "" "HOLD" #: commands/portalcmds.c:189 commands/portalcmds.c:242 -#: executor/execCurrent.c:70 utils/adt/xml.c:2936 utils/adt/xml.c:3106 +#: executor/execCurrent.c:70 utils/adt/xml.c:2938 utils/adt/xml.c:3108 #, c-format msgid "cursor \"%s\" does not exist" msgstr "курсор \"%s\" не существует" @@ -11325,8 +11327,8 @@ msgstr "" msgid "cannot change ownership of identity sequence" msgstr "сменить владельца последовательности идентификации нельзя" -#: commands/sequence.c:1671 commands/tablecmds.c:14527 -#: commands/tablecmds.c:17222 +#: commands/sequence.c:1671 commands/tablecmds.c:14550 +#: commands/tablecmds.c:17245 #, c-format msgid "Sequence \"%s\" is linked to table \"%s\"." msgstr "Последовательность \"%s\" связана с таблицей \"%s\"." @@ -11476,7 +11478,7 @@ msgid "Only roles with privileges of the \"%s\" role may create subscriptions." msgstr "Создавать подписки могут только роли с правами роли \"%s\"." #: commands/subscriptioncmds.c:758 commands/subscriptioncmds.c:891 -#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1345 +#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1371 #: replication/logical/worker.c:4524 #, c-format msgid "could not connect to the publisher: %s" @@ -11657,7 +11659,7 @@ msgstr "" "Убедитесь, что начальные данные, скопированные из таблиц публикации, " "поступили не из других источников." -#: commands/subscriptioncmds.c:2226 replication/logical/tablesync.c:906 +#: commands/subscriptioncmds.c:2226 replication/logical/tablesync.c:932 #: replication/pgoutput/pgoutput.c:1143 #, c-format msgid "" @@ -11769,7 +11771,7 @@ msgstr "" "Выполните DROP MATERIALIZED VIEW для удаления материализованного " "представления." -#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19846 +#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19877 #: parser/parse_utilcmd.c:2251 #, c-format msgid "index \"%s\" does not exist" @@ -11793,8 +11795,8 @@ msgstr "\"%s\" - это не тип" msgid "Use DROP TYPE to remove a type." msgstr "Выполните DROP TYPE для удаления типа." -#: commands/tablecmds.c:291 commands/tablecmds.c:14366 -#: commands/tablecmds.c:16927 +#: commands/tablecmds.c:291 commands/tablecmds.c:14389 +#: commands/tablecmds.c:16950 #, c-format msgid "foreign table \"%s\" does not exist" msgstr "сторонняя таблица \"%s\" не существует" @@ -11820,7 +11822,7 @@ msgstr "" "в рамках операции с ограничениями по безопасности нельзя создать временную " "таблицу" -#: commands/tablecmds.c:801 commands/tablecmds.c:15786 +#: commands/tablecmds.c:801 commands/tablecmds.c:15809 #, c-format msgid "relation \"%s\" would be inherited from more than once" msgstr "отношение \"%s\" наследуется неоднократно" @@ -11900,7 +11902,7 @@ msgstr "опустошить стороннюю таблицу \"%s\" нельз msgid "cannot truncate temporary tables of other sessions" msgstr "временные таблицы других сеансов нельзя опустошить" -#: commands/tablecmds.c:2606 commands/tablecmds.c:15683 +#: commands/tablecmds.c:2606 commands/tablecmds.c:15706 #, c-format msgid "cannot inherit from partitioned table \"%s\"" msgstr "наследование от секционированной таблицы \"%s\" не допускается" @@ -11925,12 +11927,12 @@ msgstr "" "создать временное отношение в качестве секции постоянного отношения \"%s\" " "нельзя" -#: commands/tablecmds.c:2640 commands/tablecmds.c:15662 +#: commands/tablecmds.c:2640 commands/tablecmds.c:15685 #, c-format msgid "cannot inherit from temporary relation \"%s\"" msgstr "временное отношение \"%s\" не может наследоваться" -#: commands/tablecmds.c:2650 commands/tablecmds.c:15670 +#: commands/tablecmds.c:2650 commands/tablecmds.c:15693 #, c-format msgid "cannot inherit from temporary relation of another session" msgstr "наследование от временного отношения другого сеанса невозможно" @@ -12265,12 +12267,12 @@ msgstr "добавить столбец в типизированную табл msgid "cannot add column to a partition" msgstr "добавить столбец в секцию нельзя" -#: commands/tablecmds.c:7072 commands/tablecmds.c:15901 +#: commands/tablecmds.c:7072 commands/tablecmds.c:15924 #, c-format msgid "child table \"%s\" has different type for column \"%s\"" msgstr "дочерняя таблица \"%s\" имеет другой тип для столбца \"%s\"" -#: commands/tablecmds.c:7078 commands/tablecmds.c:15907 +#: commands/tablecmds.c:7078 commands/tablecmds.c:15930 #, c-format msgid "child table \"%s\" has different collation for column \"%s\"" msgstr "" @@ -12325,7 +12327,7 @@ msgstr "Не указывайте ключевое слово ONLY." #: commands/tablecmds.c:8385 commands/tablecmds.c:8542 #: commands/tablecmds.c:8695 commands/tablecmds.c:8776 #: commands/tablecmds.c:8910 commands/tablecmds.c:12847 -#: commands/tablecmds.c:14389 commands/tablecmds.c:17016 +#: commands/tablecmds.c:14412 commands/tablecmds.c:17039 #, c-format msgid "cannot alter system column \"%s\"" msgstr "системный столбец \"%s\" нельзя изменить" @@ -12752,8 +12754,8 @@ msgstr "изменить тип столбца в типизированной msgid "cannot specify USING when altering type of generated column" msgstr "изменяя тип генерируемого столбца, нельзя указывать USING" -#: commands/tablecmds.c:12858 commands/tablecmds.c:18069 -#: commands/tablecmds.c:18159 commands/trigger.c:656 +#: commands/tablecmds.c:12858 commands/tablecmds.c:18092 +#: commands/tablecmds.c:18182 commands/trigger.c:657 #: rewrite/rewriteHandler.c:941 rewrite/rewriteHandler.c:976 #, c-format msgid "Column \"%s\" is a generated column." @@ -12880,108 +12882,108 @@ msgstr "" "изменить тип столбца, задействованного в заданном для публикации предложении " "WHERE, нельзя" -#: commands/tablecmds.c:14497 commands/tablecmds.c:14509 +#: commands/tablecmds.c:14520 commands/tablecmds.c:14532 #, c-format msgid "cannot change owner of index \"%s\"" msgstr "сменить владельца индекса \"%s\" нельзя" -#: commands/tablecmds.c:14499 commands/tablecmds.c:14511 +#: commands/tablecmds.c:14522 commands/tablecmds.c:14534 #, c-format msgid "Change the ownership of the index's table instead." msgstr "Однако возможно сменить владельца таблицы, содержащей этот индекс." -#: commands/tablecmds.c:14525 +#: commands/tablecmds.c:14548 #, c-format msgid "cannot change owner of sequence \"%s\"" msgstr "сменить владельца последовательности \"%s\" нельзя" -#: commands/tablecmds.c:14550 +#: commands/tablecmds.c:14573 #, c-format msgid "cannot change owner of relation \"%s\"" msgstr "сменить владельца отношения \"%s\" нельзя" -#: commands/tablecmds.c:15017 +#: commands/tablecmds.c:15040 #, c-format msgid "cannot have multiple SET TABLESPACE subcommands" msgstr "в одной инструкции не может быть несколько подкоманд SET TABLESPACE" -#: commands/tablecmds.c:15094 +#: commands/tablecmds.c:15117 #, c-format msgid "cannot set options for relation \"%s\"" msgstr "задать параметры отношения \"%s\" нельзя" -#: commands/tablecmds.c:15128 commands/view.c:440 +#: commands/tablecmds.c:15151 commands/view.c:440 #, c-format msgid "WITH CHECK OPTION is supported only on automatically updatable views" msgstr "" "WITH CHECK OPTION поддерживается только с автообновляемыми представлениями" -#: commands/tablecmds.c:15379 +#: commands/tablecmds.c:15402 #, c-format msgid "only tables, indexes, and materialized views exist in tablespaces" msgstr "" "в табличных пространствах есть только таблицы, индексы и материализованные " "представления" -#: commands/tablecmds.c:15391 +#: commands/tablecmds.c:15414 #, c-format msgid "cannot move relations in to or out of pg_global tablespace" msgstr "перемещать объекты в/из табличного пространства pg_global нельзя" -#: commands/tablecmds.c:15483 +#: commands/tablecmds.c:15506 #, c-format msgid "aborting because lock on relation \"%s.%s\" is not available" msgstr "" "обработка прерывается из-за невозможности заблокировать отношение \"%s.%s\"" -#: commands/tablecmds.c:15499 +#: commands/tablecmds.c:15522 #, c-format msgid "no matching relations in tablespace \"%s\" found" msgstr "в табличном пространстве \"%s\" не найдены подходящие отношения" -#: commands/tablecmds.c:15621 +#: commands/tablecmds.c:15644 #, c-format msgid "cannot change inheritance of typed table" msgstr "изменить наследование типизированной таблицы нельзя" -#: commands/tablecmds.c:15626 commands/tablecmds.c:16126 +#: commands/tablecmds.c:15649 commands/tablecmds.c:16149 #, c-format msgid "cannot change inheritance of a partition" msgstr "изменить наследование секции нельзя" -#: commands/tablecmds.c:15631 +#: commands/tablecmds.c:15654 #, c-format msgid "cannot change inheritance of partitioned table" msgstr "изменить наследование секционированной таблицы нельзя" -#: commands/tablecmds.c:15677 +#: commands/tablecmds.c:15700 #, c-format msgid "cannot inherit to temporary relation of another session" msgstr "наследование для временного отношения другого сеанса невозможно" -#: commands/tablecmds.c:15690 +#: commands/tablecmds.c:15713 #, c-format msgid "cannot inherit from a partition" msgstr "наследование от секции невозможно" -#: commands/tablecmds.c:15712 commands/tablecmds.c:18570 +#: commands/tablecmds.c:15735 commands/tablecmds.c:18593 #, c-format msgid "circular inheritance not allowed" msgstr "циклическое наследование недопустимо" -#: commands/tablecmds.c:15713 commands/tablecmds.c:18571 +#: commands/tablecmds.c:15736 commands/tablecmds.c:18594 #, c-format msgid "\"%s\" is already a child of \"%s\"." msgstr "\"%s\" уже является потомком \"%s\"." -#: commands/tablecmds.c:15726 +#: commands/tablecmds.c:15749 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming an inheritance child" msgstr "" "триггер \"%s\" не позволяет таблице \"%s\" стать потомком в иерархии " "наследования" -#: commands/tablecmds.c:15728 +#: commands/tablecmds.c:15751 #, c-format msgid "" "ROW triggers with transition tables are not supported in inheritance " @@ -12990,34 +12992,34 @@ msgstr "" "Триггеры ROW с переходными таблицами не поддерживаются в иерархиях " "наследования." -#: commands/tablecmds.c:15917 +#: commands/tablecmds.c:15940 #, c-format msgid "column \"%s\" in child table must be marked NOT NULL" msgstr "столбец \"%s\" в дочерней таблице должен быть помечен как NOT NULL" -#: commands/tablecmds.c:15926 +#: commands/tablecmds.c:15949 #, c-format msgid "column \"%s\" in child table must be a generated column" msgstr "столбец \"%s\" в дочерней таблице должен быть генерируемым" -#: commands/tablecmds.c:15930 +#: commands/tablecmds.c:15953 #, c-format msgid "column \"%s\" in child table must not be a generated column" msgstr "столбец \"%s\" в дочерней таблице должен быть не генерируемым" -#: commands/tablecmds.c:15968 +#: commands/tablecmds.c:15991 #, c-format msgid "child table is missing column \"%s\"" msgstr "в дочерней таблице не хватает столбца \"%s\"" -#: commands/tablecmds.c:16049 +#: commands/tablecmds.c:16072 #, c-format msgid "child table \"%s\" has different definition for check constraint \"%s\"" msgstr "" "дочерняя таблица \"%s\" содержит другое определение ограничения-проверки " "\"%s\"" -#: commands/tablecmds.c:16056 +#: commands/tablecmds.c:16079 #, c-format msgid "" "constraint \"%s\" conflicts with non-inherited constraint on child table " @@ -13026,7 +13028,7 @@ msgstr "" "ограничение \"%s\" конфликтует с ненаследуемым ограничением дочерней таблицы " "\"%s\"" -#: commands/tablecmds.c:16066 +#: commands/tablecmds.c:16089 #, c-format msgid "" "constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"" @@ -13034,82 +13036,82 @@ msgstr "" "ограничение \"%s\" конфликтует с непроверенным (NOT VALID) ограничением " "дочерней таблицы \"%s\"" -#: commands/tablecmds.c:16104 +#: commands/tablecmds.c:16127 #, c-format msgid "child table is missing constraint \"%s\"" msgstr "в дочерней таблице не хватает ограничения \"%s\"" -#: commands/tablecmds.c:16190 +#: commands/tablecmds.c:16213 #, c-format msgid "partition \"%s\" already pending detach in partitioned table \"%s.%s\"" msgstr "" "секция \"%s\" уже ожидает отсоединения от секционированной таблицы \"%s.%s\"" -#: commands/tablecmds.c:16219 commands/tablecmds.c:16265 +#: commands/tablecmds.c:16242 commands/tablecmds.c:16288 #, c-format msgid "relation \"%s\" is not a partition of relation \"%s\"" msgstr "отношение \"%s\" не является секцией отношения \"%s\"" -#: commands/tablecmds.c:16271 +#: commands/tablecmds.c:16294 #, c-format msgid "relation \"%s\" is not a parent of relation \"%s\"" msgstr "отношение \"%s\" не является предком отношения \"%s\"" -#: commands/tablecmds.c:16498 +#: commands/tablecmds.c:16521 #, c-format msgid "typed tables cannot inherit" msgstr "типизированные таблицы не могут наследоваться" -#: commands/tablecmds.c:16528 +#: commands/tablecmds.c:16551 #, c-format msgid "table is missing column \"%s\"" msgstr "в таблице не хватает столбца \"%s\"" -#: commands/tablecmds.c:16539 +#: commands/tablecmds.c:16562 #, c-format msgid "table has column \"%s\" where type requires \"%s\"" msgstr "таблица содержит столбец \"%s\", тогда как тип требует \"%s\"" -#: commands/tablecmds.c:16548 +#: commands/tablecmds.c:16571 #, c-format msgid "table \"%s\" has different type for column \"%s\"" msgstr "таблица \"%s\" содержит столбец \"%s\" другого типа" -#: commands/tablecmds.c:16562 +#: commands/tablecmds.c:16585 #, c-format msgid "table has extra column \"%s\"" msgstr "таблица содержит лишний столбец \"%s\"" -#: commands/tablecmds.c:16614 +#: commands/tablecmds.c:16637 #, c-format msgid "\"%s\" is not a typed table" msgstr "\"%s\" - это не типизированная таблица" -#: commands/tablecmds.c:16788 +#: commands/tablecmds.c:16811 #, c-format msgid "cannot use non-unique index \"%s\" as replica identity" msgstr "" "для идентификации реплики нельзя использовать неуникальный индекс \"%s\"" -#: commands/tablecmds.c:16794 +#: commands/tablecmds.c:16817 #, c-format msgid "cannot use non-immediate index \"%s\" as replica identity" msgstr "" "для идентификации реплики нельзя использовать не непосредственный индекс " "\"%s\"" -#: commands/tablecmds.c:16800 +#: commands/tablecmds.c:16823 #, c-format msgid "cannot use expression index \"%s\" as replica identity" msgstr "" "для идентификации реплики нельзя использовать индекс с выражением \"%s\"" -#: commands/tablecmds.c:16806 +#: commands/tablecmds.c:16829 #, c-format msgid "cannot use partial index \"%s\" as replica identity" msgstr "для идентификации реплики нельзя использовать частичный индекс \"%s\"" -#: commands/tablecmds.c:16823 +#: commands/tablecmds.c:16846 #, c-format msgid "" "index \"%s\" cannot be used as replica identity because column %d is a " @@ -13118,7 +13120,7 @@ msgstr "" "индекс \"%s\" нельзя использовать для идентификации реплики, так как столбец " "%d - системный" -#: commands/tablecmds.c:16830 +#: commands/tablecmds.c:16853 #, c-format msgid "" "index \"%s\" cannot be used as replica identity because column \"%s\" is " @@ -13127,13 +13129,13 @@ msgstr "" "индекс \"%s\" нельзя использовать для идентификации реплики, так как столбец " "\"%s\" допускает NULL" -#: commands/tablecmds.c:17082 +#: commands/tablecmds.c:17105 #, c-format msgid "cannot change logged status of table \"%s\" because it is temporary" msgstr "" "изменить состояние журналирования таблицы %s нельзя, так как она временная" -#: commands/tablecmds.c:17106 +#: commands/tablecmds.c:17129 #, c-format msgid "" "cannot change table \"%s\" to unlogged because it is part of a publication" @@ -13141,12 +13143,12 @@ msgstr "" "таблицу \"%s\" нельзя сделать нежурналируемой, так как она включена в " "публикацию" -#: commands/tablecmds.c:17108 +#: commands/tablecmds.c:17131 #, c-format msgid "Unlogged relations cannot be replicated." msgstr "Нежурналируемые отношения не поддерживают репликацию." -#: commands/tablecmds.c:17153 +#: commands/tablecmds.c:17176 #, c-format msgid "" "could not change table \"%s\" to logged because it references unlogged table " @@ -13155,7 +13157,7 @@ msgstr "" "не удалось сделать таблицу \"%s\" журналируемой, так как она ссылается на " "нежурналируемую таблицу \"%s\"" -#: commands/tablecmds.c:17163 +#: commands/tablecmds.c:17186 #, c-format msgid "" "could not change table \"%s\" to unlogged because it references logged table " @@ -13164,91 +13166,91 @@ msgstr "" "не удалось сделать таблицу \"%s\" нежурналируемой, так как она ссылается на " "журналируемую таблицу \"%s\"" -#: commands/tablecmds.c:17221 +#: commands/tablecmds.c:17244 #, c-format msgid "cannot move an owned sequence into another schema" msgstr "переместить последовательность с владельцем в другую схему нельзя" -#: commands/tablecmds.c:17329 +#: commands/tablecmds.c:17352 #, c-format msgid "relation \"%s\" already exists in schema \"%s\"" msgstr "отношение \"%s\" уже существует в схеме \"%s\"" -#: commands/tablecmds.c:17754 +#: commands/tablecmds.c:17777 #, c-format msgid "\"%s\" is not a table or materialized view" msgstr "\"%s\" - это не таблица и не материализованное представление" -#: commands/tablecmds.c:17907 +#: commands/tablecmds.c:17930 #, c-format msgid "\"%s\" is not a composite type" msgstr "\"%s\" - это не составной тип" -#: commands/tablecmds.c:17937 +#: commands/tablecmds.c:17960 #, c-format msgid "cannot change schema of index \"%s\"" msgstr "сменить схему индекса \"%s\" нельзя" -#: commands/tablecmds.c:17939 commands/tablecmds.c:17953 +#: commands/tablecmds.c:17962 commands/tablecmds.c:17976 #, c-format msgid "Change the schema of the table instead." msgstr "Однако возможно сменить владельца таблицы." -#: commands/tablecmds.c:17943 +#: commands/tablecmds.c:17966 #, c-format msgid "cannot change schema of composite type \"%s\"" msgstr "сменить схему составного типа \"%s\" нельзя" -#: commands/tablecmds.c:17951 +#: commands/tablecmds.c:17974 #, c-format msgid "cannot change schema of TOAST table \"%s\"" msgstr "сменить схему TOAST-таблицы \"%s\" нельзя" -#: commands/tablecmds.c:17983 +#: commands/tablecmds.c:18006 #, c-format msgid "cannot use \"list\" partition strategy with more than one column" msgstr "стратегия секционирования по списку не поддерживает несколько столбцов" -#: commands/tablecmds.c:18049 +#: commands/tablecmds.c:18072 #, c-format msgid "column \"%s\" named in partition key does not exist" msgstr "столбец \"%s\", упомянутый в ключе секционирования, не существует" -#: commands/tablecmds.c:18057 +#: commands/tablecmds.c:18080 #, c-format msgid "cannot use system column \"%s\" in partition key" msgstr "системный столбец \"%s\" нельзя использовать в ключе секционирования" -#: commands/tablecmds.c:18068 commands/tablecmds.c:18158 +#: commands/tablecmds.c:18091 commands/tablecmds.c:18181 #, c-format msgid "cannot use generated column in partition key" msgstr "генерируемый столбец нельзя использовать в ключе секционирования" -#: commands/tablecmds.c:18141 +#: commands/tablecmds.c:18164 #, c-format msgid "partition key expressions cannot contain system column references" msgstr "" "выражения ключей секционирования не могут содержать ссылки на системный " "столбец" -#: commands/tablecmds.c:18188 +#: commands/tablecmds.c:18211 #, c-format msgid "functions in partition key expression must be marked IMMUTABLE" msgstr "" "функции в выражении ключа секционирования должны быть помечены как IMMUTABLE" -#: commands/tablecmds.c:18197 +#: commands/tablecmds.c:18220 #, c-format msgid "cannot use constant expression as partition key" msgstr "" "в качестве ключа секционирования нельзя использовать константное выражение" -#: commands/tablecmds.c:18218 +#: commands/tablecmds.c:18241 #, c-format msgid "could not determine which collation to use for partition expression" msgstr "не удалось определить правило сортировки для выражения секционирования" -#: commands/tablecmds.c:18253 +#: commands/tablecmds.c:18276 #, c-format msgid "" "You must specify a hash operator class or define a default hash operator " @@ -13257,7 +13259,7 @@ msgstr "" "Вы должны указать класс операторов хеширования или определить класс " "операторов хеширования по умолчанию для этого типа данных." -#: commands/tablecmds.c:18259 +#: commands/tablecmds.c:18282 #, c-format msgid "" "You must specify a btree operator class or define a default btree operator " @@ -13266,27 +13268,27 @@ msgstr "" "Вы должны указать класс операторов B-дерева или определить класс операторов " "B-дерева по умолчанию для этого типа данных." -#: commands/tablecmds.c:18510 +#: commands/tablecmds.c:18533 #, c-format msgid "\"%s\" is already a partition" msgstr "\"%s\" уже является секцией" -#: commands/tablecmds.c:18516 +#: commands/tablecmds.c:18539 #, c-format msgid "cannot attach a typed table as partition" msgstr "подключить типизированную таблицу в качестве секции нельзя" -#: commands/tablecmds.c:18532 +#: commands/tablecmds.c:18555 #, c-format msgid "cannot attach inheritance child as partition" msgstr "подключить потомок в иерархии наследования в качестве секции нельзя" -#: commands/tablecmds.c:18546 +#: commands/tablecmds.c:18569 #, c-format msgid "cannot attach inheritance parent as partition" msgstr "подключить родитель в иерархии наследования в качестве секции нельзя" -#: commands/tablecmds.c:18580 +#: commands/tablecmds.c:18603 #, c-format msgid "" "cannot attach a temporary relation as partition of permanent relation \"%s\"" @@ -13294,7 +13296,7 @@ msgstr "" "подключить временное отношение в качестве секции постоянного отношения " "\"%s\" нельзя" -#: commands/tablecmds.c:18588 +#: commands/tablecmds.c:18611 #, c-format msgid "" "cannot attach a permanent relation as partition of temporary relation \"%s\"" @@ -13302,102 +13304,102 @@ msgstr "" "подключить постоянное отношение в качестве секции временного отношения " "\"%s\" нельзя" -#: commands/tablecmds.c:18596 +#: commands/tablecmds.c:18619 #, c-format msgid "cannot attach as partition of temporary relation of another session" msgstr "подключить секцию к временному отношению в другом сеансе нельзя" -#: commands/tablecmds.c:18603 +#: commands/tablecmds.c:18626 #, c-format msgid "cannot attach temporary relation of another session as partition" msgstr "" "подключить временное отношение из другого сеанса в качестве секции нельзя" -#: commands/tablecmds.c:18623 +#: commands/tablecmds.c:18646 #, c-format msgid "table \"%s\" being attached contains an identity column \"%s\"" msgstr "присоединяемая таблица \"%s\" содержит столбец идентификации \"%s\"" -#: commands/tablecmds.c:18625 +#: commands/tablecmds.c:18648 #, c-format msgid "The new partition may not contain an identity column." msgstr "Новая секция не может содержать столбец идентификации." -#: commands/tablecmds.c:18633 +#: commands/tablecmds.c:18656 #, c-format msgid "table \"%s\" contains column \"%s\" not found in parent \"%s\"" msgstr "" "таблица \"%s\" содержит столбец \"%s\", отсутствующий в родителе \"%s\"" -#: commands/tablecmds.c:18636 +#: commands/tablecmds.c:18659 #, c-format msgid "The new partition may contain only the columns present in parent." msgstr "" "Новая секция может содержать только столбцы, имеющиеся в родительской " "таблице." -#: commands/tablecmds.c:18648 +#: commands/tablecmds.c:18671 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming a partition" msgstr "триггер \"%s\" не позволяет сделать таблицу \"%s\" секцией" -#: commands/tablecmds.c:18650 +#: commands/tablecmds.c:18673 #, c-format msgid "ROW triggers with transition tables are not supported on partitions." msgstr "Триггеры ROW с переходными таблицами для секций не поддерживаются." -#: commands/tablecmds.c:18826 +#: commands/tablecmds.c:18849 #, c-format msgid "" "cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"" msgstr "" "нельзя присоединить стороннюю таблицу \"%s\" в качестве секции таблицы \"%s\"" -#: commands/tablecmds.c:18829 +#: commands/tablecmds.c:18852 #, c-format msgid "Partitioned table \"%s\" contains unique indexes." msgstr "Секционированная таблица \"%s\" содержит уникальные индексы." -#: commands/tablecmds.c:19151 +#: commands/tablecmds.c:19174 #, c-format msgid "cannot detach partitions concurrently when a default partition exists" msgstr "" "секции нельзя отсоединять в режиме CONCURRENTLY, когда существует секция по " "умолчанию" -#: commands/tablecmds.c:19260 +#: commands/tablecmds.c:19283 #, c-format msgid "partitioned table \"%s\" was removed concurrently" msgstr "секционированная таблица \"%s\" была параллельно удалена" -#: commands/tablecmds.c:19266 +#: commands/tablecmds.c:19289 #, c-format msgid "partition \"%s\" was removed concurrently" msgstr "секция \"%s\" была параллельно удалена" -#: commands/tablecmds.c:19880 commands/tablecmds.c:19900 -#: commands/tablecmds.c:19921 commands/tablecmds.c:19940 -#: commands/tablecmds.c:19982 +#: commands/tablecmds.c:19911 commands/tablecmds.c:19931 +#: commands/tablecmds.c:19952 commands/tablecmds.c:19971 +#: commands/tablecmds.c:20013 #, c-format msgid "cannot attach index \"%s\" as a partition of index \"%s\"" msgstr "нельзя присоединить индекс \"%s\" в качестве секции индекса \"%s\"" -#: commands/tablecmds.c:19883 +#: commands/tablecmds.c:19914 #, c-format msgid "Index \"%s\" is already attached to another index." msgstr "Индекс \"%s\" уже присоединён к другому индексу." -#: commands/tablecmds.c:19903 +#: commands/tablecmds.c:19934 #, c-format msgid "Index \"%s\" is not an index on any partition of table \"%s\"." msgstr "Индекс \"%s\" не является индексом какой-либо секции таблицы \"%s\"." -#: commands/tablecmds.c:19924 +#: commands/tablecmds.c:19955 #, c-format msgid "The index definitions do not match." msgstr "Определения индексов не совпадают." -#: commands/tablecmds.c:19943 +#: commands/tablecmds.c:19974 #, c-format msgid "" "The index \"%s\" belongs to a constraint in table \"%s\" but no constraint " @@ -13406,27 +13408,27 @@ msgstr "" "Индекс \"%s\" принадлежит ограничению в таблице \"%s\", но для индекса " "\"%s\" ограничения нет." -#: commands/tablecmds.c:19985 +#: commands/tablecmds.c:20016 #, c-format msgid "Another index is already attached for partition \"%s\"." msgstr "К секции \"%s\" уже присоединён другой индекс." -#: commands/tablecmds.c:20221 +#: commands/tablecmds.c:20252 #, c-format msgid "column data type %s does not support compression" msgstr "тим данных столбца %s не поддерживает сжатие" -#: commands/tablecmds.c:20228 +#: commands/tablecmds.c:20259 #, c-format msgid "invalid compression method \"%s\"" msgstr "неверный метод сжатия \"%s\"" -#: commands/tablecmds.c:20254 +#: commands/tablecmds.c:20285 #, c-format msgid "invalid storage type \"%s\"" msgstr "неверный тип хранилища \"%s\"" -#: commands/tablecmds.c:20264 +#: commands/tablecmds.c:20295 #, c-format msgid "column data type %s can only have storage PLAIN" msgstr "тип данных столбца %s совместим только с хранилищем PLAIN" @@ -13556,22 +13558,22 @@ msgstr "удалить каталоги табличного пространс msgid "You can remove the directories manually if necessary." msgstr "При необходимости вы можете удалить их вручную." -#: commands/trigger.c:225 commands/trigger.c:236 +#: commands/trigger.c:226 commands/trigger.c:237 #, c-format msgid "\"%s\" is a table" msgstr "\"%s\" - это таблица" -#: commands/trigger.c:227 commands/trigger.c:238 +#: commands/trigger.c:228 commands/trigger.c:239 #, c-format msgid "Tables cannot have INSTEAD OF triggers." msgstr "У таблиц не может быть триггеров INSTEAD OF." -#: commands/trigger.c:259 +#: commands/trigger.c:260 #, c-format msgid "\"%s\" is a partitioned table" msgstr "\"%s\" - секционированная таблица" -#: commands/trigger.c:261 +#: commands/trigger.c:262 #, c-format msgid "" "ROW triggers with transition tables are not supported on partitioned tables." @@ -13579,88 +13581,88 @@ msgstr "" "Триггеры ROW с переходными таблицами не поддерживаются в секционированных " "таблицах." -#: commands/trigger.c:273 commands/trigger.c:280 commands/trigger.c:444 +#: commands/trigger.c:274 commands/trigger.c:281 commands/trigger.c:445 #, c-format msgid "\"%s\" is a view" msgstr "\"%s\" - это представление" -#: commands/trigger.c:275 +#: commands/trigger.c:276 #, c-format msgid "Views cannot have row-level BEFORE or AFTER triggers." msgstr "У представлений не может быть строковых триггеров BEFORE/AFTER." -#: commands/trigger.c:282 +#: commands/trigger.c:283 #, c-format msgid "Views cannot have TRUNCATE triggers." msgstr "У представлений не может быть триггеров TRUNCATE." -#: commands/trigger.c:290 commands/trigger.c:302 commands/trigger.c:437 +#: commands/trigger.c:291 commands/trigger.c:303 commands/trigger.c:438 #, c-format msgid "\"%s\" is a foreign table" msgstr "\"%s\" - сторонняя таблица" -#: commands/trigger.c:292 +#: commands/trigger.c:293 #, c-format msgid "Foreign tables cannot have INSTEAD OF triggers." msgstr "У сторонних таблиц не может быть триггеров INSTEAD OF." -#: commands/trigger.c:304 +#: commands/trigger.c:305 #, c-format msgid "Foreign tables cannot have constraint triggers." msgstr "У сторонних таблиц не может быть ограничивающих триггеров." -#: commands/trigger.c:309 commands/trigger.c:1325 commands/trigger.c:1432 +#: commands/trigger.c:310 commands/trigger.c:1326 commands/trigger.c:1433 #, c-format msgid "relation \"%s\" cannot have triggers" msgstr "в отношении \"%s\" не может быть триггеров" -#: commands/trigger.c:380 +#: commands/trigger.c:381 #, c-format msgid "TRUNCATE FOR EACH ROW triggers are not supported" msgstr "триггеры TRUNCATE FOR EACH ROW не поддерживаются" -#: commands/trigger.c:388 +#: commands/trigger.c:389 #, c-format msgid "INSTEAD OF triggers must be FOR EACH ROW" msgstr "триггеры INSTEAD OF должны иметь тип FOR EACH ROW" -#: commands/trigger.c:392 +#: commands/trigger.c:393 #, c-format msgid "INSTEAD OF triggers cannot have WHEN conditions" msgstr "триггеры INSTEAD OF несовместимы с условиями WHEN" -#: commands/trigger.c:396 +#: commands/trigger.c:397 #, c-format msgid "INSTEAD OF triggers cannot have column lists" msgstr "для триггеров INSTEAD OF нельзя задать список столбцов" -#: commands/trigger.c:425 +#: commands/trigger.c:426 #, c-format msgid "ROW variable naming in the REFERENCING clause is not supported" msgstr "" "указание переменной типа кортеж в предложении REFERENCING не поддерживается" -#: commands/trigger.c:426 +#: commands/trigger.c:427 #, c-format msgid "Use OLD TABLE or NEW TABLE for naming transition tables." msgstr "Используйте OLD TABLE или NEW TABLE для именования переходных таблиц." -#: commands/trigger.c:439 +#: commands/trigger.c:440 #, c-format msgid "Triggers on foreign tables cannot have transition tables." msgstr "Триггеры сторонних таблиц не могут использовать переходные таблицы." -#: commands/trigger.c:446 +#: commands/trigger.c:447 #, c-format msgid "Triggers on views cannot have transition tables." msgstr "Триггеры представлений не могут использовать переходные таблицы." -#: commands/trigger.c:462 +#: commands/trigger.c:463 #, c-format msgid "ROW triggers with transition tables are not supported on partitions" msgstr "триггеры ROW с переходными таблицами для секций не поддерживаются" -#: commands/trigger.c:466 +#: commands/trigger.c:467 #, c-format msgid "" "ROW triggers with transition tables are not supported on inheritance children" @@ -13668,17 +13670,17 @@ msgstr "" "триггеры ROW с переходными таблицами для потомков в иерархии наследования не " "поддерживаются" -#: commands/trigger.c:472 +#: commands/trigger.c:473 #, c-format msgid "transition table name can only be specified for an AFTER trigger" msgstr "имя переходной таблицы можно задать только для триггера AFTER" -#: commands/trigger.c:477 +#: commands/trigger.c:478 #, c-format msgid "TRUNCATE triggers with transition tables are not supported" msgstr "триггеры TRUNCATE с переходными таблицами не поддерживаются" -#: commands/trigger.c:494 +#: commands/trigger.c:495 #, c-format msgid "" "transition tables cannot be specified for triggers with more than one event" @@ -13686,127 +13688,127 @@ msgstr "" "переходные таблицы нельзя задать для триггеров, назначаемых для нескольких " "событий" -#: commands/trigger.c:505 +#: commands/trigger.c:506 #, c-format msgid "transition tables cannot be specified for triggers with column lists" msgstr "переходные таблицы нельзя задать для триггеров со списками столбцов" -#: commands/trigger.c:522 +#: commands/trigger.c:523 #, c-format msgid "NEW TABLE can only be specified for an INSERT or UPDATE trigger" msgstr "NEW TABLE можно задать только для триггеров INSERT или UPDATE" -#: commands/trigger.c:527 +#: commands/trigger.c:528 #, c-format msgid "NEW TABLE cannot be specified multiple times" msgstr "NEW TABLE нельзя задать несколько раз" -#: commands/trigger.c:537 +#: commands/trigger.c:538 #, c-format msgid "OLD TABLE can only be specified for a DELETE or UPDATE trigger" msgstr "OLD TABLE можно задать только для триггеров DELETE или UPDATE" -#: commands/trigger.c:542 +#: commands/trigger.c:543 #, c-format msgid "OLD TABLE cannot be specified multiple times" msgstr "OLD TABLE нельзя задать несколько раз" -#: commands/trigger.c:552 +#: commands/trigger.c:553 #, c-format msgid "OLD TABLE name and NEW TABLE name cannot be the same" msgstr "имя OLD TABLE не должно совпадать с именем NEW TABLE" -#: commands/trigger.c:616 commands/trigger.c:629 +#: commands/trigger.c:617 commands/trigger.c:630 #, c-format msgid "statement trigger's WHEN condition cannot reference column values" msgstr "" "в условии WHEN для операторного триггера нельзя ссылаться на значения " "столбцов" -#: commands/trigger.c:621 +#: commands/trigger.c:622 #, c-format msgid "INSERT trigger's WHEN condition cannot reference OLD values" msgstr "в условии WHEN для триггера INSERT нельзя ссылаться на значения OLD" -#: commands/trigger.c:634 +#: commands/trigger.c:635 #, c-format msgid "DELETE trigger's WHEN condition cannot reference NEW values" msgstr "в условии WHEN для триггера DELETE нельзя ссылаться на значения NEW" -#: commands/trigger.c:639 +#: commands/trigger.c:640 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW system columns" msgstr "" "в условии WHEN для триггера BEFORE нельзя ссылаться на системные столбцы NEW" -#: commands/trigger.c:647 commands/trigger.c:655 +#: commands/trigger.c:648 commands/trigger.c:656 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW generated columns" msgstr "" "в условии WHEN для триггера BEFORE нельзя ссылаться на генерируемые столбцы " "NEW" -#: commands/trigger.c:648 +#: commands/trigger.c:649 #, c-format msgid "A whole-row reference is used and the table contains generated columns." msgstr "" "Используется ссылка на всю строку таблицы, а таблица содержит генерируемые " "столбцы." -#: commands/trigger.c:763 commands/trigger.c:1607 +#: commands/trigger.c:764 commands/trigger.c:1608 #, c-format msgid "trigger \"%s\" for relation \"%s\" already exists" msgstr "триггер \"%s\" для отношения \"%s\" уже существует" -#: commands/trigger.c:776 +#: commands/trigger.c:777 #, c-format msgid "trigger \"%s\" for relation \"%s\" is an internal or a child trigger" msgstr "триггер \"%s\" для отношения \"%s\" является внутренним или дочерним" -#: commands/trigger.c:795 +#: commands/trigger.c:796 #, c-format msgid "trigger \"%s\" for relation \"%s\" is a constraint trigger" msgstr "" "триггер \"%s\" для отношения \"%s\" является триггером, реализующим " "ограничение" -#: commands/trigger.c:1397 commands/trigger.c:1550 commands/trigger.c:1831 +#: commands/trigger.c:1398 commands/trigger.c:1551 commands/trigger.c:1832 #, c-format msgid "trigger \"%s\" for table \"%s\" does not exist" msgstr "триггер \"%s\" для таблицы \"%s\" не существует" -#: commands/trigger.c:1522 +#: commands/trigger.c:1523 #, c-format msgid "cannot rename trigger \"%s\" on table \"%s\"" msgstr "переименовать триггер \"%s\" в таблице \"%s\" нельзя" -#: commands/trigger.c:1524 +#: commands/trigger.c:1525 #, c-format msgid "Rename the trigger on the partitioned table \"%s\" instead." msgstr "Однако можно переименовать триггер в секционированной таблице \"%s\"." -#: commands/trigger.c:1624 +#: commands/trigger.c:1625 #, c-format msgid "renamed trigger \"%s\" on relation \"%s\"" msgstr "триггер \"%s\" в отношении \"%s\" переименован" -#: commands/trigger.c:1770 +#: commands/trigger.c:1771 #, c-format msgid "permission denied: \"%s\" is a system trigger" msgstr "нет доступа: \"%s\" - это системный триггер" -#: commands/trigger.c:2379 +#: commands/trigger.c:2382 #, c-format msgid "trigger function %u returned null value" msgstr "триггерная функция %u вернула значение NULL" -#: commands/trigger.c:2439 commands/trigger.c:2657 commands/trigger.c:2910 -#: commands/trigger.c:3263 +#: commands/trigger.c:2442 commands/trigger.c:2669 commands/trigger.c:2959 +#: commands/trigger.c:3349 #, c-format msgid "BEFORE STATEMENT trigger cannot return a value" msgstr "триггер BEFORE STATEMENT не может возвращать значение" -#: commands/trigger.c:2515 +#: commands/trigger.c:2518 #, c-format msgid "" "moving row to another partition during a BEFORE FOR EACH ROW trigger is not " @@ -13814,7 +13816,7 @@ msgid "" msgstr "" "в триггере BEFORE FOR EACH ROW нельзя перемещать строку в другую секцию" -#: commands/trigger.c:2516 +#: commands/trigger.c:2519 #, c-format msgid "" "Before executing trigger \"%s\", the row was to be in partition \"%s.%s\"." @@ -13822,10 +13824,15 @@ msgstr "" "До выполнения триггера \"%s\" строка должна была находиться в секции \"%s." "%s\"." -#: commands/trigger.c:3341 executor/nodeModifyTable.c:1561 -#: executor/nodeModifyTable.c:1635 executor/nodeModifyTable.c:2397 -#: executor/nodeModifyTable.c:2488 executor/nodeModifyTable.c:3152 -#: executor/nodeModifyTable.c:3322 +#: commands/trigger.c:2548 commands/trigger.c:2827 commands/trigger.c:3190 +#, c-format +msgid "cannot collect transition tuples from child foreign tables" +msgstr "собрать переходные кортежи из дочерних сторонних таблиц нельзя" + +#: commands/trigger.c:3428 executor/nodeModifyTable.c:1563 +#: executor/nodeModifyTable.c:1637 executor/nodeModifyTable.c:2400 +#: executor/nodeModifyTable.c:2491 executor/nodeModifyTable.c:3155 +#: executor/nodeModifyTable.c:3325 #, c-format msgid "" "Consider using an AFTER trigger instead of a BEFORE trigger to propagate " @@ -13834,34 +13841,34 @@ msgstr "" "Возможно, для распространения изменений в другие строки следует использовать " "триггер AFTER вместо BEFORE." -#: commands/trigger.c:3382 executor/nodeLockRows.c:228 -#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:334 -#: executor/nodeModifyTable.c:1577 executor/nodeModifyTable.c:2414 -#: executor/nodeModifyTable.c:2638 +#: commands/trigger.c:3470 executor/nodeLockRows.c:228 +#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:335 +#: executor/nodeModifyTable.c:1579 executor/nodeModifyTable.c:2417 +#: executor/nodeModifyTable.c:2641 #, c-format msgid "could not serialize access due to concurrent update" msgstr "не удалось сериализовать доступ из-за параллельного изменения" -#: commands/trigger.c:3390 executor/nodeModifyTable.c:1667 -#: executor/nodeModifyTable.c:2505 executor/nodeModifyTable.c:2662 -#: executor/nodeModifyTable.c:3170 +#: commands/trigger.c:3478 executor/nodeModifyTable.c:1669 +#: executor/nodeModifyTable.c:2508 executor/nodeModifyTable.c:2665 +#: executor/nodeModifyTable.c:3173 #, c-format msgid "could not serialize access due to concurrent delete" msgstr "не удалось сериализовать доступ из-за параллельного удаления" -#: commands/trigger.c:4599 +#: commands/trigger.c:4687 #, c-format msgid "cannot fire deferred trigger within security-restricted operation" msgstr "" "в рамках операции с ограничениями по безопасности нельзя вызвать отложенный " "триггер" -#: commands/trigger.c:5780 +#: commands/trigger.c:5868 #, c-format msgid "constraint \"%s\" is not deferrable" msgstr "ограничение \"%s\" не является откладываемым" -#: commands/trigger.c:5803 +#: commands/trigger.c:5891 #, c-format msgid "constraint \"%s\" does not exist" msgstr "ограничение \"%s\" не существует" @@ -14372,7 +14379,7 @@ msgid "" msgstr "Создавать роли с атрибутом %s могут только роли с атрибутом %s." #: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 -#: utils/adt/acl.c:5574 utils/adt/acl.c:5580 gram.y:17310 gram.y:17356 +#: utils/adt/acl.c:5591 utils/adt/acl.c:5597 gram.y:17317 gram.y:17363 #, c-format msgid "role name \"%s\" is reserved" msgstr "имя роли \"%s\" зарезервировано" @@ -14481,8 +14488,8 @@ msgstr "использовать специальную роль в DROP ROLE н #: commands/user.c:1135 commands/user.c:1357 commands/variable.c:851 #: commands/variable.c:854 commands/variable.c:971 commands/variable.c:974 -#: utils/adt/acl.c:365 utils/adt/acl.c:385 utils/adt/acl.c:5429 -#: utils/adt/acl.c:5477 utils/adt/acl.c:5505 utils/adt/acl.c:5524 +#: utils/adt/acl.c:382 utils/adt/acl.c:402 utils/adt/acl.c:5446 +#: utils/adt/acl.c:5494 utils/adt/acl.c:5522 utils/adt/acl.c:5541 #: utils/adt/regproc.c:1571 utils/init/miscinit.c:799 #, c-format msgid "role \"%s\" does not exist" @@ -14699,127 +14706,127 @@ msgid "" msgstr "" "Только роли с правами роли \"%s\" могут отзывать права от имени этой роли." -#: commands/user.c:2491 utils/adt/acl.c:1324 +#: commands/user.c:2491 utils/adt/acl.c:1341 #, c-format msgid "dependent privileges exist" msgstr "существуют зависимые права" -#: commands/user.c:2492 utils/adt/acl.c:1325 +#: commands/user.c:2492 utils/adt/acl.c:1342 #, c-format msgid "Use CASCADE to revoke them too." msgstr "Используйте CASCADE, чтобы отозвать и их." -#: commands/vacuum.c:134 +#: commands/vacuum.c:135 #, c-format msgid "\"vacuum_buffer_usage_limit\" must be 0 or between %d kB and %d kB" msgstr "" "значение \"vacuum_buffer_usage_limit\" должно быть нулевым или лежать в " "диапазоне от %d kB до %d kB" -#: commands/vacuum.c:209 +#: commands/vacuum.c:210 #, c-format msgid "BUFFER_USAGE_LIMIT option must be 0 or between %d kB and %d kB" msgstr "" "параметр BUFFER_USAGE_LIMIT должен быть нулевым или лежать в диапазоне от %d " "kB до %d kB" -#: commands/vacuum.c:219 +#: commands/vacuum.c:220 #, c-format msgid "unrecognized ANALYZE option \"%s\"" msgstr "нераспознанный параметр ANALYZE: \"%s\"" -#: commands/vacuum.c:259 +#: commands/vacuum.c:260 #, c-format msgid "parallel option requires a value between 0 and %d" msgstr "для параметра parallel требуется значение от 0 до %d" -#: commands/vacuum.c:271 +#: commands/vacuum.c:272 #, c-format msgid "parallel workers for vacuum must be between 0 and %d" msgstr "" "число параллельных исполнителей для выполнения очистки должно быть от 0 до %d" -#: commands/vacuum.c:292 +#: commands/vacuum.c:293 #, c-format msgid "unrecognized VACUUM option \"%s\"" msgstr "нераспознанный параметр VACUUM: \"%s\"" -#: commands/vacuum.c:318 +#: commands/vacuum.c:319 #, c-format msgid "VACUUM FULL cannot be performed in parallel" msgstr "VACUUM FULL нельзя выполнять в параллельном режиме" -#: commands/vacuum.c:329 +#: commands/vacuum.c:330 #, c-format msgid "BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL" msgstr "параметр BUFFER_USAGE_LIMIT нельзя задавать для VACUUM FULL" -#: commands/vacuum.c:343 +#: commands/vacuum.c:344 #, c-format msgid "ANALYZE option must be specified when a column list is provided" msgstr "если задаётся список столбцов, необходимо указать ANALYZE" -#: commands/vacuum.c:355 +#: commands/vacuum.c:356 #, c-format msgid "VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL" msgstr "Параметр VACUUM DISABLE_PAGE_SKIPPING нельзя использовать с FULL" -#: commands/vacuum.c:362 +#: commands/vacuum.c:363 #, c-format msgid "PROCESS_TOAST required with VACUUM FULL" msgstr "VACUUM FULL работает только с PROCESS_TOAST" -#: commands/vacuum.c:371 +#: commands/vacuum.c:372 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with a list of tables" msgstr "ONLY_DATABASE_STATS нельзя задавать со списком таблиц" -#: commands/vacuum.c:380 +#: commands/vacuum.c:381 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with other VACUUM options" msgstr "ONLY_DATABASE_STATS нельзя задавать с другими параметрами VACUUM" -#: commands/vacuum.c:515 +#: commands/vacuum.c:516 #, c-format msgid "%s cannot be executed from VACUUM or ANALYZE" msgstr "%s нельзя выполнить в ходе VACUUM или ANALYZE" -#: commands/vacuum.c:732 +#: commands/vacuum.c:741 #, c-format msgid "permission denied to vacuum \"%s\", skipping it" msgstr "нет доступа для очистки отношения \"%s\", оно пропускается" -#: commands/vacuum.c:745 +#: commands/vacuum.c:754 #, c-format msgid "permission denied to analyze \"%s\", skipping it" msgstr "нет доступа для анализа отношения \"%s\", оно пропускается" -#: commands/vacuum.c:823 commands/vacuum.c:920 +#: commands/vacuum.c:832 commands/vacuum.c:929 #, c-format msgid "skipping vacuum of \"%s\" --- lock not available" msgstr "очистка \"%s\" пропускается --- блокировка недоступна" -#: commands/vacuum.c:828 +#: commands/vacuum.c:837 #, c-format msgid "skipping vacuum of \"%s\" --- relation no longer exists" msgstr "очистка \"%s\" пропускается --- это отношение более не существует" -#: commands/vacuum.c:844 commands/vacuum.c:925 +#: commands/vacuum.c:853 commands/vacuum.c:934 #, c-format msgid "skipping analyze of \"%s\" --- lock not available" msgstr "анализ \"%s\" пропускается --- блокировка недоступна" -#: commands/vacuum.c:849 +#: commands/vacuum.c:858 #, c-format msgid "skipping analyze of \"%s\" --- relation no longer exists" msgstr "анализ \"%s\" пропускается --- это отношение более не существует" -#: commands/vacuum.c:1141 +#: commands/vacuum.c:1150 #, c-format msgid "cutoff for removing and freezing tuples is far in the past" msgstr "момент отсечки для удаления и замораживания кортежей далеко в прошлом" -#: commands/vacuum.c:1142 commands/vacuum.c:1147 +#: commands/vacuum.c:1151 commands/vacuum.c:1156 #, c-format msgid "" "Close open transactions soon to avoid wraparound problems.\n" @@ -14831,42 +14838,42 @@ msgstr "" "Возможно, вам также придётся зафиксировать или откатить старые " "подготовленные транзакции и удалить неиспользуемые слоты репликации." -#: commands/vacuum.c:1146 +#: commands/vacuum.c:1155 #, c-format msgid "cutoff for freezing multixacts is far in the past" msgstr "момент отсечки для замораживания мультитранзакций далеко в прошлом" -#: commands/vacuum.c:1902 +#: commands/vacuum.c:1911 #, c-format msgid "some databases have not been vacuumed in over 2 billion transactions" msgstr "" "есть базы данных, которые не очищались на протяжении более чем 2 миллиардов " "транзакций" -#: commands/vacuum.c:1903 +#: commands/vacuum.c:1912 #, c-format msgid "You might have already suffered transaction-wraparound data loss." msgstr "" "Возможно, вы уже потеряли данные в результате зацикливания ID транзакций." -#: commands/vacuum.c:2082 +#: commands/vacuum.c:2098 #, c-format msgid "skipping \"%s\" --- cannot vacuum non-tables or special system tables" msgstr "" "\"%s\" пропускается --- очищать не таблицы или специальные системные таблицы " "нельзя" -#: commands/vacuum.c:2514 +#: commands/vacuum.c:2545 #, c-format msgid "scanned index \"%s\" to remove %lld row versions" msgstr "просканирован индекс \"%s\", удалено версий строк: %lld" -#: commands/vacuum.c:2533 +#: commands/vacuum.c:2564 #, c-format msgid "index \"%s\" now contains %.0f row versions in %u pages" msgstr "индекс \"%s\" теперь содержит версий строк: %.0f, в страницах: %u" -#: commands/vacuum.c:2537 +#: commands/vacuum.c:2568 #, c-format msgid "" "%.0f index row versions were removed.\n" @@ -15167,27 +15174,27 @@ msgid "no value found for parameter %d" msgstr "не найдено значение параметра %d" #: executor/execExpr.c:642 executor/execExpr.c:649 executor/execExpr.c:655 -#: executor/execExprInterp.c:4852 executor/execExprInterp.c:4869 -#: executor/execExprInterp.c:4968 executor/nodeModifyTable.c:203 -#: executor/nodeModifyTable.c:222 executor/nodeModifyTable.c:239 -#: executor/nodeModifyTable.c:249 executor/nodeModifyTable.c:259 +#: executor/execExprInterp.c:4851 executor/execExprInterp.c:4868 +#: executor/execExprInterp.c:4967 executor/nodeModifyTable.c:204 +#: executor/nodeModifyTable.c:223 executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:250 executor/nodeModifyTable.c:260 #, c-format msgid "table row type and query-specified row type do not match" msgstr "тип строки таблицы отличается от типа строки-результата запроса" -#: executor/execExpr.c:643 executor/nodeModifyTable.c:204 +#: executor/execExpr.c:643 executor/nodeModifyTable.c:205 #, c-format msgid "Query has too many columns." msgstr "Запрос возвращает больше столбцов." -#: executor/execExpr.c:650 executor/nodeModifyTable.c:223 +#: executor/execExpr.c:650 executor/nodeModifyTable.c:224 #, c-format msgid "Query provides a value for a dropped column at ordinal position %d." msgstr "" "Запрос выдаёт значение для удалённого столбца (с порядковым номером %d)." -#: executor/execExpr.c:656 executor/execExprInterp.c:4870 -#: executor/nodeModifyTable.c:250 +#: executor/execExpr.c:656 executor/execExprInterp.c:4869 +#: executor/nodeModifyTable.c:251 #, c-format msgid "Table has type %s at ordinal position %d, but query expects %s." msgstr "" @@ -15342,7 +15349,7 @@ msgstr "по заданному пути не найден элемент SQL/JS msgid "could not coerce %s expression (%s) to the RETURNING type" msgstr "привести выражение %s (%s) к типу RETURNING не удалось" -#: executor/execExprInterp.c:4853 +#: executor/execExprInterp.c:4852 #, c-format msgid "Table row contains %d attribute, but query expects %d." msgid_plural "Table row contains %d attributes, but query expects %d." @@ -15350,7 +15357,7 @@ msgstr[0] "Строка таблицы содержит %d атрибут, а в msgstr[1] "Строка таблицы содержит %d атрибута, а в запросе ожидается %d." msgstr[2] "Строка таблицы содержит %d атрибутов, а в запросе ожидается %d." -#: executor/execExprInterp.c:4969 executor/execSRF.c:977 +#: executor/execExprInterp.c:4968 executor/execSRF.c:977 #, c-format msgid "Physical storage mismatch on dropped attribute at ordinal position %d." msgstr "" @@ -15870,18 +15877,18 @@ msgid "FULL JOIN is only supported with merge-joinable join conditions" msgstr "" "FULL JOIN поддерживается только с условиями, допускающими соединение слиянием" -#: executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:241 #, c-format msgid "Query provides a value for a generated column at ordinal position %d." msgstr "" "Запрос выдаёт значение для генерируемого столбца (с порядковым номером %d)." -#: executor/nodeModifyTable.c:260 +#: executor/nodeModifyTable.c:261 #, c-format msgid "Query has too few columns." msgstr "Запрос возвращает меньше столбцов." -#: executor/nodeModifyTable.c:1560 executor/nodeModifyTable.c:1634 +#: executor/nodeModifyTable.c:1562 executor/nodeModifyTable.c:1636 #, c-format msgid "" "tuple to be deleted was already modified by an operation triggered by the " @@ -15890,12 +15897,12 @@ msgstr "" "кортеж, который должен быть удалён, уже модифицирован в операции, вызванной " "текущей командой" -#: executor/nodeModifyTable.c:1789 +#: executor/nodeModifyTable.c:1791 #, c-format msgid "invalid ON UPDATE specification" msgstr "неверное указание ON UPDATE" -#: executor/nodeModifyTable.c:1790 +#: executor/nodeModifyTable.c:1792 #, c-format msgid "" "The result tuple would appear in a different partition than the original " @@ -15904,7 +15911,7 @@ msgstr "" "Результирующий кортеж окажется перемещённым из секции исходного кортежа в " "другую." -#: executor/nodeModifyTable.c:2246 +#: executor/nodeModifyTable.c:2249 #, c-format msgid "" "cannot move tuple across partitions when a non-root ancestor of the source " @@ -15913,26 +15920,26 @@ msgstr "" "нельзя переместить кортеж между секциями, когда внешний ключ непосредственно " "ссылается на предка исходной секции, который не является корнем иерархии" -#: executor/nodeModifyTable.c:2247 +#: executor/nodeModifyTable.c:2250 #, c-format msgid "" "A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\"." msgstr "" "Внешний ключ ссылается на предка \"%s\", а не на корневого предка \"%s\"." -#: executor/nodeModifyTable.c:2250 +#: executor/nodeModifyTable.c:2253 #, c-format msgid "Consider defining the foreign key on table \"%s\"." msgstr "Возможно, имеет смысл перенацелить внешний ключ на таблицу \"%s\"." #. translator: %s is a SQL command name -#: executor/nodeModifyTable.c:2616 executor/nodeModifyTable.c:3158 -#: executor/nodeModifyTable.c:3328 +#: executor/nodeModifyTable.c:2619 executor/nodeModifyTable.c:3161 +#: executor/nodeModifyTable.c:3331 #, c-format msgid "%s command cannot affect row a second time" msgstr "команда %s не может подействовать на строку дважды" -#: executor/nodeModifyTable.c:2618 +#: executor/nodeModifyTable.c:2621 #, c-format msgid "" "Ensure that no rows proposed for insertion within the same command have " @@ -15941,7 +15948,7 @@ msgstr "" "Проверьте, не содержат ли строки, которые должна добавить команда, " "дублирующиеся значения, подпадающие под ограничения." -#: executor/nodeModifyTable.c:3151 executor/nodeModifyTable.c:3321 +#: executor/nodeModifyTable.c:3154 executor/nodeModifyTable.c:3324 #, c-format msgid "" "tuple to be updated or deleted was already modified by an operation " @@ -15950,14 +15957,14 @@ msgstr "" "кортеж, который должен быть изменён или удалён, уже модифицирован в " "операции, вызванной текущей командой" -#: executor/nodeModifyTable.c:3160 executor/nodeModifyTable.c:3330 +#: executor/nodeModifyTable.c:3163 executor/nodeModifyTable.c:3333 #, c-format msgid "Ensure that not more than one source row matches any one target row." msgstr "" "Проверьте, не может ли какой-либо целевой строке соответствовать более одной " "исходной строки." -#: executor/nodeModifyTable.c:3229 +#: executor/nodeModifyTable.c:3232 #, c-format msgid "" "tuple to be merged was already moved to another partition due to concurrent " @@ -16126,7 +16133,7 @@ msgstr "не удалось передать кортеж в очередь в msgid "user mapping not found for user \"%s\", server \"%s\"" msgstr "сопоставление для пользователя \"%s\", сервера \"%s\" не найдено" -#: foreign/foreign.c:336 optimizer/plan/createplan.c:7153 +#: foreign/foreign.c:336 optimizer/plan/createplan.c:7155 #: optimizer/util/plancat.c:540 #, c-format msgid "access to non-system foreign table is restricted" @@ -16326,94 +16333,94 @@ msgstr "Некорректное подтверждение в последне msgid "Garbage found at the end of client-final-message." msgstr "Мусор в конце последнего сообщения клиента." -#: libpq/auth.c:269 +#: libpq/auth.c:277 #, c-format msgid "authentication failed for user \"%s\": host rejected" msgstr "" "пользователь \"%s\" не прошёл проверку подлинности: не разрешённый компьютер" -#: libpq/auth.c:272 +#: libpq/auth.c:280 #, c-format msgid "\"trust\" authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (\"trust\")" -#: libpq/auth.c:275 +#: libpq/auth.c:283 #, c-format msgid "Ident authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (Ident)" -#: libpq/auth.c:278 +#: libpq/auth.c:286 #, c-format msgid "Peer authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (Peer)" -#: libpq/auth.c:283 +#: libpq/auth.c:291 #, c-format msgid "password authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (по паролю)" -#: libpq/auth.c:288 +#: libpq/auth.c:296 #, c-format msgid "GSSAPI authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (GSSAPI)" -#: libpq/auth.c:291 +#: libpq/auth.c:299 #, c-format msgid "SSPI authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (SSPI)" -#: libpq/auth.c:294 +#: libpq/auth.c:302 #, c-format msgid "PAM authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (PAM)" -#: libpq/auth.c:297 +#: libpq/auth.c:305 #, c-format msgid "BSD authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (BSD)" -#: libpq/auth.c:300 +#: libpq/auth.c:308 #, c-format msgid "LDAP authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (LDAP)" -#: libpq/auth.c:303 +#: libpq/auth.c:311 #, c-format msgid "certificate authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (по сертификату)" -#: libpq/auth.c:306 +#: libpq/auth.c:314 #, c-format msgid "RADIUS authentication failed for user \"%s\"" msgstr "пользователь \"%s\" не прошёл проверку подлинности (RADIUS)" -#: libpq/auth.c:309 +#: libpq/auth.c:317 #, c-format msgid "authentication failed for user \"%s\": invalid authentication method" msgstr "" "пользователь \"%s\" не прошёл проверку подлинности: неверный метод проверки" -#: libpq/auth.c:313 +#: libpq/auth.c:321 #, c-format msgid "Connection matched file \"%s\" line %d: \"%s\"" msgstr "Подключение соответствует строке %2$d в \"%1$s\": \"%3$s\"" -#: libpq/auth.c:357 +#: libpq/auth.c:365 #, c-format msgid "authentication identifier set more than once" msgstr "аутентификационный идентификатор указан повторно" -#: libpq/auth.c:358 +#: libpq/auth.c:366 #, c-format msgid "previous identifier: \"%s\"; new identifier: \"%s\"" msgstr "предыдущий идентификатор: \"%s\"; новый: \"%s\"" -#: libpq/auth.c:368 +#: libpq/auth.c:376 #, c-format msgid "connection authenticated: identity=\"%s\" method=%s (%s:%d)" msgstr "соединение аутентифицировано: идентификатор=\"%s\" метод=%s (%s:%d)" -#: libpq/auth.c:408 +#: libpq/auth.c:416 #, c-format msgid "" "client certificates can only be checked if a root certificate store is " @@ -16422,25 +16429,25 @@ msgstr "" "сертификаты клиентов могут проверяться, только если доступно хранилище " "корневых сертификатов" -#: libpq/auth.c:419 +#: libpq/auth.c:427 #, c-format msgid "connection requires a valid client certificate" msgstr "для подключения требуется годный сертификат клиента" -#: libpq/auth.c:450 libpq/auth.c:496 +#: libpq/auth.c:458 libpq/auth.c:504 msgid "GSS encryption" msgstr "Шифрование GSS" -#: libpq/auth.c:453 libpq/auth.c:499 +#: libpq/auth.c:461 libpq/auth.c:507 msgid "SSL encryption" msgstr "Шифрование SSL" -#: libpq/auth.c:455 libpq/auth.c:501 +#: libpq/auth.c:463 libpq/auth.c:509 msgid "no encryption" msgstr "без шифрования" #. translator: last %s describes encryption state -#: libpq/auth.c:461 +#: libpq/auth.c:469 #, c-format msgid "" "pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s" @@ -16449,7 +16456,7 @@ msgstr "" "пользователь \"%s\", \"%s\"" #. translator: last %s describes encryption state -#: libpq/auth.c:468 +#: libpq/auth.c:476 #, c-format msgid "" "pg_hba.conf rejects connection for host \"%s\", user \"%s\", database " @@ -16458,38 +16465,38 @@ msgstr "" "pg_hba.conf отвергает подключение: компьютер \"%s\", пользователь \"%s\", " "база данных \"%s\", %s" -#: libpq/auth.c:506 +#: libpq/auth.c:514 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup matches." msgstr "" "IP-адрес клиента разрешается в \"%s\", соответствует прямому преобразованию." -#: libpq/auth.c:509 +#: libpq/auth.c:517 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup not checked." msgstr "" "IP-адрес клиента разрешается в \"%s\", прямое преобразование не проверялось." -#: libpq/auth.c:512 +#: libpq/auth.c:520 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup does not match." msgstr "" "IP-адрес клиента разрешается в \"%s\", это не соответствует прямому " "преобразованию." -#: libpq/auth.c:515 +#: libpq/auth.c:523 #, c-format msgid "Could not translate client host name \"%s\" to IP address: %s." msgstr "" "Преобразовать имя клиентского компьютера \"%s\" в IP-адрес не удалось: %s." -#: libpq/auth.c:520 +#: libpq/auth.c:528 #, c-format msgid "Could not resolve client IP address to a host name: %s." msgstr "Получить имя компьютера из IP-адреса клиента не удалось: %s." #. translator: last %s describes encryption state -#: libpq/auth.c:528 +#: libpq/auth.c:536 #, c-format msgid "" "no pg_hba.conf entry for replication connection from host \"%s\", user " @@ -16499,242 +16506,242 @@ msgstr "" "компьютера \"%s\" для пользователя \"%s\", %s" #. translator: last %s describes encryption state -#: libpq/auth.c:536 +#: libpq/auth.c:544 #, c-format msgid "no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s" msgstr "" "в pg_hba.conf нет записи для компьютера \"%s\", пользователя \"%s\", базы " "\"%s\", %s" -#: libpq/auth.c:656 +#: libpq/auth.c:664 #, c-format msgid "connection authenticated: user=\"%s\" method=%s (%s:%d)" msgstr "соединение аутентифицировано: пользователь=\"%s\" метод=%s (%s:%d)" -#: libpq/auth.c:725 +#: libpq/auth.c:733 #, c-format msgid "expected password response, got message type %d" msgstr "ожидался ответ с паролем, но получено сообщение %d" -#: libpq/auth.c:746 +#: libpq/auth.c:754 #, c-format msgid "invalid password packet size" msgstr "неверный размер пакета с паролем" -#: libpq/auth.c:764 +#: libpq/auth.c:772 #, c-format msgid "empty password returned by client" msgstr "клиент возвратил пустой пароль" -#: libpq/auth.c:892 +#: libpq/auth.c:900 #, c-format msgid "could not generate random MD5 salt" msgstr "не удалось сгенерировать случайную соль для MD5" -#: libpq/auth.c:943 libpq/be-secure-gssapi.c:540 +#: libpq/auth.c:951 libpq/be-secure-gssapi.c:550 #, c-format msgid "could not set environment: %m" msgstr "не удалось задать переменную окружения: %m" -#: libpq/auth.c:982 +#: libpq/auth.c:990 #, c-format msgid "expected GSS response, got message type %d" msgstr "ожидался ответ GSS, но получено сообщение %d" -#: libpq/auth.c:1048 +#: libpq/auth.c:1056 msgid "accepting GSS security context failed" msgstr "принять контекст безопасности GSS не удалось" -#: libpq/auth.c:1089 +#: libpq/auth.c:1097 msgid "retrieving GSS user name failed" msgstr "получить имя пользователя GSS не удалось" -#: libpq/auth.c:1235 +#: libpq/auth.c:1243 msgid "could not acquire SSPI credentials" msgstr "не удалось получить удостоверение SSPI" -#: libpq/auth.c:1260 +#: libpq/auth.c:1268 #, c-format msgid "expected SSPI response, got message type %d" msgstr "ожидался ответ SSPI, но получено сообщение %d" -#: libpq/auth.c:1338 +#: libpq/auth.c:1346 msgid "could not accept SSPI security context" msgstr "принять контекст безопасности SSPI не удалось" -#: libpq/auth.c:1379 +#: libpq/auth.c:1387 msgid "could not get token from SSPI security context" msgstr "не удалось получить маркер из контекста безопасности SSPI" -#: libpq/auth.c:1515 libpq/auth.c:1534 +#: libpq/auth.c:1523 libpq/auth.c:1542 #, c-format msgid "could not translate name" msgstr "не удалось преобразовать имя" -#: libpq/auth.c:1547 +#: libpq/auth.c:1555 #, c-format msgid "realm name too long" msgstr "имя области слишком длинное" -#: libpq/auth.c:1562 +#: libpq/auth.c:1570 #, c-format msgid "translated account name too long" msgstr "преобразованное имя учётной записи слишком длинное" -#: libpq/auth.c:1741 +#: libpq/auth.c:1749 #, c-format msgid "could not create socket for Ident connection: %m" msgstr "не удалось создать сокет для подключения к серверу Ident: %m" -#: libpq/auth.c:1756 +#: libpq/auth.c:1764 #, c-format msgid "could not bind to local address \"%s\": %m" msgstr "не удалось привязаться к локальному адресу \"%s\": %m" -#: libpq/auth.c:1768 +#: libpq/auth.c:1776 #, c-format msgid "could not connect to Ident server at address \"%s\", port %s: %m" msgstr "не удалось подключиться к серверу Ident по адресу \"%s\", порт %s: %m" -#: libpq/auth.c:1790 +#: libpq/auth.c:1798 #, c-format msgid "could not send query to Ident server at address \"%s\", port %s: %m" msgstr "" "не удалось отправить запрос серверу Ident по адресу \"%s\", порт %s: %m" -#: libpq/auth.c:1807 +#: libpq/auth.c:1815 #, c-format msgid "" "could not receive response from Ident server at address \"%s\", port %s: %m" msgstr "" "не удалось получить ответ от сервера Ident по адресу \"%s\", порт %s: %m" -#: libpq/auth.c:1817 +#: libpq/auth.c:1825 #, c-format msgid "invalidly formatted response from Ident server: \"%s\"" msgstr "неверно форматированный ответ от сервера Ident: \"%s\"" -#: libpq/auth.c:1870 +#: libpq/auth.c:1878 #, c-format msgid "peer authentication is not supported on this platform" msgstr "проверка подлинности peer в этой ОС не поддерживается" -#: libpq/auth.c:1874 +#: libpq/auth.c:1882 #, c-format msgid "could not get peer credentials: %m" msgstr "не удалось получить данные пользователя через механизм peer: %m" -#: libpq/auth.c:1886 +#: libpq/auth.c:1894 #, c-format msgid "could not look up local user ID %ld: %s" msgstr "найти локального пользователя по идентификатору (%ld) не удалось: %s" -#: libpq/auth.c:1988 +#: libpq/auth.c:1996 #, c-format msgid "error from underlying PAM layer: %s" msgstr "ошибка в нижележащем слое PAM: %s" -#: libpq/auth.c:1999 +#: libpq/auth.c:2007 #, c-format msgid "unsupported PAM conversation %d/\"%s\"" msgstr "неподдерживаемое сообщение ответа PAM %d/\"%s\"" -#: libpq/auth.c:2056 +#: libpq/auth.c:2064 #, c-format msgid "could not create PAM authenticator: %s" msgstr "не удалось создать аутентификатор PAM: %s" -#: libpq/auth.c:2067 +#: libpq/auth.c:2075 #, c-format msgid "pam_set_item(PAM_USER) failed: %s" msgstr "ошибка в pam_set_item(PAM_USER): %s" -#: libpq/auth.c:2099 +#: libpq/auth.c:2107 #, c-format msgid "pam_set_item(PAM_RHOST) failed: %s" msgstr "ошибка в pam_set_item(PAM_RHOST): %s" -#: libpq/auth.c:2111 +#: libpq/auth.c:2119 #, c-format msgid "pam_set_item(PAM_CONV) failed: %s" msgstr "ошибка в pam_set_item(PAM_CONV): %s" -#: libpq/auth.c:2124 +#: libpq/auth.c:2132 #, c-format msgid "pam_authenticate failed: %s" msgstr "ошибка в pam_authenticate: %s" -#: libpq/auth.c:2137 +#: libpq/auth.c:2145 #, c-format msgid "pam_acct_mgmt failed: %s" msgstr "ошибка в pam_acct_mgmt: %s" -#: libpq/auth.c:2148 +#: libpq/auth.c:2156 #, c-format msgid "could not release PAM authenticator: %s" msgstr "не удалось освободить аутентификатор PAM: %s" -#: libpq/auth.c:2228 +#: libpq/auth.c:2236 #, c-format msgid "could not initialize LDAP: error code %d" msgstr "не удалось инициализировать LDAP (код ошибки: %d)" -#: libpq/auth.c:2265 +#: libpq/auth.c:2273 #, c-format msgid "could not extract domain name from ldapbasedn" msgstr "не удалось извлечь имя домена из ldapbasedn" -#: libpq/auth.c:2273 +#: libpq/auth.c:2281 #, c-format msgid "LDAP authentication could not find DNS SRV records for \"%s\"" msgstr "для аутентификации LDAP не удалось найти записи DNS SRV для \"%s\"" -#: libpq/auth.c:2275 +#: libpq/auth.c:2283 #, c-format msgid "Set an LDAP server name explicitly." msgstr "Задайте имя сервера LDAP явным образом." -#: libpq/auth.c:2327 +#: libpq/auth.c:2335 #, c-format msgid "could not initialize LDAP: %s" msgstr "не удалось инициализировать LDAP: %s" -#: libpq/auth.c:2337 +#: libpq/auth.c:2345 #, c-format msgid "ldaps not supported with this LDAP library" msgstr "протокол ldaps с текущей библиотекой LDAP не поддерживается" -#: libpq/auth.c:2345 +#: libpq/auth.c:2353 #, c-format msgid "could not initialize LDAP: %m" msgstr "не удалось инициализировать LDAP: %m" -#: libpq/auth.c:2355 +#: libpq/auth.c:2363 #, c-format msgid "could not set LDAP protocol version: %s" msgstr "не удалось задать версию протокола LDAP: %s" -#: libpq/auth.c:2371 +#: libpq/auth.c:2379 #, c-format msgid "could not start LDAP TLS session: %s" msgstr "не удалось начать сеанс LDAP TLS: %s" -#: libpq/auth.c:2448 +#: libpq/auth.c:2456 #, c-format msgid "LDAP server not specified, and no ldapbasedn" msgstr "LDAP-сервер не задан и значение ldapbasedn не определено" -#: libpq/auth.c:2455 +#: libpq/auth.c:2463 #, c-format msgid "LDAP server not specified" msgstr "LDAP-сервер не определён" -#: libpq/auth.c:2517 +#: libpq/auth.c:2525 #, c-format msgid "invalid character in user name for LDAP authentication" msgstr "недопустимый символ в имени пользователя для проверки подлинности LDAP" -#: libpq/auth.c:2534 +#: libpq/auth.c:2542 #, c-format msgid "" "could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": " @@ -16743,28 +16750,28 @@ msgstr "" "не удалось выполнить начальную привязку LDAP для ldapbinddn \"%s\" на " "сервере \"%s\": %s" -#: libpq/auth.c:2564 +#: libpq/auth.c:2572 #, c-format msgid "could not search LDAP for filter \"%s\" on server \"%s\": %s" msgstr "" "не удалось выполнить LDAP-поиск по фильтру \"%s\" на сервере \"%s\": %s" -#: libpq/auth.c:2580 +#: libpq/auth.c:2588 #, c-format msgid "LDAP user \"%s\" does not exist" msgstr "в LDAP нет пользователя \"%s\"" -#: libpq/auth.c:2581 +#: libpq/auth.c:2589 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned no entries." msgstr "LDAP-поиск по фильтру \"%s\" на сервере \"%s\" не вернул результатов" -#: libpq/auth.c:2585 +#: libpq/auth.c:2593 #, c-format msgid "LDAP user \"%s\" is not unique" msgstr "пользователь LDAP \"%s\" не уникален" -#: libpq/auth.c:2586 +#: libpq/auth.c:2594 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned %d entry." msgid_plural "" @@ -16773,7 +16780,7 @@ msgstr[0] "LDAP-поиск по фильтру \"%s\" на сервере \"%s\" msgstr[1] "LDAP-поиск по фильтру \"%s\" на сервере \"%s\" вернул %d записи." msgstr[2] "LDAP-поиск по фильтру \"%s\" на сервере \"%s\" вернул %d записей." -#: libpq/auth.c:2606 +#: libpq/auth.c:2614 #, c-format msgid "" "could not get dn for the first entry matching \"%s\" on server \"%s\": %s" @@ -16781,18 +16788,18 @@ msgstr "" "не удалось получить dn для первого результата, соответствующего \"%s\" на " "сервере \"%s\": %s" -#: libpq/auth.c:2633 +#: libpq/auth.c:2641 #, c-format msgid "LDAP login failed for user \"%s\" on server \"%s\": %s" msgstr "" "ошибка при регистрации в LDAP пользователя \"%s\" на сервере \"%s\": %s" -#: libpq/auth.c:2665 +#: libpq/auth.c:2673 #, c-format msgid "LDAP diagnostics: %s" msgstr "Диагностика LDAP: %s" -#: libpq/auth.c:2703 +#: libpq/auth.c:2711 #, c-format msgid "" "certificate authentication failed for user \"%s\": client certificate " @@ -16801,7 +16808,7 @@ msgstr "" "ошибка проверки подлинности пользователя \"%s\" по сертификату: сертификат " "клиента не содержит имя пользователя" -#: libpq/auth.c:2724 +#: libpq/auth.c:2732 #, c-format msgid "" "certificate authentication failed for user \"%s\": unable to retrieve " @@ -16810,7 +16817,7 @@ msgstr "" "пользователь \"%s\" не прошёл проверку подлинности по сертификату: не " "удалось получить DN субъекта" -#: libpq/auth.c:2747 +#: libpq/auth.c:2755 #, c-format msgid "" "certificate validation (clientcert=verify-full) failed for user \"%s\": DN " @@ -16819,7 +16826,7 @@ msgstr "" "проверка сертификата (clientcert=verify-full) для пользователя \"%s\" не " "прошла: отличается DN" -#: libpq/auth.c:2752 +#: libpq/auth.c:2760 #, c-format msgid "" "certificate validation (clientcert=verify-full) failed for user \"%s\": CN " @@ -16828,99 +16835,99 @@ msgstr "" "проверка сертификата (clientcert=verify-full) для пользователя \"%s\" не " "прошла: отличается CN" -#: libpq/auth.c:2854 +#: libpq/auth.c:2862 #, c-format msgid "RADIUS server not specified" msgstr "RADIUS-сервер не определён" -#: libpq/auth.c:2861 +#: libpq/auth.c:2869 #, c-format msgid "RADIUS secret not specified" msgstr "секрет RADIUS не определён" # well-spelled: симв -#: libpq/auth.c:2875 +#: libpq/auth.c:2883 #, c-format msgid "" "RADIUS authentication does not support passwords longer than %d characters" msgstr "проверка подлинности RADIUS не поддерживает пароли длиннее %d симв." -#: libpq/auth.c:2977 libpq/hba.c:2352 +#: libpq/auth.c:2985 libpq/hba.c:2352 #, c-format msgid "could not translate RADIUS server name \"%s\" to address: %s" msgstr "не удалось преобразовать имя сервера RADIUS \"%s\" в адрес: %s" -#: libpq/auth.c:2991 +#: libpq/auth.c:2999 #, c-format msgid "could not generate random encryption vector" msgstr "не удалось сгенерировать случайный вектор шифрования" -#: libpq/auth.c:3028 +#: libpq/auth.c:3036 #, c-format msgid "could not perform MD5 encryption of password: %s" msgstr "не удалось вычислить MD5-хеш пароля: %s" -#: libpq/auth.c:3055 +#: libpq/auth.c:3063 #, c-format msgid "could not create RADIUS socket: %m" msgstr "не удалось создать сокет RADIUS: %m" -#: libpq/auth.c:3071 +#: libpq/auth.c:3079 #, c-format msgid "could not bind local RADIUS socket: %m" msgstr "не удалось привязаться к локальному сокету RADIUS: %m" -#: libpq/auth.c:3081 +#: libpq/auth.c:3089 #, c-format msgid "could not send RADIUS packet: %m" msgstr "не удалось отправить пакет RADIUS: %m" -#: libpq/auth.c:3115 libpq/auth.c:3141 +#: libpq/auth.c:3123 libpq/auth.c:3149 #, c-format msgid "timeout waiting for RADIUS response from %s" msgstr "превышено время ожидания ответа RADIUS от %s" -#: libpq/auth.c:3134 +#: libpq/auth.c:3142 #, c-format msgid "could not check status on RADIUS socket: %m" msgstr "не удалось проверить состояние сокета RADIUS: %m" -#: libpq/auth.c:3164 +#: libpq/auth.c:3172 #, c-format msgid "could not read RADIUS response: %m" msgstr "не удалось прочитать ответ RADIUS: %m" -#: libpq/auth.c:3172 +#: libpq/auth.c:3180 #, c-format msgid "RADIUS response from %s was sent from incorrect port: %d" msgstr "ответ RADIUS от %s был отправлен с неверного порта: %d" -#: libpq/auth.c:3180 +#: libpq/auth.c:3188 #, c-format msgid "RADIUS response from %s too short: %d" msgstr "слишком короткий ответ RADIUS от %s: %d" -#: libpq/auth.c:3187 +#: libpq/auth.c:3195 #, c-format msgid "RADIUS response from %s has corrupt length: %d (actual length %d)" msgstr "в ответе RADIUS от %s испорчена длина: %d (фактическая длина %d)" -#: libpq/auth.c:3195 +#: libpq/auth.c:3203 #, c-format msgid "RADIUS response from %s is to a different request: %d (should be %d)" msgstr "пришёл ответ RADIUS от %s на другой запрос: %d (ожидался %d)" -#: libpq/auth.c:3220 +#: libpq/auth.c:3228 #, c-format msgid "could not perform MD5 encryption of received packet: %s" msgstr "не удалось вычислить MD5-хеш для принятого пакета: %s" -#: libpq/auth.c:3230 +#: libpq/auth.c:3238 #, c-format msgid "RADIUS response from %s has incorrect MD5 signature" msgstr "ответ RADIUS от %s содержит неверную подпись MD5" -#: libpq/auth.c:3248 +#: libpq/auth.c:3256 #, c-format msgid "RADIUS response from %s has invalid code (%d) for user \"%s\"" msgstr "ответ RADIUS от %s содержит неверный код (%d) для пользователя \"%s\"" @@ -17023,44 +17030,39 @@ msgstr "" "он принадлежит пользователю сервера, либо u=rw,g=r (0640) или более строгие, " "если он принадлежит root." -#: libpq/be-secure-gssapi.c:201 +#: libpq/be-secure-gssapi.c:208 msgid "GSSAPI wrap error" msgstr "ошибка обёртывания сообщения в GSSAPI" -#: libpq/be-secure-gssapi.c:208 +#: libpq/be-secure-gssapi.c:215 #, c-format msgid "outgoing GSSAPI message would not use confidentiality" msgstr "исходящее сообщение GSSAPI не будет защищено" -#: libpq/be-secure-gssapi.c:215 libpq/be-secure-gssapi.c:634 +#: libpq/be-secure-gssapi.c:222 libpq/be-secure-gssapi.c:644 #, c-format msgid "server tried to send oversize GSSAPI packet (%zu > %zu)" msgstr "сервер попытался передать чрезмерно большой пакет GSSAPI (%zu > %zu)" -#: libpq/be-secure-gssapi.c:351 +#: libpq/be-secure-gssapi.c:358 libpq/be-secure-gssapi.c:585 #, c-format msgid "oversize GSSAPI packet sent by the client (%zu > %zu)" msgstr "клиент передал чрезмерно большой пакет GSSAPI (%zu > %zu)" -#: libpq/be-secure-gssapi.c:389 +#: libpq/be-secure-gssapi.c:396 msgid "GSSAPI unwrap error" msgstr "ошибка развёртывания сообщения в GSSAPI" -#: libpq/be-secure-gssapi.c:396 +#: libpq/be-secure-gssapi.c:403 #, c-format msgid "incoming GSSAPI message did not use confidentiality" msgstr "входящее сообщение GSSAPI не защищено" -#: libpq/be-secure-gssapi.c:575 -#, c-format -msgid "oversize GSSAPI packet sent by the client (%zu > %d)" -msgstr "клиент передал чрезмерно большой пакет GSSAPI (%zu > %d)" - -#: libpq/be-secure-gssapi.c:600 +#: libpq/be-secure-gssapi.c:610 msgid "could not accept GSSAPI security context" msgstr "принять контекст безопасности GSSAPI не удалось" -#: libpq/be-secure-gssapi.c:701 +#: libpq/be-secure-gssapi.c:728 msgid "GSSAPI size check error" msgstr "ошибка проверки размера в GSSAPI" @@ -18274,7 +18276,7 @@ msgstr "" "FULL JOIN поддерживается только с условиями, допускающими соединение " "слиянием или хеш-соединение" -#: optimizer/plan/createplan.c:7175 parser/parse_merge.c:203 +#: optimizer/plan/createplan.c:7177 parser/parse_merge.c:203 #: rewrite/rewriteHandler.c:1680 #, c-format msgid "cannot execute MERGE on relation \"%s\"" @@ -21754,32 +21756,32 @@ msgid "autovacuum worker took too long to start; canceled" msgstr "процесс автоочистки запускался слишком долго; его запуск отменён" # skip-rule: capital-letter-first -#: postmaster/autovacuum.c:2203 +#: postmaster/autovacuum.c:2218 #, c-format msgid "autovacuum: dropping orphan temp table \"%s.%s.%s\"" msgstr "автоочистка: удаление устаревшей врем. таблицы \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2439 +#: postmaster/autovacuum.c:2461 #, c-format msgid "automatic vacuum of table \"%s.%s.%s\"" msgstr "автоматическая очистка таблицы \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2442 +#: postmaster/autovacuum.c:2464 #, c-format msgid "automatic analyze of table \"%s.%s.%s\"" msgstr "автоматический анализ таблицы \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2636 +#: postmaster/autovacuum.c:2660 #, c-format msgid "processing work entry for relation \"%s.%s.%s\"" msgstr "обработка рабочей записи для отношения \"%s.%s.%s\"" -#: postmaster/autovacuum.c:3254 +#: postmaster/autovacuum.c:3291 #, c-format msgid "autovacuum not started because of misconfiguration" msgstr "автоочистка не запущена из-за неправильной конфигурации" -#: postmaster/autovacuum.c:3255 +#: postmaster/autovacuum.c:3292 #, c-format msgid "Enable the \"track_counts\" option." msgstr "Включите параметр \"track_counts\"." @@ -21863,12 +21865,12 @@ msgstr[1] "" msgstr[2] "" "Максимально возможное число фоновых процессов при текущих параметрах: %d." -#: postmaster/bgworker.c:931 postmaster/checkpointer.c:445 +#: postmaster/bgworker.c:931 postmaster/checkpointer.c:448 #, c-format msgid "Consider increasing the configuration parameter \"%s\"." msgstr "Возможно, стоит увеличить параметр \"%s\"." -#: postmaster/checkpointer.c:441 +#: postmaster/checkpointer.c:444 #, c-format msgid "checkpoints are occurring too frequently (%d second apart)" msgid_plural "checkpoints are occurring too frequently (%d seconds apart)" @@ -21876,12 +21878,12 @@ msgstr[0] "контрольные точки происходят слишком msgstr[1] "контрольные точки происходят слишком часто (через %d сек.)" msgstr[2] "контрольные точки происходят слишком часто (через %d сек.)" -#: postmaster/checkpointer.c:1067 +#: postmaster/checkpointer.c:1073 #, c-format msgid "checkpoint request failed" msgstr "сбой при запросе контрольной точки" -#: postmaster/checkpointer.c:1068 +#: postmaster/checkpointer.c:1074 #, c-format msgid "Consult recent messages in the server log for details." msgstr "Смотрите подробности в протоколе сервера." @@ -22754,7 +22756,7 @@ msgstr "" "применяющий процесс логической репликации будет сериализовывать остальное " "содержимое удалённой транзакции %u в файл" -#: replication/logical/decode.c:177 replication/logical/logical.c:141 +#: replication/logical/decode.c:177 replication/logical/logical.c:143 #, c-format msgid "" "logical decoding on standby requires \"wal_level\" >= \"logical\" on the " @@ -22763,19 +22765,19 @@ msgstr "" "для логического декодирования на ведомом сервере требуется \"wal_level\" >= " "\"logical\" на ведущем" -#: replication/logical/launcher.c:334 +#: replication/logical/launcher.c:347 #, c-format msgid "cannot start logical replication workers when max_replication_slots = 0" msgstr "" "нельзя запустить процессы-обработчики логической репликации при " "max_replication_slots = 0" -#: replication/logical/launcher.c:427 +#: replication/logical/launcher.c:440 #, c-format msgid "out of logical replication worker slots" msgstr "недостаточно слотов для процессов логической репликации" -#: replication/logical/launcher.c:428 replication/logical/launcher.c:514 +#: replication/logical/launcher.c:441 replication/logical/launcher.c:527 #: replication/slot.c:1524 storage/lmgr/lock.c:985 storage/lmgr/lock.c:1023 #: storage/lmgr/lock.c:2836 storage/lmgr/lock.c:4221 storage/lmgr/lock.c:4286 #: storage/lmgr/lock.c:4636 storage/lmgr/predicate.c:2469 @@ -22784,18 +22786,18 @@ msgstr "недостаточно слотов для процессов логи msgid "You might need to increase \"%s\"." msgstr "Возможно, следует увеличить параметр \"%s\"." -#: replication/logical/launcher.c:513 +#: replication/logical/launcher.c:526 #, c-format msgid "out of background worker slots" msgstr "недостаточно слотов для фоновых рабочих процессов" -#: replication/logical/launcher.c:720 +#: replication/logical/launcher.c:733 #, c-format msgid "logical replication worker slot %d is empty, cannot attach" msgstr "" "слот обработчика логической репликации %d пуст, подключиться к нему нельзя" -#: replication/logical/launcher.c:729 +#: replication/logical/launcher.c:742 #, c-format msgid "" "logical replication worker slot %d is already used by another worker, cannot " @@ -22804,28 +22806,28 @@ msgstr "" "слот обработчика логической репликации %d уже занят другим процессом, " "подключиться к нему нельзя" -#: replication/logical/logical.c:121 +#: replication/logical/logical.c:123 #, c-format msgid "logical decoding requires \"wal_level\" >= \"logical\"" msgstr "для логического декодирования требуется \"wal_level\" >= \"logical\"" -#: replication/logical/logical.c:126 +#: replication/logical/logical.c:128 #, c-format msgid "logical decoding requires a database connection" msgstr "для логического декодирования требуется подключение к БД" -#: replication/logical/logical.c:365 replication/logical/logical.c:519 +#: replication/logical/logical.c:367 replication/logical/logical.c:521 #, c-format msgid "cannot use physical replication slot for logical decoding" msgstr "" "физический слот репликации нельзя использовать для логического декодирования" -#: replication/logical/logical.c:370 replication/logical/logical.c:529 +#: replication/logical/logical.c:372 replication/logical/logical.c:531 #, c-format msgid "replication slot \"%s\" was not created in this database" msgstr "слот репликации \"%s\" создан не в этой базе данных" -#: replication/logical/logical.c:377 +#: replication/logical/logical.c:379 #, c-format msgid "" "cannot create logical replication slot in transaction that has performed " @@ -22833,29 +22835,29 @@ msgid "" msgstr "" "нельзя создать слот логической репликации в транзакции, осуществляющей запись" -#: replication/logical/logical.c:540 +#: replication/logical/logical.c:542 #, c-format msgid "cannot use replication slot \"%s\" for logical decoding" msgstr "" "слот репликации \"%s\" нельзя использовать для логического декодирования" -#: replication/logical/logical.c:542 replication/slot.c:798 +#: replication/logical/logical.c:544 replication/slot.c:798 #: replication/slot.c:829 #, c-format msgid "This replication slot is being synchronized from the primary server." msgstr "Этот слот репликации синхронизируется с ведущего сервера." -#: replication/logical/logical.c:543 +#: replication/logical/logical.c:545 #, c-format msgid "Specify another replication slot." msgstr "Укажите другой слот репликации." -#: replication/logical/logical.c:554 replication/logical/logical.c:561 +#: replication/logical/logical.c:556 replication/logical/logical.c:563 #, c-format msgid "can no longer get changes from replication slot \"%s\"" msgstr "из слота репликации \"%s\" больше нельзя получать изменения" -#: replication/logical/logical.c:556 +#: replication/logical/logical.c:558 #, c-format msgid "" "This slot has been invalidated because it exceeded the maximum reserved size." @@ -22863,48 +22865,48 @@ msgstr "" "Этот слот был аннулирован из-за превышения максимального зарезервированного " "размера." -#: replication/logical/logical.c:563 +#: replication/logical/logical.c:565 #, c-format msgid "" "This slot has been invalidated because it was conflicting with recovery." msgstr "Этот слот был аннулирован из-за конфликта с восстановлением." -#: replication/logical/logical.c:628 +#: replication/logical/logical.c:630 #, c-format msgid "starting logical decoding for slot \"%s\"" msgstr "начинается логическое декодирование для слота \"%s\"" -#: replication/logical/logical.c:630 +#: replication/logical/logical.c:632 #, c-format msgid "Streaming transactions committing after %X/%X, reading WAL from %X/%X." msgstr "Передача транзакций, фиксируемых после %X/%X, чтение WAL с %X/%X." -#: replication/logical/logical.c:778 +#: replication/logical/logical.c:780 #, c-format msgid "" "slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X" msgstr "" "слот \"%s\", модуль вывода \"%s\", в обработчике %s, связанный LSN: %X/%X" -#: replication/logical/logical.c:784 +#: replication/logical/logical.c:786 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback" msgstr "слот \"%s\", модуль вывода \"%s\", в обработчике %s" -#: replication/logical/logical.c:955 replication/logical/logical.c:1000 -#: replication/logical/logical.c:1045 replication/logical/logical.c:1091 +#: replication/logical/logical.c:957 replication/logical/logical.c:1002 +#: replication/logical/logical.c:1047 replication/logical/logical.c:1093 #, c-format msgid "logical replication at prepare time requires a %s callback" msgstr "для логической репликации во время подготовки требуется обработчик %s" -#: replication/logical/logical.c:1323 replication/logical/logical.c:1372 -#: replication/logical/logical.c:1413 replication/logical/logical.c:1499 -#: replication/logical/logical.c:1548 +#: replication/logical/logical.c:1325 replication/logical/logical.c:1374 +#: replication/logical/logical.c:1415 replication/logical/logical.c:1501 +#: replication/logical/logical.c:1550 #, c-format msgid "logical streaming requires a %s callback" msgstr "для логической потоковой репликации требуется обработчик %s" -#: replication/logical/logical.c:1458 +#: replication/logical/logical.c:1460 #, c-format msgid "logical streaming at prepare time requires a %s callback" msgstr "" @@ -23022,7 +23024,7 @@ msgstr "" "репликации с ID %d" #: replication/logical/origin.c:957 replication/logical/origin.c:1158 -#: replication/slot.c:2401 +#: replication/slot.c:2397 #, c-format msgid "Increase \"max_replication_slots\" and try again." msgstr "Увеличьте параметр \"max_replication_slots\" и повторите попытку." @@ -23093,19 +23095,19 @@ msgstr "" msgid "logical replication target relation \"%s.%s\" does not exist" msgstr "целевое отношение логической репликации \"%s.%s\" не существует" -#: replication/logical/reorderbuffer.c:3999 +#: replication/logical/reorderbuffer.c:4129 #, c-format msgid "could not write to data file for XID %u: %m" msgstr "не удалось записать в файл данных для XID %u: %m" -#: replication/logical/reorderbuffer.c:4345 -#: replication/logical/reorderbuffer.c:4370 +#: replication/logical/reorderbuffer.c:4475 +#: replication/logical/reorderbuffer.c:4500 #, c-format msgid "could not read from reorderbuffer spill file: %m" msgstr "не удалось прочитать файл подкачки буфера пересортировки: %m" -#: replication/logical/reorderbuffer.c:4349 -#: replication/logical/reorderbuffer.c:4374 +#: replication/logical/reorderbuffer.c:4479 +#: replication/logical/reorderbuffer.c:4504 #, c-format msgid "" "could not read from reorderbuffer spill file: read %d instead of %u bytes" @@ -23113,52 +23115,46 @@ msgstr "" "не удалось прочитать файл подкачки буфера пересортировки (прочитано байт: " "%d, требовалось: %u)" -#: replication/logical/reorderbuffer.c:4624 +#: replication/logical/reorderbuffer.c:4754 #, c-format msgid "could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m" msgstr "" "ошибка при удалении файла \"%s\" в процессе удаления pg_replslot/%s/xid*: %m" -#: replication/logical/reorderbuffer.c:5120 +#: replication/logical/reorderbuffer.c:5250 #, c-format msgid "could not read from file \"%s\": read %d instead of %d bytes" msgstr "не удалось прочитать файл \"%s\" (прочитано байт: %d, требовалось: %d)" -#: replication/logical/slotsync.c:215 +#: replication/logical/slotsync.c:215 replication/logical/slotsync.c:579 #, c-format -msgid "" -"could not synchronize replication slot \"%s\" because remote slot precedes " -"local slot" -msgstr "" -"синхронизировать слот репликации \"%s\" не удалось, так как состояние " -"удалённого слота предшествует локальному" +msgid "could not synchronize replication slot \"%s\"" +msgstr "не удалось синхронизировать слот репликации \"%s\"" #: replication/logical/slotsync.c:217 #, c-format msgid "" -"The remote slot has LSN %X/%X and catalog xmin %u, but the local slot has " -"LSN %X/%X and catalog xmin %u." +"Synchronization could lead to data loss, because the remote slot needs WAL " +"at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog " +"xmin %u." msgstr "" -"Для удалённого слота текущий LSN %X/%X и xmin каталога %u, тогда как для " -"локального — LSN %X/%X и xmin каталога %u." +"Синхронизация могла привести к потере данных, так как удалённый слот требует " +"WAL в позиции LSN %X/%X и xmin каталога %u, но на резервном сервере текущий " +"LSN %X/%X и xmin каталога %u." #: replication/logical/slotsync.c:459 #, c-format msgid "dropped replication slot \"%s\" of database with OID %u" msgstr "слот репликации \"%s\" базы данных с OID %u удалён" -#: replication/logical/slotsync.c:579 -#, c-format -msgid "could not synchronize replication slot \"%s\"" -msgstr "не удалось синхронизировать слот репликации \"%s\"" - #: replication/logical/slotsync.c:580 #, c-format msgid "" -"Logical decoding could not find consistent point from local slot's LSN %X/%X." +"Synchronization could lead to data loss, because the standby could not build " +"a consistent snapshot to decode WALs at LSN %X/%X." msgstr "" -"При логическом декодировании не удалось найти точку согласованности от LSN " -"локального слота %X/%X." +"Синхронизация могла привести к потере данных, так как резервный сервер не " +"смог получить согласованный снимок для декодирования WAL в позиции LSN %X/%X." #: replication/logical/slotsync.c:589 #, c-format @@ -23224,7 +23220,7 @@ msgid "" "replication slot synchronization requires \"%s\" to be specified in \"%s\"" msgstr "для синхронизации слотов репликации требуется указание \"%s\" в \"%s\"" -#: replication/logical/slotsync.c:1050 +#: replication/logical/slotsync.c:1048 #, c-format msgid "replication slot synchronization requires \"wal_level\" >= \"logical\"" msgstr "" @@ -23310,63 +23306,63 @@ msgstr[1] "" msgstr[2] "" "экспортирован снимок логического декодирования: \"%s\" (ид. транзакций: %u)" -#: replication/logical/snapbuild.c:1443 replication/logical/snapbuild.c:1540 -#: replication/logical/snapbuild.c:2056 +#: replication/logical/snapbuild.c:1451 replication/logical/snapbuild.c:1548 +#: replication/logical/snapbuild.c:2064 #, c-format msgid "logical decoding found consistent point at %X/%X" msgstr "процесс логического декодирования достиг точки согласованности в %X/%X" -#: replication/logical/snapbuild.c:1445 +#: replication/logical/snapbuild.c:1453 #, c-format msgid "There are no running transactions." msgstr "Больше активных транзакций нет." -#: replication/logical/snapbuild.c:1492 +#: replication/logical/snapbuild.c:1500 #, c-format msgid "logical decoding found initial starting point at %X/%X" msgstr "" "процесс логического декодирования нашёл начальную стартовую точку в %X/%X" -#: replication/logical/snapbuild.c:1494 replication/logical/snapbuild.c:1518 +#: replication/logical/snapbuild.c:1502 replication/logical/snapbuild.c:1526 #, c-format msgid "Waiting for transactions (approximately %d) older than %u to end." msgstr "Ожидание транзакций (примерно %d), старее %u до конца." -#: replication/logical/snapbuild.c:1516 +#: replication/logical/snapbuild.c:1524 #, c-format msgid "logical decoding found initial consistent point at %X/%X" msgstr "" "при логическом декодировании найдена начальная точка согласованности в %X/%X" -#: replication/logical/snapbuild.c:1542 +#: replication/logical/snapbuild.c:1550 #, c-format msgid "There are no old transactions anymore." msgstr "Больше старых транзакций нет." -#: replication/logical/snapbuild.c:1943 +#: replication/logical/snapbuild.c:1951 #, c-format msgid "snapbuild state file \"%s\" has wrong magic number: %u instead of %u" msgstr "" "файл состояния snapbuild \"%s\" имеет неправильную сигнатуру (%u вместо %u)" -#: replication/logical/snapbuild.c:1949 +#: replication/logical/snapbuild.c:1957 #, c-format msgid "snapbuild state file \"%s\" has unsupported version: %u instead of %u" msgstr "" "файл состояния snapbuild \"%s\" имеет неправильную версию (%u вместо %u)" -#: replication/logical/snapbuild.c:1990 +#: replication/logical/snapbuild.c:1998 #, c-format msgid "checksum mismatch for snapbuild state file \"%s\": is %u, should be %u" msgstr "" "в файле состояния snapbuild \"%s\" неверная контрольная сумма (%u вместо %u)" -#: replication/logical/snapbuild.c:2058 +#: replication/logical/snapbuild.c:2066 #, c-format msgid "Logical decoding will begin using saved snapshot." msgstr "Логическое декодирование начнётся с сохранённого снимка." -#: replication/logical/snapbuild.c:2165 +#: replication/logical/snapbuild.c:2173 #, c-format msgid "could not parse file name \"%s\"" msgstr "не удалось разобрать имя файла \"%s\"" @@ -23380,7 +23376,7 @@ msgstr "" "процесс синхронизации таблицы при логической репликации для подписки \"%s\", " "таблицы \"%s\" закончил обработку" -#: replication/logical/tablesync.c:641 +#: replication/logical/tablesync.c:667 #, c-format msgid "" "logical replication apply worker for subscription \"%s\" will restart so " @@ -23389,25 +23385,25 @@ msgstr "" "применяющий процесс логической репликации для подписки \"%s\" будет " "перезапущен, чтобы можно было включить режим two_phase" -#: replication/logical/tablesync.c:827 replication/logical/tablesync.c:969 +#: replication/logical/tablesync.c:853 replication/logical/tablesync.c:995 #, c-format msgid "could not fetch table info for table \"%s.%s\" from publisher: %s" msgstr "" "не удалось получить информацию о таблице \"%s.%s\" с сервера публикации: %s" -#: replication/logical/tablesync.c:834 +#: replication/logical/tablesync.c:860 #, c-format msgid "table \"%s.%s\" not found on publisher" msgstr "таблица \"%s.%s\" не найдена на сервере публикации" -#: replication/logical/tablesync.c:892 +#: replication/logical/tablesync.c:918 #, c-format msgid "could not fetch column list info for table \"%s.%s\" from publisher: %s" msgstr "" "не удалось получить информацию о списке столбцов таблицы \"%s.%s\" с сервера " "публикации: %s" -#: replication/logical/tablesync.c:1071 +#: replication/logical/tablesync.c:1097 #, c-format msgid "" "could not fetch table WHERE clause info for table \"%s.%s\" from publisher: " @@ -23416,25 +23412,25 @@ msgstr "" "не удалось получить информацию о предложении WHERE таблицы \"%s.%s\" с " "сервера публикации: %s" -#: replication/logical/tablesync.c:1230 +#: replication/logical/tablesync.c:1256 #, c-format msgid "could not start initial contents copy for table \"%s.%s\": %s" msgstr "" "не удалось начать копирование начального содержимого таблицы \"%s.%s\": %s" -#: replication/logical/tablesync.c:1429 +#: replication/logical/tablesync.c:1455 #, c-format msgid "table copy could not start transaction on publisher: %s" msgstr "" "при копировании таблицы не удалось начать транзакцию на сервере публикации: " "%s" -#: replication/logical/tablesync.c:1472 +#: replication/logical/tablesync.c:1498 #, c-format msgid "replication origin \"%s\" already exists" msgstr "источник репликации \"%s\" уже существует" -#: replication/logical/tablesync.c:1505 replication/logical/worker.c:2363 +#: replication/logical/tablesync.c:1531 replication/logical/worker.c:2363 #, c-format msgid "" "user \"%s\" cannot replicate into relation with row-level security enabled: " @@ -23443,7 +23439,7 @@ msgstr "" "пользователь \"%s\" не может реплицировать данные в отношение с включённой " "защитой на уровне строк: \"%s\"" -#: replication/logical/tablesync.c:1518 +#: replication/logical/tablesync.c:1544 #, c-format msgid "table copy could not finish transaction on publisher: %s" msgstr "" @@ -23569,7 +23565,7 @@ msgstr "" msgid "subscription has no replication slot set" msgstr "для подписки не задан слот репликации" -#: replication/logical/worker.c:4612 +#: replication/logical/worker.c:4620 #, c-format msgid "" "logical replication worker for subscription %u will not start because the " @@ -23578,7 +23574,7 @@ msgstr "" "процесс логической репликации для подписки %u не будет запущен, так как " "подписка была удалена при старте" -#: replication/logical/worker.c:4628 +#: replication/logical/worker.c:4636 #, c-format msgid "" "logical replication worker for subscription \"%s\" will not start because " @@ -23587,7 +23583,7 @@ msgstr "" "процесс логической репликации для подписки \"%s\" не будет запущен, так как " "подписка была отключена при старте" -#: replication/logical/worker.c:4652 +#: replication/logical/worker.c:4660 #, c-format msgid "" "logical replication table synchronization worker for subscription \"%s\", " @@ -23596,35 +23592,35 @@ msgstr "" "процесс синхронизации таблицы при логической репликации для подписки \"%s\", " "таблицы \"%s\" запущен" -#: replication/logical/worker.c:4657 +#: replication/logical/worker.c:4665 #, c-format msgid "logical replication apply worker for subscription \"%s\" has started" msgstr "" "запускается применяющий процесс логической репликации для подписки \"%s\"" -#: replication/logical/worker.c:4779 +#: replication/logical/worker.c:4795 #, c-format msgid "subscription \"%s\" has been disabled because of an error" msgstr "подписка \"%s\" была отключена из-за ошибки" -#: replication/logical/worker.c:4827 +#: replication/logical/worker.c:4843 #, c-format msgid "logical replication starts skipping transaction at LSN %X/%X" msgstr "" "обработчик логической репликации начинает пропускать транзакцию с LSN %X/%X" -#: replication/logical/worker.c:4841 +#: replication/logical/worker.c:4857 #, c-format msgid "logical replication completed skipping transaction at LSN %X/%X" msgstr "" "обработчик логической репликации завершил пропуск транзакции с LSN %X/%X" -#: replication/logical/worker.c:4923 +#: replication/logical/worker.c:4945 #, c-format msgid "skip-LSN of subscription \"%s\" cleared" msgstr "значение skip-LSN для подписки \"%s\" очищено" -#: replication/logical/worker.c:4924 +#: replication/logical/worker.c:4946 #, c-format msgid "" "Remote transaction's finish WAL location (LSN) %X/%X did not match skip-LSN " @@ -23633,7 +23629,7 @@ msgstr "" "Позиция завершения удалённой транзакции в WAL (LSN) %X/%X не совпала со " "значением skip-LSN %X/%X." -#: replication/logical/worker.c:4950 +#: replication/logical/worker.c:4974 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23642,7 +23638,7 @@ msgstr "" "обработка внешних данных для источника репликации \"%s\" в контексте " "сообщения типа \"%s\"" -#: replication/logical/worker.c:4954 +#: replication/logical/worker.c:4978 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23651,7 +23647,7 @@ msgstr "" "обработка внешних данных из источника репликации \"%s\" в контексте " "сообщения типа \"%s\" в транзакции %u" -#: replication/logical/worker.c:4959 +#: replication/logical/worker.c:4983 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23660,7 +23656,7 @@ msgstr "" "обработка внешних данных для источника репликации \"%s\" в контексте " "сообщения типа \"%s\" в транзакции %u, конечная позиция %X/%X" -#: replication/logical/worker.c:4970 +#: replication/logical/worker.c:4994 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23670,7 +23666,7 @@ msgstr "" "сообщения типа \"%s\" для целевого отношения репликации \"%s.%s\" в " "транзакции %u" -#: replication/logical/worker.c:4977 +#: replication/logical/worker.c:5001 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23681,7 +23677,7 @@ msgstr "" "сообщения типа \"%s\" для целевого отношения репликации \"%s.%s\" в " "транзакции %u, конечная позиция %X/%X" -#: replication/logical/worker.c:4988 +#: replication/logical/worker.c:5012 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23692,7 +23688,7 @@ msgstr "" "сообщения типа \"%s\" для целевого отношения репликации \"%s.%s\", столбца " "\"%s\", в транзакции %u" -#: replication/logical/worker.c:4996 +#: replication/logical/worker.c:5020 #, c-format msgid "" "processing remote data for replication origin \"%s\" during message type " @@ -23825,7 +23821,7 @@ msgstr "используются все слоты репликации" msgid "Free one or increase \"max_replication_slots\"." msgstr "Освободите ненужный или увеличьте параметр \"max_replication_slots\"." -#: replication/slot.c:560 replication/slot.c:2467 replication/slotfuncs.c:661 +#: replication/slot.c:560 replication/slot.c:2463 replication/slotfuncs.c:661 #: utils/activity/pgstat_replslot.c:56 utils/adt/genfile.c:728 #, c-format msgid "replication slot \"%s\" does not exist" @@ -23876,7 +23872,7 @@ msgstr "изменить слот репликации \"%s\" нельзя" msgid "cannot enable failover for a replication slot on the standby" msgstr "сделать переносимым слот репликации на ведомом сервере нельзя" -#: replication/slot.c:969 replication/slot.c:1927 replication/slot.c:2311 +#: replication/slot.c:969 replication/slot.c:1923 replication/slot.c:2307 #, c-format msgid "could not remove directory \"%s\"" msgstr "ошибка при удалении каталога \"%s\"" @@ -23935,40 +23931,40 @@ msgstr "завершение процесса %d для освобождения msgid "invalidating obsolete replication slot \"%s\"" msgstr "аннулирование устаревшего слота репликации \"%s\"" -#: replication/slot.c:2249 +#: replication/slot.c:2245 #, c-format msgid "replication slot file \"%s\" has wrong magic number: %u instead of %u" msgstr "" "файл слота репликации \"%s\" имеет неправильную сигнатуру (%u вместо %u)" -#: replication/slot.c:2256 +#: replication/slot.c:2252 #, c-format msgid "replication slot file \"%s\" has unsupported version %u" msgstr "файл состояния snapbuild \"%s\" имеет неподдерживаемую версию %u" -#: replication/slot.c:2263 +#: replication/slot.c:2259 #, c-format msgid "replication slot file \"%s\" has corrupted length %u" msgstr "у файла слота репликации \"%s\" неверная длина: %u" -#: replication/slot.c:2299 +#: replication/slot.c:2295 #, c-format msgid "checksum mismatch for replication slot file \"%s\": is %u, should be %u" msgstr "" "в файле слота репликации \"%s\" неверная контрольная сумма (%u вместо %u)" -#: replication/slot.c:2335 +#: replication/slot.c:2331 #, c-format msgid "logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"" msgstr "" "существует слот логической репликации \"%s\", но \"wal_level\" < \"logical\"" -#: replication/slot.c:2337 +#: replication/slot.c:2333 #, c-format msgid "Change \"wal_level\" to be \"logical\" or higher." msgstr "Смените \"wal_level\" на \"logical\" или более высокий уровень." -#: replication/slot.c:2349 +#: replication/slot.c:2345 #, c-format msgid "" "logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = " @@ -23977,39 +23973,39 @@ msgstr "" "на ведомом сервере существует слот логической репликации \"%s\", но " "\"hot_standby\" = \"off\"" -#: replication/slot.c:2351 +#: replication/slot.c:2347 #, c-format msgid "Change \"hot_standby\" to be \"on\"." msgstr "Смените значение \"hot_standby\" на \"on\"." -#: replication/slot.c:2356 +#: replication/slot.c:2352 #, c-format msgid "" "physical replication slot \"%s\" exists, but \"wal_level\" < \"replica\"" msgstr "" "существует слот физической репликации \"%s\", но \"wal_level\" < \"replica\"" -#: replication/slot.c:2358 +#: replication/slot.c:2354 #, c-format msgid "Change \"wal_level\" to be \"replica\" or higher." msgstr "Смените \"wal_level\" на \"replica\" или более высокий уровень." -#: replication/slot.c:2400 +#: replication/slot.c:2396 #, c-format msgid "too many replication slots active before shutdown" msgstr "перед завершением активно слишком много слотов репликации" -#: replication/slot.c:2475 +#: replication/slot.c:2471 #, c-format msgid "\"%s\" is not a physical replication slot" msgstr "\"%s\" не является слотом физической репликации" -#: replication/slot.c:2654 +#: replication/slot.c:2650 #, c-format msgid "replication slot \"%s\" specified in parameter \"%s\" does not exist" msgstr "слот репликации \"%s\", указанный в параметре \"%s\", не существует" -#: replication/slot.c:2656 replication/slot.c:2690 replication/slot.c:2705 +#: replication/slot.c:2652 replication/slot.c:2686 replication/slot.c:2701 #, c-format msgid "" "Logical replication is waiting on the standby associated with replication " @@ -24018,30 +24014,30 @@ msgstr "" "Логическая репликация ожидает резервного сервера, связанного со слотом " "репликации \"%s\"." -#: replication/slot.c:2658 +#: replication/slot.c:2654 #, c-format msgid "Create the replication slot \"%s\" or amend parameter \"%s\"." msgstr "Создайте слот репликации \"%s\" или опустите параметр \"%s\"." -#: replication/slot.c:2668 +#: replication/slot.c:2664 #, c-format msgid "cannot specify logical replication slot \"%s\" in parameter \"%s\"" msgstr "" "слот логической репликации \"%s\" не может быть указан в параметре \"%s\"" -#: replication/slot.c:2670 +#: replication/slot.c:2666 #, c-format msgid "" "Logical replication is waiting for correction on replication slot \"%s\"." msgstr "Логическая репликация ожидает исправления слота репликации \"%s\"." -#: replication/slot.c:2672 +#: replication/slot.c:2668 #, c-format msgid "Remove the logical replication slot \"%s\" from parameter \"%s\"." msgstr "" "Удалите указание слота логической репликации \"%s\" из параметра \"%s\"." -#: replication/slot.c:2688 +#: replication/slot.c:2684 #, c-format msgid "" "physical replication slot \"%s\" specified in parameter \"%s\" has been " @@ -24050,14 +24046,14 @@ msgstr "" "слот физической репликации \"%s\", указанный в параметре \"%s\", был " "аннулирован" -#: replication/slot.c:2692 +#: replication/slot.c:2688 #, c-format msgid "" "Drop and recreate the replication slot \"%s\", or amend parameter \"%s\"." msgstr "" "Удалите и пересоздайте слот репликации \"%s\" или опустите параметр \"%s\"." -#: replication/slot.c:2703 +#: replication/slot.c:2699 #, c-format msgid "" "replication slot \"%s\" specified in parameter \"%s\" does not have " @@ -24066,7 +24062,7 @@ msgstr "" "у слота репликации \"%s\", указанного в параметре \"%s\", нулевое значение " "active_pid" -#: replication/slot.c:2707 +#: replication/slot.c:2703 #, c-format msgid "" "Start the standby associated with the replication slot \"%s\", or amend " @@ -24419,7 +24415,7 @@ msgstr "неверный тип сообщения резервного серв msgid "unexpected message type \"%c\"" msgstr "неожиданный тип сообщения \"%c\"" -#: replication/walsender.c:2774 +#: replication/walsender.c:2778 #, c-format msgid "terminating walsender process due to replication timeout" msgstr "завершение процесса передачи журнала из-за тайм-аута репликации" @@ -25140,105 +25136,105 @@ msgstr "ошибка удаления набора файлов \"%s\": %m" msgid "could not truncate file \"%s\": %m" msgstr "не удалось обрезать файл \"%s\": %m" -#: storage/file/fd.c:583 storage/file/fd.c:655 storage/file/fd.c:691 +#: storage/file/fd.c:580 storage/file/fd.c:652 storage/file/fd.c:688 #, c-format msgid "could not flush dirty data: %m" msgstr "не удалось сбросить грязные данные: %m" -#: storage/file/fd.c:613 +#: storage/file/fd.c:610 #, c-format msgid "could not determine dirty data size: %m" msgstr "не удалось определить размер грязных данных: %m" -#: storage/file/fd.c:665 +#: storage/file/fd.c:662 #, c-format msgid "could not munmap() while flushing data: %m" msgstr "ошибка в munmap() при сбросе данных на диск: %m" -#: storage/file/fd.c:983 +#: storage/file/fd.c:980 #, c-format msgid "getrlimit failed: %m" msgstr "ошибка в getrlimit(): %m" -#: storage/file/fd.c:1073 +#: storage/file/fd.c:1070 #, c-format msgid "insufficient file descriptors available to start server process" msgstr "недостаточно дескрипторов файлов для запуска серверного процесса" -#: storage/file/fd.c:1074 +#: storage/file/fd.c:1071 #, c-format msgid "System allows %d, server needs at least %d." msgstr "Система может выделить: %d, серверу требуется минимум: %d." -#: storage/file/fd.c:1162 storage/file/fd.c:2618 storage/file/fd.c:2727 -#: storage/file/fd.c:2878 +#: storage/file/fd.c:1159 storage/file/fd.c:2615 storage/file/fd.c:2724 +#: storage/file/fd.c:2875 #, c-format msgid "out of file descriptors: %m; release and retry" msgstr "нехватка дескрипторов файлов: %m; освободите их и повторите попытку" -#: storage/file/fd.c:1536 +#: storage/file/fd.c:1533 #, c-format msgid "temporary file: path \"%s\", size %lu" msgstr "временный файл: путь \"%s\", размер %lu" -#: storage/file/fd.c:1675 +#: storage/file/fd.c:1672 #, c-format msgid "cannot create temporary directory \"%s\": %m" msgstr "не удалось создать временный каталог \"%s\": %m" -#: storage/file/fd.c:1682 +#: storage/file/fd.c:1679 #, c-format msgid "cannot create temporary subdirectory \"%s\": %m" msgstr "не удалось создать временный подкаталог \"%s\": %m" -#: storage/file/fd.c:1879 +#: storage/file/fd.c:1876 #, c-format msgid "could not create temporary file \"%s\": %m" msgstr "не удалось создать временный файл \"%s\": %m" -#: storage/file/fd.c:1915 +#: storage/file/fd.c:1912 #, c-format msgid "could not open temporary file \"%s\": %m" msgstr "не удалось открыть временный файл \"%s\": %m" -#: storage/file/fd.c:1956 +#: storage/file/fd.c:1953 #, c-format msgid "could not unlink temporary file \"%s\": %m" msgstr "ошибка удаления временного файла \"%s\": %m" -#: storage/file/fd.c:2044 +#: storage/file/fd.c:2041 #, c-format msgid "could not delete file \"%s\": %m" msgstr "ошибка удаления файла \"%s\": %m" -#: storage/file/fd.c:2234 +#: storage/file/fd.c:2231 #, c-format msgid "temporary file size exceeds temp_file_limit (%dkB)" msgstr "размер временного файла превышает предел temp_file_limit (%d КБ)" -#: storage/file/fd.c:2594 storage/file/fd.c:2653 +#: storage/file/fd.c:2591 storage/file/fd.c:2650 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"" msgstr "превышен предел maxAllocatedDescs (%d) при попытке открыть файл \"%s\"" -#: storage/file/fd.c:2698 +#: storage/file/fd.c:2695 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to execute command \"%s\"" msgstr "" "превышен предел maxAllocatedDescs (%d) при попытке выполнить команду \"%s\"" -#: storage/file/fd.c:2854 +#: storage/file/fd.c:2851 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"" msgstr "" "превышен предел maxAllocatedDescs (%d) при попытке открыть каталог \"%s\"" -#: storage/file/fd.c:3384 +#: storage/file/fd.c:3381 #, c-format msgid "unexpected file found in temporary-files directory: \"%s\"" msgstr "в каталоге временных файлов обнаружен неуместный файл: \"%s\"" -#: storage/file/fd.c:3502 +#: storage/file/fd.c:3499 #, c-format msgid "" "syncing data directory (syncfs), elapsed time: %ld.%02d s, current path: %s" @@ -25246,7 +25242,7 @@ msgstr "" "синхронизация каталога данных (syncfs), прошло времени: %ld.%02d с, текущий " "путь: %s" -#: storage/file/fd.c:3729 +#: storage/file/fd.c:3726 #, c-format msgid "" "syncing data directory (pre-fsync), elapsed time: %ld.%02d s, current path: " @@ -25255,7 +25251,7 @@ msgstr "" "синхронизация каталога данных (подготовка к fsync), прошло времени: %ld.%02d " "с, текущий путь: %s" -#: storage/file/fd.c:3761 +#: storage/file/fd.c:3758 #, c-format msgid "" "syncing data directory (fsync), elapsed time: %ld.%02d s, current path: %s" @@ -25263,22 +25259,22 @@ msgstr "" "синхронизация каталога данных (fsync), прошло времени: %ld.%02d с, текущий " "путь: %s" -#: storage/file/fd.c:3950 +#: storage/file/fd.c:3947 #, c-format msgid "\"debug_io_direct\" is not supported on this platform." msgstr "Параметр \"debug_io_direct\" не поддерживается в этой ОС." -#: storage/file/fd.c:3964 +#: storage/file/fd.c:3961 #, c-format msgid "Invalid list syntax in parameter \"%s\"" msgstr "Неверный формат списка в параметре \"%s\"." -#: storage/file/fd.c:3984 +#: storage/file/fd.c:3981 #, c-format msgid "Invalid option \"%s\"" msgstr "Неверный параметр \"%s\"." -#: storage/file/fd.c:3997 +#: storage/file/fd.c:3994 #, c-format msgid "" "\"debug_io_direct\" is not supported for WAL because XLOG_BLCKSZ is too small" @@ -25286,7 +25282,7 @@ msgstr "" "режим \"debug_io_direct\" не поддерживается для WAL из-за слишком маленького " "размера XLOG_BLCKSZ" -#: storage/file/fd.c:4004 +#: storage/file/fd.c:4001 #, c-format msgid "" "\"debug_io_direct\" is not supported for data because BLCKSZ is too small" @@ -25666,108 +25662,108 @@ msgstr "обнаружена взаимоблокировка" msgid "See server log for query details." msgstr "Подробности запроса смотрите в протоколе сервера." -#: storage/lmgr/lmgr.c:848 +#: storage/lmgr/lmgr.c:854 #, c-format msgid "while updating tuple (%u,%u) in relation \"%s\"" msgstr "при изменении кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:851 +#: storage/lmgr/lmgr.c:857 #, c-format msgid "while deleting tuple (%u,%u) in relation \"%s\"" msgstr "при удалении кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:854 +#: storage/lmgr/lmgr.c:860 #, c-format msgid "while locking tuple (%u,%u) in relation \"%s\"" msgstr "при блокировке кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:857 +#: storage/lmgr/lmgr.c:863 #, c-format msgid "while locking updated version (%u,%u) of tuple in relation \"%s\"" msgstr "при блокировке изменённой версии (%u,%u) кортежа в отношении \"%s\"" -#: storage/lmgr/lmgr.c:860 +#: storage/lmgr/lmgr.c:866 #, c-format msgid "while inserting index tuple (%u,%u) in relation \"%s\"" msgstr "при добавлении кортежа индекса (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:863 +#: storage/lmgr/lmgr.c:869 #, c-format msgid "while checking uniqueness of tuple (%u,%u) in relation \"%s\"" msgstr "при проверке уникальности кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:866 +#: storage/lmgr/lmgr.c:872 #, c-format msgid "while rechecking updated tuple (%u,%u) in relation \"%s\"" msgstr "при перепроверке изменённого кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:869 +#: storage/lmgr/lmgr.c:875 #, c-format msgid "while checking exclusion constraint on tuple (%u,%u) in relation \"%s\"" msgstr "" "при проверке ограничения-исключения для кортежа (%u,%u) в отношении \"%s\"" -#: storage/lmgr/lmgr.c:1239 +#: storage/lmgr/lmgr.c:1245 #, c-format msgid "relation %u of database %u" msgstr "отношение %u базы данных %u" -#: storage/lmgr/lmgr.c:1245 +#: storage/lmgr/lmgr.c:1251 #, c-format msgid "extension of relation %u of database %u" msgstr "расширение отношения %u базы данных %u" -#: storage/lmgr/lmgr.c:1251 +#: storage/lmgr/lmgr.c:1257 #, c-format msgid "pg_database.datfrozenxid of database %u" msgstr "pg_database.datfrozenxid базы %u" -#: storage/lmgr/lmgr.c:1256 +#: storage/lmgr/lmgr.c:1262 #, c-format msgid "page %u of relation %u of database %u" msgstr "страница %u отношения %u базы данных %u" -#: storage/lmgr/lmgr.c:1263 +#: storage/lmgr/lmgr.c:1269 #, c-format msgid "tuple (%u,%u) of relation %u of database %u" msgstr "кортеж (%u,%u) отношения %u базы данных %u" -#: storage/lmgr/lmgr.c:1271 +#: storage/lmgr/lmgr.c:1277 #, c-format msgid "transaction %u" msgstr "транзакция %u" -#: storage/lmgr/lmgr.c:1276 +#: storage/lmgr/lmgr.c:1282 #, c-format msgid "virtual transaction %d/%u" msgstr "виртуальная транзакция %d/%u" -#: storage/lmgr/lmgr.c:1282 +#: storage/lmgr/lmgr.c:1288 #, c-format msgid "speculative token %u of transaction %u" msgstr "спекулятивный маркер %u транзакции %u" -#: storage/lmgr/lmgr.c:1288 +#: storage/lmgr/lmgr.c:1294 #, c-format msgid "object %u of class %u of database %u" msgstr "объект %u класса %u базы данных %u" -#: storage/lmgr/lmgr.c:1296 +#: storage/lmgr/lmgr.c:1302 #, c-format msgid "user lock [%u,%u,%u]" msgstr "пользовательская блокировка [%u,%u,%u]" -#: storage/lmgr/lmgr.c:1303 +#: storage/lmgr/lmgr.c:1309 #, c-format msgid "advisory lock [%u,%u,%u,%u]" msgstr "рекомендательная блокировка [%u,%u,%u,%u]" -#: storage/lmgr/lmgr.c:1311 +#: storage/lmgr/lmgr.c:1317 #, c-format msgid "remote transaction %u of subscription %u of database %u" msgstr "удалённая транзакция %u подписки %u в базе данных %u" -#: storage/lmgr/lmgr.c:1318 +#: storage/lmgr/lmgr.c:1324 #, c-format msgid "unrecognized locktag type %d" msgstr "нераспознанный тип блокировки %d" @@ -26584,12 +26580,12 @@ msgstr "" "число форматов результатов в сообщении Bind (%d) не равно числу столбцов в " "запросе (%d)" -#: tcop/pquery.c:942 tcop/pquery.c:1696 +#: tcop/pquery.c:942 tcop/pquery.c:1687 #, c-format msgid "cursor can only scan forward" msgstr "курсор может сканировать только вперёд" -#: tcop/pquery.c:943 tcop/pquery.c:1697 +#: tcop/pquery.c:943 tcop/pquery.c:1688 #, c-format msgid "Declare it with SCROLL option to enable backward scan." msgstr "Добавьте в его объявление SCROLL, чтобы он мог перемещаться назад." @@ -26791,7 +26787,7 @@ msgid "invalid regular expression: %s" msgstr "неверное регулярное выражение: %s" #: tsearch/spell.c:964 tsearch/spell.c:981 tsearch/spell.c:998 -#: tsearch/spell.c:1015 tsearch/spell.c:1081 gram.y:18735 gram.y:18752 +#: tsearch/spell.c:1015 tsearch/spell.c:1081 gram.y:18742 gram.y:18759 #, c-format msgid "syntax error" msgstr "ошибка синтаксиса" @@ -26954,102 +26950,102 @@ msgstr "событие ожидания \"%s\" в типе \"%s\" уже сущ msgid "too many custom wait events" msgstr "слишком много пользовательских событий ожидания" -#: utils/adt/acl.c:183 utils/adt/name.c:93 +#: utils/adt/acl.c:200 utils/adt/name.c:93 #, c-format msgid "identifier too long" msgstr "слишком длинный идентификатор" -#: utils/adt/acl.c:184 utils/adt/name.c:94 +#: utils/adt/acl.c:201 utils/adt/name.c:94 #, c-format msgid "Identifier must be less than %d characters." msgstr "Идентификатор должен быть короче %d байт." -#: utils/adt/acl.c:272 +#: utils/adt/acl.c:289 #, c-format msgid "unrecognized key word: \"%s\"" msgstr "нераспознанное ключевое слово: \"%s\"" -#: utils/adt/acl.c:273 +#: utils/adt/acl.c:290 #, c-format msgid "ACL key word must be \"group\" or \"user\"." msgstr "Ключевым словом ACL должно быть \"group\" или \"user\"." -#: utils/adt/acl.c:281 +#: utils/adt/acl.c:298 #, c-format msgid "missing name" msgstr "отсутствует имя" -#: utils/adt/acl.c:282 +#: utils/adt/acl.c:299 #, c-format msgid "A name must follow the \"group\" or \"user\" key word." msgstr "За ключевыми словами \"group\" или \"user\" должно следовать имя." -#: utils/adt/acl.c:288 +#: utils/adt/acl.c:305 #, c-format msgid "missing \"=\" sign" msgstr "отсутствует знак \"=\"" -#: utils/adt/acl.c:350 +#: utils/adt/acl.c:367 #, c-format msgid "invalid mode character: must be one of \"%s\"" msgstr "неверный символ режима: должен быть один из \"%s\"" -#: utils/adt/acl.c:380 +#: utils/adt/acl.c:397 #, c-format msgid "a name must follow the \"/\" sign" msgstr "за знаком \"/\" должно следовать имя" -#: utils/adt/acl.c:392 +#: utils/adt/acl.c:409 #, c-format msgid "defaulting grantor to user ID %u" msgstr "назначившим права считается пользователь с ID %u" -#: utils/adt/acl.c:578 +#: utils/adt/acl.c:595 #, c-format msgid "ACL array contains wrong data type" msgstr "Массив ACL содержит неверный тип данных" -#: utils/adt/acl.c:582 +#: utils/adt/acl.c:599 #, c-format msgid "ACL arrays must be one-dimensional" msgstr "Массивы ACL должны быть одномерными" -#: utils/adt/acl.c:586 +#: utils/adt/acl.c:603 #, c-format msgid "ACL arrays must not contain null values" msgstr "Массивы ACL не должны содержать значения null" -#: utils/adt/acl.c:615 +#: utils/adt/acl.c:632 #, c-format msgid "extra garbage at the end of the ACL specification" msgstr "лишний мусор в конце спецификации ACL" -#: utils/adt/acl.c:1263 +#: utils/adt/acl.c:1280 #, c-format msgid "grant options cannot be granted back to your own grantor" msgstr "привилегию назначения прав нельзя вернуть тому, кто назначил её вам" -#: utils/adt/acl.c:1579 +#: utils/adt/acl.c:1596 #, c-format msgid "aclinsert is no longer supported" msgstr "aclinsert больше не поддерживается" -#: utils/adt/acl.c:1589 +#: utils/adt/acl.c:1606 #, c-format msgid "aclremove is no longer supported" msgstr "aclremove больше не поддерживается" -#: utils/adt/acl.c:1709 +#: utils/adt/acl.c:1726 #, c-format msgid "unrecognized privilege type: \"%s\"" msgstr "нераспознанный тип прав: \"%s\"" -#: utils/adt/acl.c:3550 utils/adt/regproc.c:100 utils/adt/regproc.c:265 +#: utils/adt/acl.c:3567 utils/adt/regproc.c:100 utils/adt/regproc.c:265 #, c-format msgid "function \"%s\" does not exist" msgstr "функция \"%s\" не существует" -#: utils/adt/acl.c:5196 +#: utils/adt/acl.c:5213 #, c-format msgid "must be able to SET ROLE \"%s\"" msgstr "нужны права для выполнения SET ROLE \"%s\"" @@ -27075,7 +27071,7 @@ msgstr "тип входных данных не является массиво #: utils/adt/int.c:1025 utils/adt/int.c:1058 utils/adt/int.c:1072 #: utils/adt/int.c:1086 utils/adt/int.c:1117 utils/adt/int.c:1199 #: utils/adt/int.c:1263 utils/adt/int.c:1331 utils/adt/int.c:1337 -#: utils/adt/int8.c:1256 utils/adt/numeric.c:1917 utils/adt/numeric.c:4454 +#: utils/adt/int8.c:1256 utils/adt/numeric.c:1918 utils/adt/numeric.c:4455 #: utils/adt/rangetypes.c:1488 utils/adt/rangetypes.c:1501 #: utils/adt/varbit.c:1195 utils/adt/varbit.c:1596 utils/adt/varlena.c:1135 #: utils/adt/varlena.c:3137 @@ -27446,8 +27442,8 @@ msgstr "преобразование кодировки из %s в ASCII не п #: utils/adt/geo_ops.c:3428 utils/adt/geo_ops.c:4650 utils/adt/geo_ops.c:4665 #: utils/adt/geo_ops.c:4672 utils/adt/int.c:174 utils/adt/int.c:186 #: utils/adt/jsonpath.c:185 utils/adt/mac.c:94 utils/adt/mac8.c:226 -#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7221 -#: utils/adt/numeric.c:7424 utils/adt/numeric.c:8371 utils/adt/numutils.c:356 +#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7222 +#: utils/adt/numeric.c:7425 utils/adt/numeric.c:8372 utils/adt/numutils.c:356 #: utils/adt/numutils.c:618 utils/adt/numutils.c:880 utils/adt/numutils.c:919 #: utils/adt/numutils.c:941 utils/adt/numutils.c:1005 utils/adt/numutils.c:1027 #: utils/adt/pg_lsn.c:73 utils/adt/tid.c:72 utils/adt/tid.c:80 @@ -27468,10 +27464,10 @@ msgstr "денежное значение вне диапазона" #: utils/adt/int.c:1101 utils/adt/int.c:1139 utils/adt/int.c:1167 #: utils/adt/int8.c:514 utils/adt/int8.c:572 utils/adt/int8.c:942 #: utils/adt/int8.c:1022 utils/adt/int8.c:1084 utils/adt/int8.c:1164 -#: utils/adt/numeric.c:3191 utils/adt/numeric.c:3214 utils/adt/numeric.c:3299 -#: utils/adt/numeric.c:3317 utils/adt/numeric.c:3413 utils/adt/numeric.c:8920 -#: utils/adt/numeric.c:9233 utils/adt/numeric.c:9581 utils/adt/numeric.c:9697 -#: utils/adt/numeric.c:11208 utils/adt/timestamp.c:3713 +#: utils/adt/numeric.c:3192 utils/adt/numeric.c:3215 utils/adt/numeric.c:3300 +#: utils/adt/numeric.c:3318 utils/adt/numeric.c:3414 utils/adt/numeric.c:8921 +#: utils/adt/numeric.c:9234 utils/adt/numeric.c:9582 utils/adt/numeric.c:9698 +#: utils/adt/numeric.c:11209 utils/adt/timestamp.c:3713 #, c-format msgid "division by zero" msgstr "деление на ноль" @@ -27518,7 +27514,7 @@ msgid "date out of range: \"%s\"" msgstr "дата вне диапазона: \"%s\"" #: utils/adt/date.c:222 utils/adt/date.c:520 utils/adt/date.c:544 -#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2552 +#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2554 #, c-format msgid "date out of range" msgstr "дата вне диапазона" @@ -27592,8 +27588,8 @@ msgstr "единица \"%s\" для типа %s не распознана" #: utils/adt/timestamp.c:6260 utils/adt/timestamp.c:6347 #: utils/adt/timestamp.c:6388 utils/adt/timestamp.c:6392 #: utils/adt/timestamp.c:6446 utils/adt/timestamp.c:6450 -#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2574 -#: utils/adt/xml.c:2581 utils/adt/xml.c:2601 utils/adt/xml.c:2608 +#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2576 +#: utils/adt/xml.c:2583 utils/adt/xml.c:2603 utils/adt/xml.c:2610 #, c-format msgid "timestamp out of range" msgstr "timestamp вне диапазона" @@ -27625,7 +27621,7 @@ msgstr "бесконечный интервал нельзя вычесть из #: utils/adt/date.c:2115 utils/adt/date.c:2667 utils/adt/float.c:1036 #: utils/adt/float.c:1112 utils/adt/int.c:635 utils/adt/int.c:682 -#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2595 +#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2596 #: utils/adt/timestamp.c:3810 utils/adt/timestamp.c:3847 #: utils/adt/timestamp.c:3888 #, c-format @@ -27821,34 +27817,34 @@ msgstr "\"%s\" вне диапазона для типа double precision" #: utils/adt/float.c:1247 utils/adt/float.c:1321 utils/adt/int.c:355 #: utils/adt/int.c:893 utils/adt/int.c:915 utils/adt/int.c:929 #: utils/adt/int.c:943 utils/adt/int.c:975 utils/adt/int.c:1213 -#: utils/adt/int8.c:1277 utils/adt/numeric.c:4593 utils/adt/numeric.c:4598 +#: utils/adt/int8.c:1277 utils/adt/numeric.c:4594 utils/adt/numeric.c:4599 #, c-format msgid "smallint out of range" msgstr "smallint вне диапазона" -#: utils/adt/float.c:1447 utils/adt/numeric.c:3709 utils/adt/numeric.c:10112 +#: utils/adt/float.c:1447 utils/adt/numeric.c:3710 utils/adt/numeric.c:10113 #, c-format msgid "cannot take square root of a negative number" msgstr "извлечь квадратный корень отрицательного числа нельзя" -#: utils/adt/float.c:1515 utils/adt/numeric.c:3997 utils/adt/numeric.c:4109 +#: utils/adt/float.c:1515 utils/adt/numeric.c:3998 utils/adt/numeric.c:4110 #, c-format msgid "zero raised to a negative power is undefined" msgstr "ноль в отрицательной степени даёт неопределённость" -#: utils/adt/float.c:1519 utils/adt/numeric.c:4001 utils/adt/numeric.c:11003 +#: utils/adt/float.c:1519 utils/adt/numeric.c:4002 utils/adt/numeric.c:11004 #, c-format msgid "a negative number raised to a non-integer power yields a complex result" msgstr "отрицательное число в дробной степени даёт комплексный результат" -#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3909 -#: utils/adt/numeric.c:10783 +#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3910 +#: utils/adt/numeric.c:10784 #, c-format msgid "cannot take logarithm of zero" msgstr "вычислить логарифм нуля нельзя" -#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3847 -#: utils/adt/numeric.c:3904 utils/adt/numeric.c:10787 +#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3848 +#: utils/adt/numeric.c:3905 utils/adt/numeric.c:10788 #, c-format msgid "cannot take logarithm of a negative number" msgstr "вычислить логарифм отрицательного числа нельзя" @@ -27862,22 +27858,22 @@ msgstr "вычислить логарифм отрицательного чис msgid "input is out of range" msgstr "введённое значение вне диапазона" -#: utils/adt/float.c:4000 utils/adt/numeric.c:1857 +#: utils/adt/float.c:4000 utils/adt/numeric.c:1858 #, c-format msgid "count must be greater than zero" msgstr "счётчик должен быть больше нуля" -#: utils/adt/float.c:4005 utils/adt/numeric.c:1868 +#: utils/adt/float.c:4005 utils/adt/numeric.c:1869 #, c-format msgid "operand, lower bound, and upper bound cannot be NaN" msgstr "операнд, нижняя и верхняя границы не могут быть NaN" -#: utils/adt/float.c:4011 utils/adt/numeric.c:1873 +#: utils/adt/float.c:4011 utils/adt/numeric.c:1874 #, c-format msgid "lower and upper bounds must be finite" msgstr "нижняя и верхняя границы должны быть конечными" -#: utils/adt/float.c:4077 utils/adt/numeric.c:1887 +#: utils/adt/float.c:4077 utils/adt/numeric.c:1888 #, c-format msgid "lower bound cannot equal upper bound" msgstr "нижняя граница не может равняться верхней" @@ -28255,7 +28251,7 @@ msgstr "размер шага не может быть нулевым" #: utils/adt/int8.c:994 utils/adt/int8.c:1008 utils/adt/int8.c:1041 #: utils/adt/int8.c:1055 utils/adt/int8.c:1069 utils/adt/int8.c:1100 #: utils/adt/int8.c:1122 utils/adt/int8.c:1136 utils/adt/int8.c:1150 -#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4542 +#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4543 #: utils/adt/rangetypes.c:1535 utils/adt/rangetypes.c:1548 #: utils/adt/varbit.c:1676 #, c-format @@ -28310,7 +28306,7 @@ msgstr "массив должен иметь два столбца" msgid "mismatched array dimensions" msgstr "неподходящие размерности массива" -#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1956 +#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1962 #, c-format msgid "duplicate JSON object key value" msgstr "повторяющиеся ключи в объекте JSON" @@ -28377,23 +28373,23 @@ msgstr "привести объект jsonb к типу %s нельзя" msgid "cannot cast jsonb array or object to type %s" msgstr "привести массив или объект jsonb к типу %s нельзя" -#: utils/adt/jsonb_util.c:756 +#: utils/adt/jsonb_util.c:753 #, c-format msgid "number of jsonb object pairs exceeds the maximum allowed (%zu)" msgstr "число пар объекта jsonb превышает предел (%zu)" -#: utils/adt/jsonb_util.c:797 +#: utils/adt/jsonb_util.c:794 #, c-format msgid "number of jsonb array elements exceeds the maximum allowed (%zu)" msgstr "число элементов массива jsonb превышает предел (%zu)" -#: utils/adt/jsonb_util.c:1671 utils/adt/jsonb_util.c:1691 +#: utils/adt/jsonb_util.c:1677 utils/adt/jsonb_util.c:1697 #, c-format msgid "total size of jsonb array elements exceeds the maximum of %d bytes" msgstr "общий размер элементов массива jsonb превышает предел (%d байт)" -#: utils/adt/jsonb_util.c:1752 utils/adt/jsonb_util.c:1787 -#: utils/adt/jsonb_util.c:1807 +#: utils/adt/jsonb_util.c:1758 utils/adt/jsonb_util.c:1793 +#: utils/adt/jsonb_util.c:1813 #, c-format msgid "total size of jsonb object elements exceeds the maximum of %d bytes" msgstr "общий размер элементов объекта jsonb превышает предел (%d байт)" @@ -28974,12 +28970,12 @@ msgstr "недетерминированные правила сортировк msgid "LIKE pattern must not end with escape character" msgstr "шаблон LIKE не должен заканчиваться защитным символом" -#: utils/adt/like_match.c:293 utils/adt/regexp.c:800 +#: utils/adt/like_match.c:293 utils/adt/regexp.c:803 #, c-format msgid "invalid escape string" msgstr "неверный защитный символ" -#: utils/adt/like_match.c:294 utils/adt/regexp.c:801 +#: utils/adt/like_match.c:294 utils/adt/regexp.c:804 #, c-format msgid "Escape string must be empty or one character." msgstr "Защитный символ должен быть пустым или состоять из одного байта." @@ -29195,10 +29191,10 @@ msgstr "результат вне диапазона" msgid "cannot subtract inet values of different sizes" msgstr "нельзя вычитать значения inet разного размера" -#: utils/adt/numeric.c:793 utils/adt/numeric.c:3659 utils/adt/numeric.c:7216 -#: utils/adt/numeric.c:7419 utils/adt/numeric.c:7891 utils/adt/numeric.c:10586 -#: utils/adt/numeric.c:11061 utils/adt/numeric.c:11155 -#: utils/adt/numeric.c:11290 +#: utils/adt/numeric.c:793 utils/adt/numeric.c:3660 utils/adt/numeric.c:7217 +#: utils/adt/numeric.c:7420 utils/adt/numeric.c:7892 utils/adt/numeric.c:10587 +#: utils/adt/numeric.c:11062 utils/adt/numeric.c:11156 +#: utils/adt/numeric.c:11291 #, c-format msgid "value overflows numeric format" msgstr "значение переполняет формат numeric" @@ -29263,54 +29259,54 @@ msgstr "размер шага не может быть NaN" msgid "step size cannot be infinity" msgstr "размер шага не может быть бесконечностью" -#: utils/adt/numeric.c:3649 +#: utils/adt/numeric.c:3650 #, c-format msgid "factorial of a negative number is undefined" msgstr "факториал отрицательного числа даёт неопределённость" -#: utils/adt/numeric.c:4256 +#: utils/adt/numeric.c:4257 #, c-format msgid "lower bound cannot be NaN" msgstr "нижняя граница не может быть NaN" -#: utils/adt/numeric.c:4260 +#: utils/adt/numeric.c:4261 #, c-format msgid "lower bound cannot be infinity" msgstr "нижняя граница не может быть бесконечностью" -#: utils/adt/numeric.c:4267 +#: utils/adt/numeric.c:4268 #, c-format msgid "upper bound cannot be NaN" msgstr "верхняя граница не может быть NaN" -#: utils/adt/numeric.c:4271 +#: utils/adt/numeric.c:4272 #, c-format msgid "upper bound cannot be infinity" msgstr "верхняя граница не может быть бесконечностью" -#: utils/adt/numeric.c:4432 utils/adt/numeric.c:4520 utils/adt/numeric.c:4580 -#: utils/adt/numeric.c:4776 +#: utils/adt/numeric.c:4433 utils/adt/numeric.c:4521 utils/adt/numeric.c:4581 +#: utils/adt/numeric.c:4777 #, c-format msgid "cannot convert NaN to %s" msgstr "нельзя преобразовать NaN в %s" -#: utils/adt/numeric.c:4436 utils/adt/numeric.c:4524 utils/adt/numeric.c:4584 -#: utils/adt/numeric.c:4780 +#: utils/adt/numeric.c:4437 utils/adt/numeric.c:4525 utils/adt/numeric.c:4585 +#: utils/adt/numeric.c:4781 #, c-format msgid "cannot convert infinity to %s" msgstr "нельзя представить бесконечность в %s" -#: utils/adt/numeric.c:4789 +#: utils/adt/numeric.c:4790 #, c-format msgid "pg_lsn out of range" msgstr "pg_lsn вне диапазона" -#: utils/adt/numeric.c:7981 utils/adt/numeric.c:8032 +#: utils/adt/numeric.c:7982 utils/adt/numeric.c:8033 #, c-format msgid "numeric field overflow" msgstr "переполнение поля numeric" -#: utils/adt/numeric.c:7982 +#: utils/adt/numeric.c:7983 #, c-format msgid "" "A field with precision %d, scale %d must round to an absolute value less " @@ -29319,13 +29315,13 @@ msgstr "" "Поле с точностью %d, порядком %d должно округляться до абсолютного значения " "меньше чем %s%d." -#: utils/adt/numeric.c:8033 +#: utils/adt/numeric.c:8034 #, c-format msgid "A field with precision %d, scale %d cannot hold an infinite value." msgstr "" "Поле с точностью %d, порядком %d не может содержать значение бесконечности." -#: utils/adt/numeric.c:11359 utils/adt/pseudorandomfuncs.c:135 +#: utils/adt/numeric.c:11360 utils/adt/pseudorandomfuncs.c:135 #: utils/adt/pseudorandomfuncs.c:159 #, c-format msgid "lower bound must be less than or equal to upper bound" @@ -29658,7 +29654,7 @@ msgstr "Слишком много запятых." msgid "Junk after right parenthesis or bracket." msgstr "Мусор после правой скобки." -#: utils/adt/regexp.c:304 utils/adt/regexp.c:1996 utils/adt/varlena.c:4273 +#: utils/adt/regexp.c:304 utils/adt/regexp.c:2022 utils/adt/varlena.c:4273 #, c-format msgid "regular expression failed: %s" msgstr "ошибка в регулярном выражении: %s" @@ -29677,15 +29673,15 @@ msgstr "" "Если вы хотите вызвать regexp_replace() с параметром start, явно приведите " "четвёртый аргумент к целочисленному типу." -#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1082 -#: utils/adt/regexp.c:1146 utils/adt/regexp.c:1155 utils/adt/regexp.c:1164 -#: utils/adt/regexp.c:1173 utils/adt/regexp.c:1853 utils/adt/regexp.c:1862 -#: utils/adt/regexp.c:1871 utils/misc/guc.c:6820 utils/misc/guc.c:6854 +#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1108 +#: utils/adt/regexp.c:1172 utils/adt/regexp.c:1181 utils/adt/regexp.c:1190 +#: utils/adt/regexp.c:1199 utils/adt/regexp.c:1879 utils/adt/regexp.c:1888 +#: utils/adt/regexp.c:1897 utils/misc/guc.c:6831 utils/misc/guc.c:6865 #, c-format msgid "invalid value for parameter \"%s\": %d" msgstr "неверное значение параметра \"%s\": %d" -#: utils/adt/regexp.c:936 +#: utils/adt/regexp.c:939 #, c-format msgid "" "SQL regular expression may not contain more than two escape-double-quote " @@ -29695,19 +29691,19 @@ msgstr "" "(экранированных кавычек)" #. translator: %s is a SQL function name -#: utils/adt/regexp.c:1093 utils/adt/regexp.c:1184 utils/adt/regexp.c:1271 -#: utils/adt/regexp.c:1310 utils/adt/regexp.c:1698 utils/adt/regexp.c:1753 -#: utils/adt/regexp.c:1882 +#: utils/adt/regexp.c:1119 utils/adt/regexp.c:1210 utils/adt/regexp.c:1297 +#: utils/adt/regexp.c:1336 utils/adt/regexp.c:1724 utils/adt/regexp.c:1779 +#: utils/adt/regexp.c:1908 #, c-format msgid "%s does not support the \"global\" option" msgstr "%s не поддерживает режим \"global\"" -#: utils/adt/regexp.c:1312 +#: utils/adt/regexp.c:1338 #, c-format msgid "Use the regexp_matches function instead." msgstr "Вместо неё используйте функцию regexp_matches." -#: utils/adt/regexp.c:1500 +#: utils/adt/regexp.c:1526 #, c-format msgid "too many regular expression matches" msgstr "слишком много совпадений для регулярного выражения" @@ -29733,8 +29729,8 @@ msgid "Use NONE to denote the missing argument of a unary operator." msgstr "" "Чтобы обозначить отсутствующий аргумент унарного оператора, укажите NONE." -#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10516 -#: utils/adt/ruleutils.c:10729 +#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10526 +#: utils/adt/ruleutils.c:10739 #, c-format msgid "too many arguments" msgstr "слишком много аргументов" @@ -30480,32 +30476,32 @@ msgstr "ошибка в XML-комментарии" msgid "not an XML document" msgstr "не XML-документ" -#: utils/adt/xml.c:1008 utils/adt/xml.c:1031 +#: utils/adt/xml.c:1020 utils/adt/xml.c:1043 #, c-format msgid "invalid XML processing instruction" msgstr "неправильная XML-инструкция обработки (PI)" -#: utils/adt/xml.c:1009 +#: utils/adt/xml.c:1021 #, c-format msgid "XML processing instruction target name cannot be \"%s\"." msgstr "назначением XML-инструкции обработки (PI) не может быть \"%s\"." -#: utils/adt/xml.c:1032 +#: utils/adt/xml.c:1044 #, c-format msgid "XML processing instruction cannot contain \"?>\"." msgstr "XML-инструкция обработки (PI) не может содержать \"?>\"." -#: utils/adt/xml.c:1111 +#: utils/adt/xml.c:1123 #, c-format msgid "xmlvalidate is not implemented" msgstr "функция xmlvalidate не реализована" -#: utils/adt/xml.c:1167 +#: utils/adt/xml.c:1179 #, c-format msgid "could not initialize XML library" msgstr "не удалось инициализировать библиотеку XML" -#: utils/adt/xml.c:1168 +#: utils/adt/xml.c:1180 #, c-format msgid "" "libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu." @@ -30513,12 +30509,12 @@ msgstr "" "В libxml2 оказался несовместимый тип char: sizeof(char)=%zu, " "sizeof(xmlChar)=%zu." -#: utils/adt/xml.c:1254 +#: utils/adt/xml.c:1266 #, c-format msgid "could not set up XML error handler" msgstr "не удалось установить обработчик XML-ошибок" -#: utils/adt/xml.c:1255 +#: utils/adt/xml.c:1267 #, c-format msgid "" "This probably indicates that the version of libxml2 being used is not " @@ -30527,99 +30523,99 @@ msgstr "" "Возможно, это означает, что используемая версия libxml2 несовместима с " "заголовочными файлами libxml2, с которыми был собран PostgreSQL." -#: utils/adt/xml.c:2281 +#: utils/adt/xml.c:2283 msgid "Invalid character value." msgstr "Неверный символ." -#: utils/adt/xml.c:2284 +#: utils/adt/xml.c:2286 msgid "Space required." msgstr "Требуется пробел." -#: utils/adt/xml.c:2287 +#: utils/adt/xml.c:2289 msgid "standalone accepts only 'yes' or 'no'." msgstr "значениями атрибута standalone могут быть только 'yes' и 'no'." -#: utils/adt/xml.c:2290 +#: utils/adt/xml.c:2292 msgid "Malformed declaration: missing version." msgstr "Ошибочное объявление: не указана версия." -#: utils/adt/xml.c:2293 +#: utils/adt/xml.c:2295 msgid "Missing encoding in text declaration." msgstr "В объявлении не указана кодировка." -#: utils/adt/xml.c:2296 +#: utils/adt/xml.c:2298 msgid "Parsing XML declaration: '?>' expected." msgstr "Ошибка при разборе XML-объявления: ожидается '?>'." -#: utils/adt/xml.c:2299 +#: utils/adt/xml.c:2301 #, c-format msgid "Unrecognized libxml error code: %d." msgstr "Нераспознанный код ошибки libxml: %d." -#: utils/adt/xml.c:2553 +#: utils/adt/xml.c:2555 #, c-format msgid "XML does not support infinite date values." msgstr "XML не поддерживает бесконечность в датах." -#: utils/adt/xml.c:2575 utils/adt/xml.c:2602 +#: utils/adt/xml.c:2577 utils/adt/xml.c:2604 #, c-format msgid "XML does not support infinite timestamp values." msgstr "XML не поддерживает бесконечность в timestamp." -#: utils/adt/xml.c:3018 +#: utils/adt/xml.c:3020 #, c-format msgid "invalid query" msgstr "неверный запрос" -#: utils/adt/xml.c:3110 +#: utils/adt/xml.c:3112 #, c-format msgid "portal \"%s\" does not return tuples" msgstr "портал \"%s\" не возвращает кортежи" -#: utils/adt/xml.c:4362 +#: utils/adt/xml.c:4364 #, c-format msgid "invalid array for XML namespace mapping" msgstr "неправильный массив с сопоставлениями пространств имён XML" -#: utils/adt/xml.c:4363 +#: utils/adt/xml.c:4365 #, c-format msgid "" "The array must be two-dimensional with length of the second axis equal to 2." msgstr "Массив должен быть двухмерным и содержать 2 элемента по второй оси." -#: utils/adt/xml.c:4387 +#: utils/adt/xml.c:4389 #, c-format msgid "empty XPath expression" msgstr "пустое выражение XPath" -#: utils/adt/xml.c:4439 +#: utils/adt/xml.c:4441 #, c-format msgid "neither namespace name nor URI may be null" msgstr "ни префикс, ни URI пространства имён не может быть null" -#: utils/adt/xml.c:4446 +#: utils/adt/xml.c:4448 #, c-format msgid "could not register XML namespace with name \"%s\" and URI \"%s\"" msgstr "" "не удалось зарегистрировать пространство имён XML с префиксом \"%s\" и URI " "\"%s\"" -#: utils/adt/xml.c:4795 +#: utils/adt/xml.c:4797 #, c-format msgid "DEFAULT namespace is not supported" msgstr "пространство имён DEFAULT не поддерживается" -#: utils/adt/xml.c:4824 +#: utils/adt/xml.c:4826 #, c-format msgid "row path filter must not be empty string" msgstr "путь отбираемых строк не должен быть пустым" -#: utils/adt/xml.c:4858 +#: utils/adt/xml.c:4860 #, c-format msgid "column path filter must not be empty string" msgstr "путь отбираемого столбца не должен быть пустым" -#: utils/adt/xml.c:5005 +#: utils/adt/xml.c:5007 #, c-format msgid "more than one value returned by column XPath expression" msgstr "выражение XPath, отбирающее столбец, возвратило более одного значения" @@ -31122,7 +31118,7 @@ msgstr "" msgid "could not write lock file \"%s\": %m" msgstr "не удалось записать файл блокировки \"%s\": %m" -#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5765 +#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5770 #, c-format msgid "could not read from file \"%s\": %m" msgstr "не удалось прочитать файл \"%s\": %m" @@ -31429,7 +31425,7 @@ msgstr "ошибка в bind_textdomain_codeset" msgid "invalid byte sequence for encoding \"%s\": %s" msgstr "неверная последовательность байт для кодировки \"%s\": %s" -#: utils/mb/mbutils.c:1751 +#: utils/mb/mbutils.c:1759 #, c-format msgid "" "character with byte sequence %s in encoding \"%s\" has no equivalent in " @@ -31625,7 +31621,7 @@ msgid "parameter \"%s\" cannot be changed now" msgstr "параметр \"%s\" нельзя изменить сейчас" #: utils/misc/guc.c:3541 utils/misc/guc.c:3603 utils/misc/guc.c:4671 -#: utils/misc/guc.c:6756 +#: utils/misc/guc.c:6767 #, c-format msgid "permission denied to set parameter \"%s\"" msgstr "нет прав для изменения параметра \"%s\"" @@ -31652,12 +31648,12 @@ msgstr "параметр \"%s\" нельзя сбросить" msgid "parameter \"%s\" cannot be set locally in functions" msgstr "параметр \"%s\" нельзя задавать локально в функциях" -#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5450 +#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5455 #, c-format msgid "permission denied to examine \"%s\"" msgstr "нет прав для просмотра параметра \"%s\"" -#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5451 +#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5456 #, c-format msgid "" "Only roles with privileges of the \"%s\" role may examine this parameter." @@ -31673,47 +31669,47 @@ msgstr "команда ALTER SYSTEM запрещена в этом окруже msgid "permission denied to perform ALTER SYSTEM RESET ALL" msgstr "нет прав для выполнения ALTER SYSTEM RESET ALL" -#: utils/misc/guc.c:4740 +#: utils/misc/guc.c:4745 #, c-format msgid "parameter value for ALTER SYSTEM must not contain a newline" msgstr "значение параметра для ALTER SYSTEM не должно быть многострочным" -#: utils/misc/guc.c:4785 +#: utils/misc/guc.c:4790 #, c-format msgid "could not parse contents of file \"%s\"" msgstr "не удалось разобрать содержимое файла \"%s\"" -#: utils/misc/guc.c:4967 +#: utils/misc/guc.c:4972 #, c-format msgid "attempt to redefine parameter \"%s\"" msgstr "попытка переопределить параметр \"%s\"" -#: utils/misc/guc.c:5306 +#: utils/misc/guc.c:5311 #, c-format msgid "invalid configuration parameter name \"%s\", removing it" msgstr "неверное имя параметра конфигурации: \"%s\", он удаляется" -#: utils/misc/guc.c:5308 +#: utils/misc/guc.c:5313 #, c-format msgid "\"%s\" is now a reserved prefix." msgstr "Теперь \"%s\" — зарезервированный префикс." -#: utils/misc/guc.c:6179 +#: utils/misc/guc.c:6184 #, c-format msgid "while setting parameter \"%s\" to \"%s\"" msgstr "при назначении параметру \"%s\" значения \"%s\"" -#: utils/misc/guc.c:6348 +#: utils/misc/guc.c:6353 #, c-format msgid "parameter \"%s\" could not be set" msgstr "параметр \"%s\" нельзя установить" -#: utils/misc/guc.c:6438 +#: utils/misc/guc.c:6443 #, c-format msgid "could not parse setting for parameter \"%s\"" msgstr "не удалось разобрать значение параметра \"%s\"" -#: utils/misc/guc.c:6888 +#: utils/misc/guc.c:6899 #, c-format msgid "invalid value for parameter \"%s\": %g" msgstr "неверное значение параметра \"%s\": %g" @@ -34944,109 +34940,114 @@ msgstr "" msgid "unrecognized column option \"%s\"" msgstr "нераспознанный параметр столбца \"%s\"" -#: gram.y:14147 +#: gram.y:14098 +#, c-format +msgid "option name \"%s\" cannot be used in XMLTABLE" +msgstr "имя параметра \"%s\" не может использоваться в XMLTABLE" + +#: gram.y:14154 #, c-format msgid "only string constants are supported in JSON_TABLE path specification" msgstr "в указании пути JSON_TABLE поддерживаются только строковые константы" -#: gram.y:14469 +#: gram.y:14476 #, c-format msgid "precision for type float must be at least 1 bit" msgstr "тип float должен иметь точность минимум 1 бит" -#: gram.y:14478 +#: gram.y:14485 #, c-format msgid "precision for type float must be less than 54 bits" msgstr "тип float должен иметь точность меньше 54 бит" -#: gram.y:14995 +#: gram.y:15002 #, c-format msgid "wrong number of parameters on left side of OVERLAPS expression" msgstr "неверное число параметров в левой части выражения OVERLAPS" -#: gram.y:15000 +#: gram.y:15007 #, c-format msgid "wrong number of parameters on right side of OVERLAPS expression" msgstr "неверное число параметров в правой части выражения OVERLAPS" -#: gram.y:15177 +#: gram.y:15184 #, c-format msgid "UNIQUE predicate is not yet implemented" msgstr "предикат UNIQUE ещё не реализован" -#: gram.y:15591 +#: gram.y:15598 #, c-format msgid "cannot use multiple ORDER BY clauses with WITHIN GROUP" msgstr "ORDER BY с WITHIN GROUP можно указать только один раз" -#: gram.y:15596 +#: gram.y:15603 #, c-format msgid "cannot use DISTINCT with WITHIN GROUP" msgstr "DISTINCT нельзя использовать с WITHIN GROUP" -#: gram.y:15601 +#: gram.y:15608 #, c-format msgid "cannot use VARIADIC with WITHIN GROUP" msgstr "VARIADIC нельзя использовать с WITHIN GROUP" -#: gram.y:16328 gram.y:16352 +#: gram.y:16335 gram.y:16359 #, c-format msgid "frame start cannot be UNBOUNDED FOLLOWING" msgstr "началом рамки не может быть UNBOUNDED FOLLOWING" -#: gram.y:16333 +#: gram.y:16340 #, c-format msgid "frame starting from following row cannot end with current row" msgstr "" "рамка, начинающаяся со следующей строки, не может заканчиваться текущей" -#: gram.y:16357 +#: gram.y:16364 #, c-format msgid "frame end cannot be UNBOUNDED PRECEDING" msgstr "концом рамки не может быть UNBOUNDED PRECEDING" -#: gram.y:16363 +#: gram.y:16370 #, c-format msgid "frame starting from current row cannot have preceding rows" msgstr "" "рамка, начинающаяся с текущей строки, не может иметь предшествующих строк" -#: gram.y:16370 +#: gram.y:16377 #, c-format msgid "frame starting from following row cannot have preceding rows" msgstr "" "рамка, начинающаяся со следующей строки, не может иметь предшествующих строк" -#: gram.y:16919 +#: gram.y:16926 #, c-format msgid "unrecognized JSON encoding: %s" msgstr "нераспознанная кодировка JSON: %s" -#: gram.y:17243 +#: gram.y:17250 #, c-format msgid "type modifier cannot have parameter name" msgstr "параметр функции-модификатора типа должен быть безымянным" -#: gram.y:17249 +#: gram.y:17256 #, c-format msgid "type modifier cannot have ORDER BY" msgstr "модификатор типа не может включать ORDER BY" -#: gram.y:17317 gram.y:17324 gram.y:17331 +#: gram.y:17324 gram.y:17331 gram.y:17338 #, c-format msgid "%s cannot be used as a role name here" msgstr "%s нельзя использовать здесь как имя роли" -#: gram.y:17421 gram.y:18906 +#: gram.y:17428 gram.y:18913 #, c-format msgid "WITH TIES cannot be specified without ORDER BY clause" msgstr "WITH TIES нельзя задать без предложения ORDER BY" -#: gram.y:18597 gram.y:18772 +#: gram.y:18604 gram.y:18779 msgid "improper use of \"*\"" msgstr "недопустимое использование \"*\"" -#: gram.y:18836 +#: gram.y:18843 #, c-format msgid "" "an ordered-set aggregate with a VARIADIC direct argument must have one " @@ -35055,70 +35056,70 @@ msgstr "" "сортирующая агрегатная функция с непосредственным аргументом VARIADIC должна " "иметь один агрегатный аргумент VARIADIC того же типа данных" -#: gram.y:18873 +#: gram.y:18880 #, c-format msgid "multiple ORDER BY clauses not allowed" msgstr "ORDER BY можно указать только один раз" -#: gram.y:18884 +#: gram.y:18891 #, c-format msgid "multiple OFFSET clauses not allowed" msgstr "OFFSET можно указать только один раз" -#: gram.y:18893 +#: gram.y:18900 #, c-format msgid "multiple LIMIT clauses not allowed" msgstr "LIMIT можно указать только один раз" -#: gram.y:18902 +#: gram.y:18909 #, c-format msgid "multiple limit options not allowed" msgstr "параметры LIMIT можно указать только один раз" -#: gram.y:18929 +#: gram.y:18936 #, c-format msgid "multiple WITH clauses not allowed" msgstr "WITH можно указать только один раз" -#: gram.y:19122 +#: gram.y:19129 #, c-format msgid "OUT and INOUT arguments aren't allowed in TABLE functions" msgstr "в табличных функциях не может быть аргументов OUT и INOUT" -#: gram.y:19255 +#: gram.y:19262 #, c-format msgid "multiple COLLATE clauses not allowed" msgstr "COLLATE можно указать только один раз" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19293 gram.y:19306 +#: gram.y:19300 gram.y:19313 #, c-format msgid "%s constraints cannot be marked DEFERRABLE" msgstr "ограничения %s не могут иметь характеристики DEFERRABLE" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19319 +#: gram.y:19326 #, c-format msgid "%s constraints cannot be marked NOT VALID" msgstr "ограничения %s не могут иметь характеристики NOT VALID" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19332 +#: gram.y:19339 #, c-format msgid "%s constraints cannot be marked NO INHERIT" msgstr "ограничения %s не могут иметь характеристики NO INHERIT" -#: gram.y:19354 +#: gram.y:19361 #, c-format msgid "unrecognized partitioning strategy \"%s\"" msgstr "нераспознанная стратегия секционирования \"%s\"" -#: gram.y:19378 +#: gram.y:19385 #, c-format msgid "invalid publication object list" msgstr "неверный список объектов публикации" -#: gram.y:19379 +#: gram.y:19386 #, c-format msgid "" "One of TABLE or TABLES IN SCHEMA must be specified before a standalone table " @@ -35127,22 +35128,22 @@ msgstr "" "Перед именем отдельной таблицы или схемы нужно указать TABLE либо TABLES IN " "SCHEMA." -#: gram.y:19395 +#: gram.y:19402 #, c-format msgid "invalid table name" msgstr "неверное имя таблицы" -#: gram.y:19416 +#: gram.y:19423 #, c-format msgid "WHERE clause not allowed for schema" msgstr "предложение WHERE не допускается для схемы" -#: gram.y:19423 +#: gram.y:19430 #, c-format msgid "column specification not allowed for schema" msgstr "указание столбца не допускается для схемы" -#: gram.y:19437 +#: gram.y:19444 #, c-format msgid "invalid schema name" msgstr "неверное имя схемы" @@ -35398,6 +35399,30 @@ msgstr "нестандартное использование спецсимво msgid "Use the escape string syntax for escapes, e.g., E'\\r\\n'." msgstr "Используйте для записи спецсимволов синтаксис спецстрок E'\\r\\n'." +#, c-format +#~ msgid "\"%s\" must be set to -1 during binary upgrade mode." +#~ msgstr "Параметр \"%s\" должен быть равен -1 в режиме двоичного обновления." + +#, c-format +#~ msgid "oversize GSSAPI packet sent by the client (%zu > %d)" +#~ msgstr "клиент передал чрезмерно большой пакет GSSAPI (%zu > %d)" + +#, c-format +#~ msgid "" +#~ "could not synchronize replication slot \"%s\" because remote slot " +#~ "precedes local slot" +#~ msgstr "" +#~ "синхронизировать слот репликации \"%s\" не удалось, так как состояние " +#~ "удалённого слота предшествует локальному" + +#, c-format +#~ msgid "" +#~ "Logical decoding could not find consistent point from local slot's LSN %X/" +#~ "%X." +#~ msgstr "" +#~ "При логическом декодировании не удалось найти точку согласованности от " +#~ "LSN локального слота %X/%X." + #, c-format #~ msgid "cannot use RETURNING type %s in %s" #~ msgstr "использовать в RETURNING тип %s в %s нельзя" @@ -38183,9 +38208,6 @@ msgstr "Используйте для записи спецсимволов си #~ msgid "cannot copy to foreign table \"%s\"" #~ msgstr "копировать в стороннюю таблицу \"%s\" нельзя" -#~ msgid "cannot route inserted tuples to a foreign table" -#~ msgstr "направить вставляемые кортежи в стороннюю таблицу нельзя" - #~ msgid "unrecognized function attribute \"%s\" ignored" #~ msgstr "нераспознанный атрибут функции \"%s\" --- игнорируется" diff --git a/src/backend/po/sv.po b/src/backend/po/sv.po index db87d04a3c232..0ec316deecbcf 100644 --- a/src/backend/po/sv.po +++ b/src/backend/po/sv.po @@ -23,8 +23,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-12 13:46+0000\n" -"PO-Revision-Date: 2025-02-12 20:52+0100\n" +"POT-Creation-Date: 2025-08-09 05:15+0000\n" +"PO-Revision-Date: 2025-08-09 20:15+0200\n" "Last-Translator: Dennis Björklund \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -104,15 +104,15 @@ msgstr "kunde inte öppna filen \"%s\" för läsning: %m" #: ../common/controldata_utils.c:108 ../common/controldata_utils.c:110 #: access/transam/timeline.c:143 access/transam/timeline.c:362 -#: access/transam/twophase.c:1353 access/transam/xlog.c:3477 -#: access/transam/xlog.c:4341 access/transam/xlogrecovery.c:1238 +#: access/transam/twophase.c:1353 access/transam/xlog.c:3459 +#: access/transam/xlog.c:4323 access/transam/xlogrecovery.c:1238 #: access/transam/xlogrecovery.c:1336 access/transam/xlogrecovery.c:1373 #: access/transam/xlogrecovery.c:1440 backup/basebackup.c:2123 #: backup/walsummary.c:283 commands/extension.c:3548 libpq/hba.c:764 #: replication/logical/origin.c:745 replication/logical/origin.c:781 -#: replication/logical/reorderbuffer.c:5113 -#: replication/logical/snapbuild.c:2052 replication/slot.c:2236 -#: replication/slot.c:2277 replication/walsender.c:655 +#: replication/logical/reorderbuffer.c:5243 +#: replication/logical/snapbuild.c:2099 replication/slot.c:2232 +#: replication/slot.c:2273 replication/walsender.c:659 #: storage/file/buffile.c:470 storage/file/copydir.c:185 #: utils/adt/genfile.c:197 utils/adt/misc.c:1028 utils/cache/relmapper.c:829 #, c-format @@ -120,10 +120,10 @@ msgid "could not read file \"%s\": %m" msgstr "kunde inte läsa fil \"%s\": %m" #: ../common/controldata_utils.c:116 ../common/controldata_utils.c:119 -#: access/transam/xlog.c:3482 access/transam/xlog.c:4346 +#: access/transam/xlog.c:3464 access/transam/xlog.c:4328 #: replication/logical/origin.c:750 replication/logical/origin.c:789 -#: replication/logical/snapbuild.c:2057 replication/slot.c:2240 -#: replication/slot.c:2281 replication/walsender.c:660 +#: replication/logical/snapbuild.c:2104 replication/slot.c:2236 +#: replication/slot.c:2277 replication/walsender.c:664 #: utils/cache/relmapper.c:833 #, c-format msgid "could not read file \"%s\": read %d of %zu" @@ -134,17 +134,17 @@ msgstr "kunde inte läsa fil \"%s\": läste %d av %zu" #: access/heap/rewriteheap.c:1141 access/heap/rewriteheap.c:1246 #: access/transam/timeline.c:392 access/transam/timeline.c:438 #: access/transam/timeline.c:512 access/transam/twophase.c:1365 -#: access/transam/twophase.c:1784 access/transam/xlog.c:3323 -#: access/transam/xlog.c:3517 access/transam/xlog.c:3522 -#: access/transam/xlog.c:3658 access/transam/xlog.c:4311 -#: access/transam/xlog.c:5246 commands/copyfrom.c:1799 commands/copyto.c:325 +#: access/transam/twophase.c:1784 access/transam/xlog.c:3305 +#: access/transam/xlog.c:3499 access/transam/xlog.c:3504 +#: access/transam/xlog.c:3640 access/transam/xlog.c:4293 +#: access/transam/xlog.c:5228 commands/copyfrom.c:1799 commands/copyto.c:325 #: libpq/be-fsstubs.c:470 libpq/be-fsstubs.c:540 #: replication/logical/origin.c:683 replication/logical/origin.c:822 -#: replication/logical/reorderbuffer.c:5165 -#: replication/logical/snapbuild.c:1819 replication/logical/snapbuild.c:1943 -#: replication/slot.c:2126 replication/slot.c:2288 replication/walsender.c:670 -#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:828 -#: storage/file/fd.c:3753 storage/file/fd.c:3859 utils/cache/relmapper.c:841 +#: replication/logical/reorderbuffer.c:5295 +#: replication/logical/snapbuild.c:1866 replication/logical/snapbuild.c:1990 +#: replication/slot.c:2122 replication/slot.c:2284 replication/walsender.c:674 +#: storage/file/copydir.c:208 storage/file/copydir.c:213 storage/file/fd.c:825 +#: storage/file/fd.c:3750 storage/file/fd.c:3856 utils/cache/relmapper.c:841 #: utils/cache/relmapper.c:956 #, c-format msgid "could not close file \"%s\": %m" @@ -172,31 +172,31 @@ msgstr "" #: ../common/file_utils.c:406 ../common/file_utils.c:480 #: access/heap/rewriteheap.c:1229 access/transam/timeline.c:111 #: access/transam/timeline.c:251 access/transam/timeline.c:348 -#: access/transam/twophase.c:1309 access/transam/xlog.c:3230 -#: access/transam/xlog.c:3393 access/transam/xlog.c:3432 -#: access/transam/xlog.c:3625 access/transam/xlog.c:4331 +#: access/transam/twophase.c:1309 access/transam/xlog.c:3212 +#: access/transam/xlog.c:3375 access/transam/xlog.c:3414 +#: access/transam/xlog.c:3607 access/transam/xlog.c:4313 #: access/transam/xlogrecovery.c:4264 access/transam/xlogrecovery.c:4367 #: access/transam/xlogutils.c:836 backup/basebackup.c:547 #: backup/basebackup.c:1598 backup/walsummary.c:220 libpq/hba.c:624 #: postmaster/syslogger.c:1511 replication/logical/origin.c:735 -#: replication/logical/reorderbuffer.c:3766 -#: replication/logical/reorderbuffer.c:4320 -#: replication/logical/reorderbuffer.c:5093 -#: replication/logical/snapbuild.c:1774 replication/logical/snapbuild.c:1884 -#: replication/slot.c:2208 replication/walsender.c:628 -#: replication/walsender.c:3051 storage/file/copydir.c:151 -#: storage/file/fd.c:803 storage/file/fd.c:3510 storage/file/fd.c:3740 -#: storage/file/fd.c:3830 storage/smgr/md.c:661 utils/cache/relmapper.c:818 +#: replication/logical/reorderbuffer.c:3896 +#: replication/logical/reorderbuffer.c:4450 +#: replication/logical/reorderbuffer.c:5223 +#: replication/logical/snapbuild.c:1821 replication/logical/snapbuild.c:1931 +#: replication/slot.c:2204 replication/walsender.c:632 +#: replication/walsender.c:3085 storage/file/copydir.c:151 +#: storage/file/fd.c:800 storage/file/fd.c:3507 storage/file/fd.c:3737 +#: storage/file/fd.c:3827 storage/smgr/md.c:661 utils/cache/relmapper.c:818 #: utils/cache/relmapper.c:935 utils/error/elog.c:2124 #: utils/init/miscinit.c:1580 utils/init/miscinit.c:1714 -#: utils/init/miscinit.c:1791 utils/misc/guc.c:4777 utils/misc/guc.c:4827 +#: utils/init/miscinit.c:1791 utils/misc/guc.c:4782 utils/misc/guc.c:4832 #, c-format msgid "could not open file \"%s\": %m" msgstr "kunde inte öppna fil \"%s\": %m" #: ../common/controldata_utils.c:246 ../common/controldata_utils.c:249 #: access/transam/twophase.c:1757 access/transam/twophase.c:1766 -#: access/transam/xlog.c:9280 access/transam/xlogfuncs.c:698 +#: access/transam/xlog.c:9306 access/transam/xlogfuncs.c:698 #: backup/basebackup_server.c:173 backup/basebackup_server.c:266 #: backup/walsummary.c:304 postmaster/postmaster.c:4127 #: postmaster/syslogger.c:1522 postmaster/syslogger.c:1535 @@ -210,12 +210,12 @@ msgstr "kunde inte skriva fil \"%s\": %m" #: access/heap/rewriteheap.c:925 access/heap/rewriteheap.c:1135 #: access/heap/rewriteheap.c:1240 access/transam/timeline.c:432 #: access/transam/timeline.c:506 access/transam/twophase.c:1778 -#: access/transam/xlog.c:3316 access/transam/xlog.c:3511 -#: access/transam/xlog.c:4304 access/transam/xlog.c:8655 -#: access/transam/xlog.c:8700 backup/basebackup_server.c:207 -#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1812 -#: replication/slot.c:2112 replication/slot.c:2218 storage/file/fd.c:820 -#: storage/file/fd.c:3851 storage/smgr/md.c:1331 storage/smgr/md.c:1376 +#: access/transam/xlog.c:3298 access/transam/xlog.c:3493 +#: access/transam/xlog.c:4286 access/transam/xlog.c:8681 +#: access/transam/xlog.c:8726 backup/basebackup_server.c:207 +#: commands/dbcommands.c:514 replication/logical/snapbuild.c:1859 +#: replication/slot.c:2108 replication/slot.c:2214 storage/file/fd.c:817 +#: storage/file/fd.c:3848 storage/smgr/md.c:1331 storage/smgr/md.c:1376 #: storage/sync/sync.c:446 utils/misc/guc.c:4530 #, c-format msgid "could not fsync file \"%s\": %m" @@ -229,21 +229,22 @@ msgstr "kunde inte fsync:a fil \"%s\": %m" #: ../common/parse_manifest.c:852 ../common/psprintf.c:143 #: ../common/scram-common.c:268 ../common/stringinfo.c:314 ../port/path.c:828 #: ../port/path.c:865 ../port/path.c:882 access/transam/twophase.c:1418 -#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1352 -#: libpq/auth.c:1396 libpq/auth.c:1953 libpq/be-secure-gssapi.c:524 -#: postmaster/bgworker.c:355 postmaster/bgworker.c:945 -#: postmaster/postmaster.c:3560 postmaster/postmaster.c:4021 -#: postmaster/postmaster.c:4383 postmaster/walsummarizer.c:935 +#: access/transam/xlogrecovery.c:564 lib/dshash.c:253 libpq/auth.c:1360 +#: libpq/auth.c:1404 libpq/auth.c:1961 libpq/be-secure-gssapi.c:534 +#: libpq/be-secure-gssapi.c:714 postmaster/bgworker.c:355 +#: postmaster/bgworker.c:945 postmaster/postmaster.c:3560 +#: postmaster/postmaster.c:4021 postmaster/postmaster.c:4383 +#: postmaster/walsummarizer.c:935 #: replication/libpqwalreceiver/libpqwalreceiver.c:387 -#: replication/logical/logical.c:210 replication/walsender.c:835 -#: storage/buffer/localbuf.c:606 storage/file/fd.c:912 storage/file/fd.c:1443 -#: storage/file/fd.c:1604 storage/file/fd.c:2531 storage/ipc/procarray.c:1465 +#: replication/logical/logical.c:212 replication/walsender.c:839 +#: storage/buffer/localbuf.c:606 storage/file/fd.c:909 storage/file/fd.c:1440 +#: storage/file/fd.c:1601 storage/file/fd.c:2528 storage/ipc/procarray.c:1465 #: storage/ipc/procarray.c:2219 storage/ipc/procarray.c:2226 #: storage/ipc/procarray.c:2731 storage/ipc/procarray.c:3435 #: utils/adt/formatting.c:1725 utils/adt/formatting.c:1873 #: utils/adt/formatting.c:2075 utils/adt/pg_locale.c:532 -#: utils/adt/pg_locale.c:696 utils/fmgr/dfmgr.c:229 utils/hash/dynahash.c:516 -#: utils/hash/dynahash.c:616 utils/hash/dynahash.c:1099 utils/mb/mbutils.c:401 +#: utils/adt/pg_locale.c:696 utils/fmgr/dfmgr.c:229 utils/hash/dynahash.c:517 +#: utils/hash/dynahash.c:617 utils/hash/dynahash.c:1100 utils/mb/mbutils.c:401 #: utils/mb/mbutils.c:429 utils/mb/mbutils.c:814 utils/mb/mbutils.c:841 #: utils/misc/guc.c:649 utils/misc/guc.c:674 utils/misc/guc.c:1062 #: utils/misc/guc.c:4508 utils/misc/tzparser.c:477 utils/mmgr/aset.c:451 @@ -327,7 +328,7 @@ msgstr "slut på minne\n" msgid "cannot duplicate null pointer (internal error)\n" msgstr "kan inte duplicera null-pekare (internt fel)\n" -#: ../common/file_utils.c:76 storage/file/fd.c:3516 +#: ../common/file_utils.c:76 storage/file/fd.c:3513 #, c-format msgid "could not synchronize file system for file \"%s\": %m" msgstr "kan inte synkronisera filsystemet för fil \"%s\": %m" @@ -339,8 +340,8 @@ msgstr "kan inte synkronisera filsystemet för fil \"%s\": %m" #: backup/walsummary.c:247 backup/walsummary.c:254 commands/copyfrom.c:1749 #: commands/copyto.c:700 commands/extension.c:3527 commands/tablespace.c:804 #: commands/tablespace.c:893 postmaster/pgarch.c:680 -#: replication/logical/snapbuild.c:1670 replication/logical/snapbuild.c:2173 -#: storage/file/fd.c:1968 storage/file/fd.c:2054 storage/file/fd.c:3564 +#: replication/logical/snapbuild.c:1717 replication/logical/snapbuild.c:2220 +#: storage/file/fd.c:1965 storage/file/fd.c:2051 storage/file/fd.c:3561 #: utils/adt/dbsize.c:105 utils/adt/dbsize.c:257 utils/adt/dbsize.c:337 #: utils/adt/genfile.c:437 utils/adt/genfile.c:612 utils/adt/misc.c:340 #, c-format @@ -355,22 +356,22 @@ msgstr "detta bygge stöder inte synkmetod \"%s\"" #: ../common/file_utils.c:151 ../common/file_utils.c:281 #: ../common/pgfnames.c:48 ../common/rmtree.c:63 commands/tablespace.c:728 #: commands/tablespace.c:738 postmaster/postmaster.c:1470 -#: storage/file/fd.c:2933 storage/file/reinit.c:126 utils/adt/misc.c:256 +#: storage/file/fd.c:2930 storage/file/reinit.c:126 utils/adt/misc.c:256 #: utils/misc/tzparser.c:339 #, c-format msgid "could not open directory \"%s\": %m" msgstr "kunde inte öppna katalog \"%s\": %m" #: ../common/file_utils.c:169 ../common/file_utils.c:315 -#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2945 +#: ../common/pgfnames.c:69 ../common/rmtree.c:106 storage/file/fd.c:2942 #, c-format msgid "could not read directory \"%s\": %m" msgstr "kunde inte läsa katalog \"%s\": %m" #: ../common/file_utils.c:498 access/transam/xlogarchive.c:389 #: postmaster/pgarch.c:834 postmaster/syslogger.c:1559 -#: replication/logical/snapbuild.c:1831 replication/slot.c:936 -#: replication/slot.c:1998 replication/slot.c:2140 storage/file/fd.c:838 +#: replication/logical/snapbuild.c:1878 replication/slot.c:936 +#: replication/slot.c:1994 replication/slot.c:2136 storage/file/fd.c:835 #: utils/time/snapmgr.c:1255 #, c-format msgid "could not rename file \"%s\" to \"%s\": %m" @@ -380,101 +381,101 @@ msgstr "kunde inte döpa om fil \"%s\" till \"%s\": %m" msgid "internal error" msgstr "internt fel" -#: ../common/jsonapi.c:2121 +#: ../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "Recursive-descent-parser kan inte använda inkrementell lexer." -#: ../common/jsonapi.c:2123 +#: ../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "Inkrementell parser kräver en inkrementell lexer." -#: ../common/jsonapi.c:2125 +#: ../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "JSON nästlad för djupt, maximal tillåtet djup är 6400." -#: ../common/jsonapi.c:2127 +#: ../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "Escape-sekvens \"\\%.*s\" är ogiltig." -#: ../common/jsonapi.c:2131 +#: ../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "Tecken med värde 0x%02x måste escape:as." -#: ../common/jsonapi.c:2135 +#: ../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "Förväntade slut på indata, men hittade \"%.*s\"." -#: ../common/jsonapi.c:2138 +#: ../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "Färväntade array-element eller \"]\", men hittade \"%.*s\"." -#: ../common/jsonapi.c:2141 +#: ../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "Förväntade \",\" eller \"]\", men hittade \"%.*s\"." -#: ../common/jsonapi.c:2144 +#: ../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "Förväntade sig \":\" men hittade \"%.*s\"." -#: ../common/jsonapi.c:2147 +#: ../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "Förväntade JSON-värde, men hittade \"%.*s\"." -#: ../common/jsonapi.c:2150 +#: ../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "Indatasträngen avslutades oväntat." -#: ../common/jsonapi.c:2152 +#: ../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "Färväntade sträng eller \"}\", men hittade \"%.*s\"." -#: ../common/jsonapi.c:2155 +#: ../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "Förväntade sig \",\" eller \"}\" men hittade \"%.*s\"." -#: ../common/jsonapi.c:2158 +#: ../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "Förväntade sträng, men hittade \"%.*s\"." -#: ../common/jsonapi.c:2161 +#: ../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "Token \"%.*s\" är ogiltig." -#: ../common/jsonapi.c:2164 jsonpath_scan.l:608 +#: ../common/jsonapi.c:2167 jsonpath_scan.l:608 #, c-format msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 kan inte konverteras till text." -#: ../common/jsonapi.c:2166 +#: ../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "\"\\u\" måste följas av fyra hexdecimala siffror." -#: ../common/jsonapi.c:2169 +#: ../common/jsonapi.c:2172 msgid "Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8." msgstr "Escape-värden för unicode kan inte användas för kodpunkter med värde över 007F när kodningen inte är UTF8." -#: ../common/jsonapi.c:2178 +#: ../common/jsonapi.c:2181 #, c-format msgid "Unicode escape value could not be translated to the server's encoding %s." msgstr "Escape-värde för unicode kan inte översättas till serverns kodning %s." -#: ../common/jsonapi.c:2185 jsonpath_scan.l:641 +#: ../common/jsonapi.c:2188 jsonpath_scan.l:641 #, c-format msgid "Unicode high surrogate must not follow a high surrogate." msgstr "Unicodes övre surrogathalva får inte komma efter en övre surrogathalva." -#: ../common/jsonapi.c:2187 jsonpath_scan.l:652 jsonpath_scan.l:662 +#: ../common/jsonapi.c:2190 jsonpath_scan.l:652 jsonpath_scan.l:662 #: jsonpath_scan.l:713 #, c-format msgid "Unicode low surrogate must follow a high surrogate." @@ -660,8 +661,8 @@ msgstr "kunde inte parsa backup-manifest: %s" #: ../common/percentrepl.c:79 ../common/percentrepl.c:85 #: ../common/percentrepl.c:118 ../common/percentrepl.c:124 #: tcop/backend_startup.c:741 utils/misc/guc.c:3167 utils/misc/guc.c:3208 -#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6931 -#: utils/misc/guc.c:6972 +#: utils/misc/guc.c:3283 utils/misc/guc.c:4712 utils/misc/guc.c:6942 +#: utils/misc/guc.c:6983 #, c-format msgid "invalid value for parameter \"%s\": \"%s\"" msgstr "ogiltigt värde för parameter \"%s\": \"%s\"" @@ -725,10 +726,10 @@ msgstr "kunde inte hämta statuskod för underprocess: felkod %lu" #: access/transam/twophase.c:1717 access/transam/xlogarchive.c:119 #: access/transam/xlogarchive.c:399 postmaster/postmaster.c:1048 #: postmaster/syslogger.c:1488 replication/logical/origin.c:591 -#: replication/logical/reorderbuffer.c:4589 -#: replication/logical/snapbuild.c:1712 replication/logical/snapbuild.c:2146 -#: replication/slot.c:2192 storage/file/fd.c:878 storage/file/fd.c:3378 -#: storage/file/fd.c:3440 storage/file/reinit.c:261 storage/ipc/dsm.c:343 +#: replication/logical/reorderbuffer.c:4719 +#: replication/logical/snapbuild.c:1759 replication/logical/snapbuild.c:2193 +#: replication/slot.c:2188 storage/file/fd.c:875 storage/file/fd.c:3375 +#: storage/file/fd.c:3437 storage/file/reinit.c:261 storage/ipc/dsm.c:343 #: storage/smgr/md.c:381 storage/smgr/md.c:440 storage/sync/sync.c:243 #: utils/time/snapmgr.c:1591 #, c-format @@ -736,8 +737,8 @@ msgid "could not remove file \"%s\": %m" msgstr "kunde inte ta bort fil \"%s\": %m" #: ../common/rmtree.c:124 commands/tablespace.c:767 commands/tablespace.c:780 -#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3370 -#: storage/file/fd.c:3779 +#: commands/tablespace.c:815 commands/tablespace.c:905 storage/file/fd.c:3367 +#: storage/file/fd.c:3776 #, c-format msgid "could not remove directory \"%s\": %m" msgstr "kunde inte ta bort katalog \"%s\": %m" @@ -775,7 +776,7 @@ msgstr "" msgid "could not look up effective user ID %ld: %s" msgstr "kunde inte slå upp effektivt användar-id %ld: %s" -#: ../common/username.c:45 libpq/auth.c:1888 +#: ../common/username.c:45 libpq/auth.c:1896 msgid "user does not exist" msgstr "användaren finns inte" @@ -907,7 +908,7 @@ msgstr "kunde inte kontrollera access-token-medlemskap: felkod %lu\n" msgid "request for BRIN range summarization for index \"%s\" page %u was not recorded" msgstr "förfrågan efter BRIN-intervallsummering för index \"%s\" sida %u har inte spelats in" -#: access/brin/brin.c:1385 access/brin/brin.c:1493 access/gin/ginfast.c:1040 +#: access/brin/brin.c:1387 access/brin/brin.c:1495 access/gin/ginfast.c:1040 #: access/transam/xlogfuncs.c:183 access/transam/xlogfuncs.c:208 #: access/transam/xlogfuncs.c:241 access/transam/xlogfuncs.c:280 #: access/transam/xlogfuncs.c:301 access/transam/xlogfuncs.c:322 @@ -916,34 +917,34 @@ msgstr "förfrågan efter BRIN-intervallsummering för index \"%s\" sida %u har msgid "recovery is in progress" msgstr "återställning pågår" -#: access/brin/brin.c:1386 access/brin/brin.c:1494 +#: access/brin/brin.c:1388 access/brin/brin.c:1496 #, c-format msgid "BRIN control functions cannot be executed during recovery." msgstr "BRIN-kontrollfunktioner kan inte köras under återställning." -#: access/brin/brin.c:1391 access/brin/brin.c:1499 +#: access/brin/brin.c:1393 access/brin/brin.c:1501 #, c-format msgid "block number out of range: %lld" msgstr "blocknummer är utanför giltigt intervall: %lld" -#: access/brin/brin.c:1436 access/brin/brin.c:1525 +#: access/brin/brin.c:1438 access/brin/brin.c:1527 #, c-format msgid "\"%s\" is not a BRIN index" msgstr "\"%s\" är inte ett BRIN-index" -#: access/brin/brin.c:1452 access/brin/brin.c:1541 +#: access/brin/brin.c:1454 access/brin/brin.c:1543 #, c-format msgid "could not open parent table of index \"%s\"" msgstr "kunde inte öppna föräldratabell för index \"%s\"" -#: access/brin/brin.c:1461 access/brin/brin.c:1557 access/gin/ginfast.c:1085 +#: access/brin/brin.c:1463 access/brin/brin.c:1559 access/gin/ginfast.c:1085 #: parser/parse_utilcmd.c:2277 #, c-format msgid "index \"%s\" is not valid" msgstr "index \"%s\" är inte giltigt" -#: access/brin/brin_bloom.c:783 access/brin/brin_bloom.c:825 -#: access/brin/brin_minmax_multi.c:2993 access/brin/brin_minmax_multi.c:3130 +#: access/brin/brin_bloom.c:785 access/brin/brin_bloom.c:827 +#: access/brin/brin_minmax_multi.c:2984 access/brin/brin_minmax_multi.c:3121 #: statistics/dependencies.c:661 statistics/dependencies.c:714 #: statistics/mcv.c:1480 statistics/mcv.c:1511 statistics/mvdistinct.c:343 #: statistics/mvdistinct.c:396 utils/adt/pseudotypes.c:40 @@ -954,7 +955,7 @@ msgstr "kan inte acceptera ett värde av type %s" #: access/brin/brin_pageops.c:75 access/brin/brin_pageops.c:361 #: access/brin/brin_pageops.c:851 access/gin/ginentrypage.c:109 -#: access/gist/gist.c:1463 access/spgist/spgdoinsert.c:2001 +#: access/gist/gist.c:1470 access/spgist/spgdoinsert.c:2001 #: access/spgist/spgdoinsert.c:2278 #, c-format msgid "index row size %zu exceeds maximum %zu for index \"%s\"" @@ -1092,67 +1093,72 @@ msgstr "överskriden gräns för användardefinierade relationsparametertyper" msgid "RESET must not include values for parameters" msgstr "RESET får inte ha med värden på parametrar" -#: access/common/reloptions.c:1263 +#: access/common/reloptions.c:1264 #, c-format msgid "unrecognized parameter namespace \"%s\"" msgstr "okänd parameternamnrymd \"%s\"" -#: access/common/reloptions.c:1300 commands/variable.c:1214 +#: access/common/reloptions.c:1294 commands/foreigncmds.c:86 +#, c-format +msgid "invalid option name \"%s\": must not contain \"=\"" +msgstr "ogiltigt flaggnamn \"%s\": får inte innehålla \"=\"" + +#: access/common/reloptions.c:1309 commands/variable.c:1214 #, c-format msgid "tables declared WITH OIDS are not supported" msgstr "tabeller deklarerade med WITH OIDS stöds inte" -#: access/common/reloptions.c:1468 +#: access/common/reloptions.c:1477 #, c-format msgid "unrecognized parameter \"%s\"" msgstr "okänd parameter \"%s\"" -#: access/common/reloptions.c:1580 +#: access/common/reloptions.c:1589 #, c-format msgid "parameter \"%s\" specified more than once" msgstr "parameter \"%s\" angiven mer än en gång" -#: access/common/reloptions.c:1596 +#: access/common/reloptions.c:1605 #, c-format msgid "invalid value for boolean option \"%s\": %s" msgstr "ogiltigt värde för booleansk flagga \"%s\": \"%s\"" -#: access/common/reloptions.c:1608 +#: access/common/reloptions.c:1617 #, c-format msgid "invalid value for integer option \"%s\": %s" msgstr "ogiltigt värde för heltalsflagga \"%s\": \"%s\"" -#: access/common/reloptions.c:1614 access/common/reloptions.c:1634 +#: access/common/reloptions.c:1623 access/common/reloptions.c:1643 #, c-format msgid "value %s out of bounds for option \"%s\"" msgstr "värdet %s är utanför sitt intervall för flaggan \"%s\"" -#: access/common/reloptions.c:1616 +#: access/common/reloptions.c:1625 #, c-format msgid "Valid values are between \"%d\" and \"%d\"." msgstr "Giltiga värden är mellan \"%d\" och \"%d\"." -#: access/common/reloptions.c:1628 +#: access/common/reloptions.c:1637 #, c-format msgid "invalid value for floating point option \"%s\": %s" msgstr "ogiltigt värde för flyttalsflagga \"%s\": %s" -#: access/common/reloptions.c:1636 +#: access/common/reloptions.c:1645 #, c-format msgid "Valid values are between \"%f\" and \"%f\"." msgstr "Giltiga värden är mellan \"%f\" och \"%f\"." -#: access/common/reloptions.c:1658 +#: access/common/reloptions.c:1667 #, c-format msgid "invalid value for enum option \"%s\": %s" msgstr "ogiltigt värde för enum-flagga \"%s\": %s" -#: access/common/reloptions.c:1989 +#: access/common/reloptions.c:1998 #, c-format msgid "cannot specify storage parameters for a partitioned table" msgstr "kan inte ange lagringsparametrar för partitionerad tabell" -#: access/common/reloptions.c:1990 +#: access/common/reloptions.c:1999 #, c-format msgid "Specify storage parameters for its leaf partitions instead." msgstr "Ange lagringsparametrar för dess löv-partition istället." @@ -1197,18 +1203,18 @@ msgstr "kan inte flytta temporära index tillhörande andra sessioner" msgid "failed to re-find tuple within index \"%s\"" msgstr "misslyckades att återfinna tuple i index \"%s\"" -#: access/gin/ginscan.c:431 +#: access/gin/ginscan.c:436 #, c-format msgid "old GIN indexes do not support whole-index scans nor searches for nulls" msgstr "gamla GIN-index stöder inte hela-index-scan eller sökningar efter null" -#: access/gin/ginscan.c:432 +#: access/gin/ginscan.c:437 #, c-format msgid "To fix this, do REINDEX INDEX \"%s\"." msgstr "För att fixa detta, kör REINDEX INDEX \"%s\"." #: access/gin/ginutil.c:147 executor/execExpr.c:2200 -#: utils/adt/arrayfuncs.c:4016 utils/adt/arrayfuncs.c:6712 +#: utils/adt/arrayfuncs.c:4016 utils/adt/arrayfuncs.c:6714 #: utils/adt/rowtypes.c:974 #, c-format msgid "could not identify a comparison function for type %s" @@ -1250,7 +1256,7 @@ msgstr "Detta orsakas av en inkomplett siduppdelning under krashåterställning msgid "Please REINDEX it." msgstr "Var vänlig och kör REINDEX på det." -#: access/gist/gist.c:1196 +#: access/gist/gist.c:1203 #, c-format msgid "fixing incomplete split in index \"%s\", block %u" msgstr "lagar ofärdig split i index \"%s\", block %u" @@ -1293,9 +1299,9 @@ msgstr "operatorfamiljen \"%s\" för accessmetod %s innehåller en inkorrekt ORD msgid "could not determine which collation to use for string hashing" msgstr "kunde inte bestämma vilken jämförelse (collation) som skall användas för sträng-hashning" -#: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:672 -#: catalog/heap.c:678 commands/createas.c:201 commands/createas.c:508 -#: commands/indexcmds.c:2021 commands/tablecmds.c:18178 commands/view.c:81 +#: access/hash/hashfunc.c:278 access/hash/hashfunc.c:334 catalog/heap.c:673 +#: catalog/heap.c:679 commands/createas.c:201 commands/createas.c:508 +#: commands/indexcmds.c:2021 commands/tablecmds.c:18242 commands/view.c:81 #: regex/regc_pg_locale.c:245 utils/adt/formatting.c:1653 #: utils/adt/formatting.c:1801 utils/adt/formatting.c:1991 utils/adt/like.c:189 #: utils/adt/like_support.c:1024 utils/adt/varchar.c:738 @@ -1350,39 +1356,39 @@ msgstr "operatorfamilj \"%s\" för accessmetod %s saknar supportfunktion för op msgid "operator family \"%s\" of access method %s is missing cross-type operator(s)" msgstr "operatorfamilj \"%s\" för accessmetod %s saknar mellan-typ-operator(er)" -#: access/heap/heapam.c:2206 +#: access/heap/heapam.c:2241 #, c-format msgid "cannot insert tuples in a parallel worker" msgstr "kan inte lägga till tupler i en parellell arbetare" -#: access/heap/heapam.c:2725 +#: access/heap/heapam.c:2764 #, c-format msgid "cannot delete tuples during a parallel operation" msgstr "kan inte radera tupler under en parallell operation" -#: access/heap/heapam.c:2772 +#: access/heap/heapam.c:2811 #, c-format msgid "attempted to delete invisible tuple" msgstr "försökte ta bort en osynlig tuple" -#: access/heap/heapam.c:3220 access/heap/heapam.c:6501 access/index/genam.c:818 +#: access/heap/heapam.c:3261 access/heap/heapam.c:6542 access/index/genam.c:818 #, c-format msgid "cannot update tuples during a parallel operation" msgstr "kan inte uppdatera tupler under en parallell operation" -#: access/heap/heapam.c:3397 +#: access/heap/heapam.c:3438 #, c-format msgid "attempted to update invisible tuple" msgstr "försökte uppdatera en osynlig tuple" -#: access/heap/heapam.c:4908 access/heap/heapam.c:4946 -#: access/heap/heapam.c:5211 access/heap/heapam_handler.c:468 +#: access/heap/heapam.c:4949 access/heap/heapam.c:4987 +#: access/heap/heapam.c:5252 access/heap/heapam_handler.c:468 #, c-format msgid "could not obtain lock on row in relation \"%s\"" msgstr "kunde inte låsa rad i relationen \"%s\"" -#: access/heap/heapam.c:6314 commands/trigger.c:3340 -#: executor/nodeModifyTable.c:2376 executor/nodeModifyTable.c:2467 +#: access/heap/heapam.c:6355 commands/trigger.c:3427 +#: executor/nodeModifyTable.c:2399 executor/nodeModifyTable.c:2490 #, c-format msgid "tuple to be updated was already modified by an operation triggered by the current command" msgstr "tupel som skall uppdateras hade redan ändrats av en operation som triggats av aktuellt kommando" @@ -1404,13 +1410,13 @@ msgstr "kunde inte skriva till fil \"%s\", skrev %d av %d: %m." #: access/heap/rewriteheap.c:977 access/heap/rewriteheap.c:1094 #: access/transam/timeline.c:329 access/transam/timeline.c:481 -#: access/transam/xlog.c:3255 access/transam/xlog.c:3446 -#: access/transam/xlog.c:4283 access/transam/xlog.c:9269 +#: access/transam/xlog.c:3237 access/transam/xlog.c:3428 +#: access/transam/xlog.c:4265 access/transam/xlog.c:9295 #: access/transam/xlogfuncs.c:692 backup/basebackup_server.c:149 #: backup/basebackup_server.c:242 commands/dbcommands.c:494 #: postmaster/launch_backend.c:340 postmaster/postmaster.c:4114 #: postmaster/walsummarizer.c:1212 replication/logical/origin.c:603 -#: replication/slot.c:2059 storage/file/copydir.c:157 storage/smgr/md.c:230 +#: replication/slot.c:2055 storage/file/copydir.c:157 storage/smgr/md.c:230 #: utils/time/snapmgr.c:1234 #, c-format msgid "could not create file \"%s\": %m" @@ -1423,15 +1429,15 @@ msgstr "kunde inte trunkera fil \"%s\" till %u: %m" #: access/heap/rewriteheap.c:1122 access/transam/timeline.c:384 #: access/transam/timeline.c:424 access/transam/timeline.c:498 -#: access/transam/xlog.c:3305 access/transam/xlog.c:3502 -#: access/transam/xlog.c:4295 commands/dbcommands.c:506 +#: access/transam/xlog.c:3287 access/transam/xlog.c:3484 +#: access/transam/xlog.c:4277 commands/dbcommands.c:506 #: postmaster/launch_backend.c:351 postmaster/launch_backend.c:363 #: replication/logical/origin.c:615 replication/logical/origin.c:657 -#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1788 -#: replication/slot.c:2094 storage/file/buffile.c:545 +#: replication/logical/origin.c:676 replication/logical/snapbuild.c:1835 +#: replication/slot.c:2090 storage/file/buffile.c:545 #: storage/file/copydir.c:197 utils/init/miscinit.c:1655 #: utils/init/miscinit.c:1666 utils/init/miscinit.c:1674 utils/misc/guc.c:4491 -#: utils/misc/guc.c:4522 utils/misc/guc.c:5675 utils/misc/guc.c:5693 +#: utils/misc/guc.c:4522 utils/misc/guc.c:5680 utils/misc/guc.c:5698 #: utils/time/snapmgr.c:1239 utils/time/snapmgr.c:1246 #, c-format msgid "could not write to file \"%s\": %m" @@ -1563,22 +1569,22 @@ msgstr "WAL-användning: %lld poster, %lld hela sidor, %llu bytes\n" msgid "system usage: %s" msgstr "systemanvändning: %s" -#: access/heap/vacuumlazy.c:2170 +#: access/heap/vacuumlazy.c:2178 #, c-format msgid "table \"%s\": removed %lld dead item identifiers in %u pages" msgstr "tabell \"%s\": tog bort %lld döda postidentifierare i %u sidor" -#: access/heap/vacuumlazy.c:2324 +#: access/heap/vacuumlazy.c:2332 #, c-format msgid "bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans" msgstr "hoppar över ej nödvändigt underhåll av tabell \"%s.%s.%s\" som skyddsåtgärd efter %d index-scan" -#: access/heap/vacuumlazy.c:2327 +#: access/heap/vacuumlazy.c:2335 #, c-format msgid "The table's relfrozenxid or relminmxid is too far in the past." msgstr "Tabellens relfrozenxid eller relminmxid är för långt bak i tiden." -#: access/heap/vacuumlazy.c:2328 +#: access/heap/vacuumlazy.c:2336 #, c-format msgid "" "Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n" @@ -1587,67 +1593,67 @@ msgstr "" "Överväg att öka konfigurationsparametern \"maintenance_work_mem\" eller \"autovacuum_work_mem\".\n" "Du kan också överväga andra metoder för att VACUUM skall hinna med allokeringen av transactions-ID." -#: access/heap/vacuumlazy.c:2590 +#: access/heap/vacuumlazy.c:2598 #, c-format msgid "\"%s\": stopping truncate due to conflicting lock request" msgstr "\"%s\": stoppar trunkering pga konfliktande låskrav" -#: access/heap/vacuumlazy.c:2660 +#: access/heap/vacuumlazy.c:2668 #, c-format msgid "table \"%s\": truncated %u to %u pages" msgstr "tabell \"%s\": trunkerade %u till %u sidor" -#: access/heap/vacuumlazy.c:2722 +#: access/heap/vacuumlazy.c:2730 #, c-format msgid "table \"%s\": suspending truncate due to conflicting lock request" msgstr "tabell \"%s\": pausar trunkering pga konfliktande låskrav" -#: access/heap/vacuumlazy.c:2841 +#: access/heap/vacuumlazy.c:2849 #, c-format msgid "disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary tables in parallel" msgstr "stänger av parallell-flaggan för vacuumn på \"%s\" --- kan inte köra vacuum på temporära tabeller parallellt" -#: access/heap/vacuumlazy.c:3108 +#: access/heap/vacuumlazy.c:3116 #, c-format msgid "while scanning block %u offset %u of relation \"%s.%s\"" msgstr "vid skanning av block %u offset %u i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3111 +#: access/heap/vacuumlazy.c:3119 #, c-format msgid "while scanning block %u of relation \"%s.%s\"" msgstr "vid skanning av block %u i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3115 +#: access/heap/vacuumlazy.c:3123 #, c-format msgid "while scanning relation \"%s.%s\"" msgstr "vid skanning av relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3123 +#: access/heap/vacuumlazy.c:3131 #, c-format msgid "while vacuuming block %u offset %u of relation \"%s.%s\"" msgstr "vid vacuum av block %u offset %u i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3126 +#: access/heap/vacuumlazy.c:3134 #, c-format msgid "while vacuuming block %u of relation \"%s.%s\"" msgstr "vid vacuum av block %u i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3130 +#: access/heap/vacuumlazy.c:3138 #, c-format msgid "while vacuuming relation \"%s.%s\"" msgstr "vid vacuum av relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3135 commands/vacuumparallel.c:1112 +#: access/heap/vacuumlazy.c:3143 commands/vacuumparallel.c:1112 #, c-format msgid "while vacuuming index \"%s\" of relation \"%s.%s\"" msgstr "vid vaccum av index \"%s\" i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3140 commands/vacuumparallel.c:1118 +#: access/heap/vacuumlazy.c:3148 commands/vacuumparallel.c:1118 #, c-format msgid "while cleaning up index \"%s\" of relation \"%s.%s\"" msgstr "vid uppstädning av index \"%s\" i relation \"%s.%s\"" -#: access/heap/vacuumlazy.c:3146 +#: access/heap/vacuumlazy.c:3154 #, c-format msgid "while truncating relation \"%s.%s\" to %u blocks" msgstr "vid trunkering av relation \"%s.%s\" till %u block" @@ -1673,8 +1679,8 @@ msgid "cannot access index \"%s\" while it is being reindexed" msgstr "kan inte använda index \"%s\" som håller på att indexeras om" #: access/index/indexam.c:203 catalog/objectaddress.c:1356 -#: commands/indexcmds.c:2851 commands/tablecmds.c:281 commands/tablecmds.c:305 -#: commands/tablecmds.c:17873 commands/tablecmds.c:19762 +#: commands/indexcmds.c:2885 commands/tablecmds.c:281 commands/tablecmds.c:305 +#: commands/tablecmds.c:17937 commands/tablecmds.c:19834 #, c-format msgid "\"%s\" is not an index" msgstr "\"%s\" är inte ett index" @@ -1720,17 +1726,17 @@ msgstr "index \"%s\" innehåller en halvdöd intern sida" msgid "This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it." msgstr "Detta kan ha orsakats av en avbruten VACUUM i version 9.3 eller äldre, innan uppdatering. Vänligen REINDEX:era det." -#: access/nbtree/nbtutils.c:5108 +#: access/nbtree/nbtutils.c:5114 #, c-format msgid "index row size %zu exceeds btree version %u maximum %zu for index \"%s\"" msgstr "indexradstorlek %zu överstiger btree version %u maximum %zu för index \"%s\"" -#: access/nbtree/nbtutils.c:5114 +#: access/nbtree/nbtutils.c:5120 #, c-format msgid "Index row references tuple (%u,%u) in relation \"%s\"." msgstr "Indexrad refererar tupel (%u,%u) i relation \"%s\"." -#: access/nbtree/nbtutils.c:5118 +#: access/nbtree/nbtutils.c:5124 #, c-format msgid "" "Values larger than 1/3 of a buffer page cannot be indexed.\n" @@ -1771,7 +1777,7 @@ msgstr "SP-GiST lövdatatyp %s matchar deklarerad typ %s" msgid "operator family \"%s\" of access method %s is missing support function %d for type %s" msgstr "operatorfamilj \"%s\" för accessmetod %s saknar supportfunktion %d för typ %s" -#: access/table/tableam.c:255 +#: access/table/tableam.c:256 #, c-format msgid "tid (%u, %u) is not valid for relation \"%s\"" msgstr "tid (%u, %u) är inte giltigt för relation \"%s\"" @@ -2220,8 +2226,8 @@ msgid "calculated CRC checksum does not match value stored in file \"%s\"" msgstr "beräknad CRC-checksumma matchar inte värdet som är lagrat i filen \"%s\"" #: access/transam/twophase.c:1419 access/transam/xlogrecovery.c:565 -#: postmaster/walsummarizer.c:936 replication/logical/logical.c:211 -#: replication/walsender.c:836 +#: postmaster/walsummarizer.c:936 replication/logical/logical.c:213 +#: replication/walsender.c:840 #, c-format msgid "Failed while allocating a WAL reading processor." msgstr "Misslyckades vid allokering av en WAL-läs-processor." @@ -2298,7 +2304,7 @@ msgstr "kunde inte återställa tvåfas-statusfil för transaktion %u" msgid "Two-phase state file has been found in WAL record %X/%X, but this transaction has already been restored from disk." msgstr "Statefil för tvåfas har hittats i WAL-post %X/%X men denna transaktion har redan återställts från disk." -#: access/transam/twophase.c:2524 storage/file/fd.c:514 utils/fmgr/dfmgr.c:209 +#: access/transam/twophase.c:2524 storage/file/fd.c:511 utils/fmgr/dfmgr.c:209 #, c-format msgid "could not access file \"%s\": %m" msgstr "kunde inte komma åt filen \"%s\": %m" @@ -2462,464 +2468,459 @@ msgstr "kan inte rulla tillbaka till sparpunkt under en parallell operation" msgid "cannot have more than 2^32-1 subtransactions in a transaction" msgstr "kan inte ha mer än 2^32-1 undertransaktioner i en transaktion" -#: access/transam/xlog.c:1542 +#: access/transam/xlog.c:1543 #, c-format msgid "request to flush past end of generated WAL; request %X/%X, current position %X/%X" msgstr "förfrågan att flush:a efter slutet av genererad WAL; efterfrågad %X/%X, aktuell position %X/%X" -#: access/transam/xlog.c:1769 +#: access/transam/xlog.c:1770 #, c-format msgid "cannot read past end of generated WAL: requested %X/%X, current position %X/%X" msgstr "kan inte läsa efter slutet av genererad WAL; efterfrågad %X/%X, aktuell position %X/%X" -#: access/transam/xlog.c:2210 access/transam/xlog.c:4501 +#: access/transam/xlog.c:2211 access/transam/xlog.c:4483 #, c-format msgid "The WAL segment size must be a power of two between 1 MB and 1 GB." msgstr "WAL-segmentstorleken måste vara en tvåpotens mellan 1 MB och 1 GB." -#: access/transam/xlog.c:2228 -#, c-format -msgid "\"%s\" must be set to -1 during binary upgrade mode." -msgstr "\"%s\" måsta vara satt till -1 i binärt uppgraderingsläge" - -#: access/transam/xlog.c:2477 +#: access/transam/xlog.c:2459 #, c-format msgid "could not write to log file \"%s\" at offset %u, length %zu: %m" msgstr "kunde inte skriva till loggfil \"%s\" vid offset %u, längd %zu: %m" -#: access/transam/xlog.c:3739 access/transam/xlogutils.c:831 -#: replication/walsender.c:3045 +#: access/transam/xlog.c:3721 access/transam/xlogutils.c:831 +#: replication/walsender.c:3079 #, c-format msgid "requested WAL segment %s has already been removed" msgstr "efterfrågat WAL-segment %s har redan tagits bort" -#: access/transam/xlog.c:4061 +#: access/transam/xlog.c:4043 #, c-format msgid "could not rename file \"%s\": %m" msgstr "kunde inte byta namn på fil \"%s\": %m" -#: access/transam/xlog.c:4104 access/transam/xlog.c:4115 -#: access/transam/xlog.c:4136 +#: access/transam/xlog.c:4086 access/transam/xlog.c:4097 +#: access/transam/xlog.c:4118 #, c-format msgid "required WAL directory \"%s\" does not exist" msgstr "krävd WAL-katalog \"%s\" finns inte" -#: access/transam/xlog.c:4121 access/transam/xlog.c:4142 +#: access/transam/xlog.c:4103 access/transam/xlog.c:4124 #, c-format msgid "creating missing WAL directory \"%s\"" msgstr "skapar saknad WAL-katalog \"%s\"" -#: access/transam/xlog.c:4125 access/transam/xlog.c:4145 +#: access/transam/xlog.c:4107 access/transam/xlog.c:4127 #: commands/dbcommands.c:3262 #, c-format msgid "could not create missing directory \"%s\": %m" msgstr "kunde inte skapa saknad katalog \"%s\": %m" -#: access/transam/xlog.c:4212 +#: access/transam/xlog.c:4194 #, c-format msgid "could not generate secret authorization token" msgstr "kunde inte generera hemligt auktorisationstoken" -#: access/transam/xlog.c:4363 access/transam/xlog.c:4373 -#: access/transam/xlog.c:4399 access/transam/xlog.c:4407 -#: access/transam/xlog.c:4415 access/transam/xlog.c:4421 -#: access/transam/xlog.c:4429 access/transam/xlog.c:4437 -#: access/transam/xlog.c:4445 access/transam/xlog.c:4453 +#: access/transam/xlog.c:4345 access/transam/xlog.c:4355 +#: access/transam/xlog.c:4381 access/transam/xlog.c:4389 +#: access/transam/xlog.c:4397 access/transam/xlog.c:4403 +#: access/transam/xlog.c:4411 access/transam/xlog.c:4419 +#: access/transam/xlog.c:4427 access/transam/xlog.c:4435 +#: access/transam/xlog.c:4443 access/transam/xlog.c:4451 #: access/transam/xlog.c:4461 access/transam/xlog.c:4469 -#: access/transam/xlog.c:4479 access/transam/xlog.c:4487 #: utils/init/miscinit.c:1812 #, c-format msgid "database files are incompatible with server" msgstr "databasfilerna är inkompatibla med servern" -#: access/transam/xlog.c:4364 +#: access/transam/xlog.c:4346 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d (0x%08x), but the server was compiled with PG_CONTROL_VERSION %d (0x%08x)." msgstr "Databasklustret initierades med PG_CONTROL_VERSION %d (0x%08x), men servern kompilerades med PG_CONTROL_VERSION %d (0x%08x)." -#: access/transam/xlog.c:4368 +#: access/transam/xlog.c:4350 #, c-format msgid "This could be a problem of mismatched byte ordering. It looks like you need to initdb." msgstr "Detta kan orsakas av en felaktig byte-ordning. Du behöver troligen köra initdb." -#: access/transam/xlog.c:4374 +#: access/transam/xlog.c:4356 #, c-format msgid "The database cluster was initialized with PG_CONTROL_VERSION %d, but the server was compiled with PG_CONTROL_VERSION %d." msgstr "Databasklustret initierades med PG_CONTROL_VERSION %d, men servern kompilerades med PG_CONTROL_VERSION %d." -#: access/transam/xlog.c:4377 access/transam/xlog.c:4403 -#: access/transam/xlog.c:4411 access/transam/xlog.c:4417 +#: access/transam/xlog.c:4359 access/transam/xlog.c:4385 +#: access/transam/xlog.c:4393 access/transam/xlog.c:4399 #, c-format msgid "It looks like you need to initdb." msgstr "Du behöver troligen köra initdb." -#: access/transam/xlog.c:4389 +#: access/transam/xlog.c:4371 #, c-format msgid "incorrect checksum in control file" msgstr "ogiltig kontrollsumma kontrollfil" -#: access/transam/xlog.c:4400 +#: access/transam/xlog.c:4382 #, c-format msgid "The database cluster was initialized with CATALOG_VERSION_NO %d, but the server was compiled with CATALOG_VERSION_NO %d." msgstr "Databasklustret initierades med CATALOG_VERSION_NO %d, men servern kompilerades med CATALOG_VERSION_NO %d." -#: access/transam/xlog.c:4408 +#: access/transam/xlog.c:4390 #, c-format msgid "The database cluster was initialized with MAXALIGN %d, but the server was compiled with MAXALIGN %d." msgstr "Databasklustret initierades med MAXALIGN %d, men servern kompilerades med MAXALIGN %d." -#: access/transam/xlog.c:4416 +#: access/transam/xlog.c:4398 #, c-format msgid "The database cluster appears to use a different floating-point number format than the server executable." msgstr "Databasklustret verkar använda en annan flyttalsrepresentation än vad serverprogrammet gör." -#: access/transam/xlog.c:4422 +#: access/transam/xlog.c:4404 #, c-format msgid "The database cluster was initialized with BLCKSZ %d, but the server was compiled with BLCKSZ %d." msgstr "Databasklustret initierades med BLCKSZ %d, men servern kompilerades med BLCKSZ %d." -#: access/transam/xlog.c:4425 access/transam/xlog.c:4433 -#: access/transam/xlog.c:4441 access/transam/xlog.c:4449 -#: access/transam/xlog.c:4457 access/transam/xlog.c:4465 -#: access/transam/xlog.c:4473 access/transam/xlog.c:4482 -#: access/transam/xlog.c:4490 +#: access/transam/xlog.c:4407 access/transam/xlog.c:4415 +#: access/transam/xlog.c:4423 access/transam/xlog.c:4431 +#: access/transam/xlog.c:4439 access/transam/xlog.c:4447 +#: access/transam/xlog.c:4455 access/transam/xlog.c:4464 +#: access/transam/xlog.c:4472 #, c-format msgid "It looks like you need to recompile or initdb." msgstr "Det verkar som om du måste kompilera om eller köra initdb." -#: access/transam/xlog.c:4430 +#: access/transam/xlog.c:4412 #, c-format msgid "The database cluster was initialized with RELSEG_SIZE %d, but the server was compiled with RELSEG_SIZE %d." msgstr "Databasklustret initierades med RELSEG_SIZE %d, men servern kompilerades med RELSEG_SIZE %d." -#: access/transam/xlog.c:4438 +#: access/transam/xlog.c:4420 #, c-format msgid "The database cluster was initialized with XLOG_BLCKSZ %d, but the server was compiled with XLOG_BLCKSZ %d." msgstr "Databasklustret initierades med XLOG_BLCKSZ %d, men servern kompilerades med XLOG_BLCKSZ %d." -#: access/transam/xlog.c:4446 +#: access/transam/xlog.c:4428 #, c-format msgid "The database cluster was initialized with NAMEDATALEN %d, but the server was compiled with NAMEDATALEN %d." msgstr "Databasklustret initierades med NAMEDATALEN %d, men servern kompilerades med NAMEDATALEN %d." -#: access/transam/xlog.c:4454 +#: access/transam/xlog.c:4436 #, c-format msgid "The database cluster was initialized with INDEX_MAX_KEYS %d, but the server was compiled with INDEX_MAX_KEYS %d." msgstr "Databasklustret initierades med INDEX_MAX_KEYS %d, men servern kompilerades med INDEX_MAX_KEYS %d." -#: access/transam/xlog.c:4462 +#: access/transam/xlog.c:4444 #, c-format msgid "The database cluster was initialized with TOAST_MAX_CHUNK_SIZE %d, but the server was compiled with TOAST_MAX_CHUNK_SIZE %d." msgstr "Databasklustret initierades med TOAST_MAX_CHUNK_SIZE %d, men servern kompilerades med TOAST_MAX_CHUNK_SIZE %d." -#: access/transam/xlog.c:4470 +#: access/transam/xlog.c:4452 #, c-format msgid "The database cluster was initialized with LOBLKSIZE %d, but the server was compiled with LOBLKSIZE %d." msgstr "Databasklustret initierades med LOBLKSIZE %d, men servern kompilerades med LOBLKSIZE %d." -#: access/transam/xlog.c:4480 +#: access/transam/xlog.c:4462 #, c-format msgid "The database cluster was initialized without USE_FLOAT8_BYVAL but the server was compiled with USE_FLOAT8_BYVAL." msgstr "Databasklustret initierades utan USE_FLOAT8_BYVAL, men servern kompilerades med USE_FLOAT8_BYVAL." -#: access/transam/xlog.c:4488 +#: access/transam/xlog.c:4470 #, c-format msgid "The database cluster was initialized with USE_FLOAT8_BYVAL but the server was compiled without USE_FLOAT8_BYVAL." msgstr "Databasklustret initierades med USE_FLOAT8_BYVAL, men servern kompilerades utan USE_FLOAT8_BYVAL." -#: access/transam/xlog.c:4497 +#: access/transam/xlog.c:4479 #, c-format msgid "invalid WAL segment size in control file (%d byte)" msgid_plural "invalid WAL segment size in control file (%d bytes)" msgstr[0] "ogiltigt WAL-segmentstorlek i kontrollfil (%d byte)" msgstr[1] "ogiltigt WAL-segmentstorlek i kontrollfil (%d byte)" -#: access/transam/xlog.c:4510 +#: access/transam/xlog.c:4492 #, c-format msgid "\"min_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"min_wal_size\" måste vara minst dubbla \"wal_segment_size\"" -#: access/transam/xlog.c:4514 +#: access/transam/xlog.c:4496 #, c-format msgid "\"max_wal_size\" must be at least twice \"wal_segment_size\"" msgstr "\"max_wal_size\" måste vara minst dubbla \"wal_segment_size\"" -#: access/transam/xlog.c:4662 catalog/namespace.c:4696 +#: access/transam/xlog.c:4644 catalog/namespace.c:4696 #: commands/tablespace.c:1210 commands/user.c:2529 commands/variable.c:72 -#: replication/slot.c:2429 tcop/postgres.c:3715 utils/error/elog.c:2247 +#: replication/slot.c:2442 tcop/postgres.c:3715 utils/error/elog.c:2247 #, c-format msgid "List syntax is invalid." msgstr "List-syntaxen är ogiltig." -#: access/transam/xlog.c:4708 commands/user.c:2545 commands/variable.c:173 +#: access/transam/xlog.c:4690 commands/user.c:2545 commands/variable.c:173 #: tcop/postgres.c:3731 utils/error/elog.c:2273 #, c-format msgid "Unrecognized key word: \"%s\"." msgstr "Okänt nyckelord: \"%s\"" -#: access/transam/xlog.c:5129 +#: access/transam/xlog.c:5111 #, c-format msgid "could not write bootstrap write-ahead log file: %m" msgstr "kunde inte skriva bootstrap-write-ahead-loggfil: %m" -#: access/transam/xlog.c:5137 +#: access/transam/xlog.c:5119 #, c-format msgid "could not fsync bootstrap write-ahead log file: %m" msgstr "kunde inte fsync:a bootstrap-write-ahead-loggfil: %m" -#: access/transam/xlog.c:5143 +#: access/transam/xlog.c:5125 #, c-format msgid "could not close bootstrap write-ahead log file: %m" msgstr "kunde inte stänga bootstrap-write-ahead-loggfil: %m" -#: access/transam/xlog.c:5368 +#: access/transam/xlog.c:5350 #, c-format msgid "WAL was generated with \"wal_level=minimal\", cannot continue recovering" msgstr "WAL genererades med \"wal_level=minimal\", kan inte fortsätta återställande" -#: access/transam/xlog.c:5369 +#: access/transam/xlog.c:5351 #, c-format msgid "This happens if you temporarily set \"wal_level=minimal\" on the server." msgstr "Detta händer om du temporärt sätter \"wal_level=minimal\" på servern." -#: access/transam/xlog.c:5370 +#: access/transam/xlog.c:5352 #, c-format msgid "Use a backup taken after setting \"wal_level\" to higher than \"minimal\"." msgstr "Använd en backup som är tagen efter att inställningen \"wal_level\" satts till ett högre värde än \"minimal\"." -#: access/transam/xlog.c:5435 +#: access/transam/xlog.c:5417 #, c-format msgid "control file contains invalid checkpoint location" msgstr "kontrollfil innehåller ogiltig checkpoint-position" -#: access/transam/xlog.c:5446 +#: access/transam/xlog.c:5428 #, c-format msgid "database system was shut down at %s" msgstr "databassystemet stängdes ner vid %s" -#: access/transam/xlog.c:5452 +#: access/transam/xlog.c:5434 #, c-format msgid "database system was shut down in recovery at %s" msgstr "databassystemet stängdes ner under återställning vid %s" -#: access/transam/xlog.c:5458 +#: access/transam/xlog.c:5440 #, c-format msgid "database system shutdown was interrupted; last known up at %s" msgstr "nedstängning av databasen avbröts; senast kända upptidpunkt vid %s" -#: access/transam/xlog.c:5464 +#: access/transam/xlog.c:5446 #, c-format msgid "database system was interrupted while in recovery at %s" msgstr "databassystemet avbröts under återställning vid %s" -#: access/transam/xlog.c:5466 +#: access/transam/xlog.c:5448 #, c-format msgid "This probably means that some data is corrupted and you will have to use the last backup for recovery." msgstr "Det betyder troligen att en del data är förstörd och du behöver återställa databasen från den senaste backup:en." -#: access/transam/xlog.c:5472 +#: access/transam/xlog.c:5454 #, c-format msgid "database system was interrupted while in recovery at log time %s" msgstr "databassystemet avbröts under återställning vid loggtid %s" -#: access/transam/xlog.c:5474 +#: access/transam/xlog.c:5456 #, c-format msgid "If this has occurred more than once some data might be corrupted and you might need to choose an earlier recovery target." msgstr "Om detta har hänt mer än en gång så kan data vara korrupt och du kanske måste återställa till ett tidigare återställningsmål." -#: access/transam/xlog.c:5480 +#: access/transam/xlog.c:5462 #, c-format msgid "database system was interrupted; last known up at %s" msgstr "databassystemet avbröts; senast kända upptidpunkt vid %s" -#: access/transam/xlog.c:5487 +#: access/transam/xlog.c:5469 #, c-format msgid "control file contains invalid database cluster state" msgstr "kontrollfil innehåller ogiltigt databasklustertillstånd" -#: access/transam/xlog.c:5875 +#: access/transam/xlog.c:5857 #, c-format msgid "WAL ends before end of online backup" msgstr "WAL slutar före sluttiden av online-backup:en" -#: access/transam/xlog.c:5876 +#: access/transam/xlog.c:5858 #, c-format msgid "All WAL generated while online backup was taken must be available at recovery." msgstr "Alla genererade WAL under tiden online-backup:en togs måste vara tillgängliga vid återställning." -#: access/transam/xlog.c:5880 +#: access/transam/xlog.c:5862 #, c-format msgid "WAL ends before consistent recovery point" msgstr "WAL avslutas innan konstistent återställningspunkt" -#: access/transam/xlog.c:5926 +#: access/transam/xlog.c:5908 #, c-format msgid "selected new timeline ID: %u" msgstr "valt nytt tidslinje-ID: %u" -#: access/transam/xlog.c:5959 +#: access/transam/xlog.c:5941 #, c-format msgid "archive recovery complete" msgstr "arkivåterställning klar" -#: access/transam/xlog.c:6612 +#: access/transam/xlog.c:6594 #, c-format msgid "shutting down" msgstr "stänger ner" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6651 +#: access/transam/xlog.c:6633 #, c-format msgid "restartpoint starting:%s%s%s%s%s%s%s%s" msgstr "restartpoint startar:%s%s%s%s%s%s%s%s" #. translator: the placeholders show checkpoint options -#: access/transam/xlog.c:6663 +#: access/transam/xlog.c:6645 #, c-format msgid "checkpoint starting:%s%s%s%s%s%s%s%s" msgstr "checkpoint startar:%s%s%s%s%s%s%s%s" -#: access/transam/xlog.c:6728 +#: access/transam/xlog.c:6710 #, c-format msgid "restartpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "restartpoint klar: skrev %d buffers (%.1f%%); %d WAL-fil(er) tillagda, %d borttagna, %d recyclade; skriv=%ld.%03d s, synk=%ld.%03d s, totalt=%ld.%03d s; synk-filer=%d, längsta=%ld.%03d s, genomsnitt=%ld.%03d s; distans=%d kB, estimat=%d kB; lsn=%X/%X, redo-lsn=%X/%X" -#: access/transam/xlog.c:6751 +#: access/transam/xlog.c:6733 #, c-format msgid "checkpoint complete: wrote %d buffers (%.1f%%); %d WAL file(s) added, %d removed, %d recycled; write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s; sync files=%d, longest=%ld.%03d s, average=%ld.%03d s; distance=%d kB, estimate=%d kB; lsn=%X/%X, redo lsn=%X/%X" msgstr "checkpoint klar: skrev %d buffers (%.1f%%); %d WAL-fil(er) tillagda, %d borttagna, %d recyclade; skriv=%ld.%03d s, synk=%ld.%03d s, totalt=%ld.%03d s; synk-filer=%d, längsta=%ld.%03d s, genomsnitt=%ld.%03d s; distans=%d kB, estimat=%d kB; lsn=%X/%X, redo-lsn=%X/%X" -#: access/transam/xlog.c:7233 +#: access/transam/xlog.c:7225 #, c-format msgid "concurrent write-ahead log activity while database system is shutting down" msgstr "samtidig write-ahead-logg-aktivitet när databassystemet stängs ner" -#: access/transam/xlog.c:7818 +#: access/transam/xlog.c:7840 #, c-format msgid "recovery restart point at %X/%X" msgstr "återställningens omstartspunkt vid %X/%X" -#: access/transam/xlog.c:7820 +#: access/transam/xlog.c:7842 #, c-format msgid "Last completed transaction was at log time %s." msgstr "Senaste kompletta transaktionen var vid loggtid %s" -#: access/transam/xlog.c:8082 +#: access/transam/xlog.c:8108 #, c-format msgid "restore point \"%s\" created at %X/%X" msgstr "återställningspunkt \"%s\" skapad vid %X/%X" -#: access/transam/xlog.c:8289 +#: access/transam/xlog.c:8315 #, c-format msgid "online backup was canceled, recovery cannot continue" msgstr "online-backup avbröts, återställning kan inte fortsätta" -#: access/transam/xlog.c:8347 +#: access/transam/xlog.c:8373 #, c-format msgid "unexpected timeline ID %u (should be %u) in shutdown checkpoint record" msgstr "oväntad tidslinje-ID %u (skall vara %u) i checkpoint-post för nedstängning" -#: access/transam/xlog.c:8405 +#: access/transam/xlog.c:8431 #, c-format msgid "unexpected timeline ID %u (should be %u) in online checkpoint record" msgstr "oväntad tidslinje-ID %u (skall vara %u) i checkpoint-post för online" -#: access/transam/xlog.c:8434 +#: access/transam/xlog.c:8460 #, c-format msgid "unexpected timeline ID %u (should be %u) in end-of-recovery record" msgstr "oväntad tidslinje-ID %u (skall vara %u) i post för slutet av återställning" -#: access/transam/xlog.c:8705 +#: access/transam/xlog.c:8731 #, c-format msgid "could not fsync write-through file \"%s\": %m" msgstr "kunde inte fsync:a skriv-igenom-loggfil \"%s\": %m" -#: access/transam/xlog.c:8710 +#: access/transam/xlog.c:8736 #, c-format msgid "could not fdatasync file \"%s\": %m" msgstr "kunde inte fdatasync:a fil \"%s\": %m" -#: access/transam/xlog.c:8797 access/transam/xlog.c:9133 +#: access/transam/xlog.c:8823 access/transam/xlog.c:9159 #, c-format msgid "WAL level not sufficient for making an online backup" msgstr "WAL-nivå inte tillräcklig för att kunna skapa en online-backup" -#: access/transam/xlog.c:8798 access/transam/xlogfuncs.c:248 +#: access/transam/xlog.c:8824 access/transam/xlogfuncs.c:248 #, c-format msgid "\"wal_level\" must be set to \"replica\" or \"logical\" at server start." msgstr "\"wal_level\" måste vara satt till \"replica\" eller \"logical\" vid serverstart." -#: access/transam/xlog.c:8803 +#: access/transam/xlog.c:8829 #, c-format msgid "backup label too long (max %d bytes)" msgstr "backup-etikett för lång (max %d byte)" -#: access/transam/xlog.c:8924 +#: access/transam/xlog.c:8950 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed since last restartpoint" msgstr "WAL skapad med \"full_page_writes=off\" har återspelats sedan senaste omstartpunkten" -#: access/transam/xlog.c:8926 access/transam/xlog.c:9222 +#: access/transam/xlog.c:8952 access/transam/xlog.c:9248 #, c-format msgid "This means that the backup being taken on the standby is corrupt and should not be used. Enable \"full_page_writes\" and run CHECKPOINT on the primary, and then try an online backup again." msgstr "Det betyder att backup:en som tas på standby:en är trasig och inte skall användas. Slå på \"full_page_writes\" och kör CHECKPOINT på primären och försök sedan ta en ny online-backup igen." -#: access/transam/xlog.c:9006 backup/basebackup.c:1417 utils/adt/misc.c:354 +#: access/transam/xlog.c:9032 backup/basebackup.c:1417 utils/adt/misc.c:354 #, c-format msgid "could not read symbolic link \"%s\": %m" msgstr "kan inte läsa symbolisk länk \"%s\": %m" -#: access/transam/xlog.c:9013 backup/basebackup.c:1422 utils/adt/misc.c:359 +#: access/transam/xlog.c:9039 backup/basebackup.c:1422 utils/adt/misc.c:359 #, c-format msgid "symbolic link \"%s\" target is too long" msgstr "mål för symbolisk länk \"%s\" är för lång" -#: access/transam/xlog.c:9134 +#: access/transam/xlog.c:9160 #, c-format msgid "wal_level must be set to \"replica\" or \"logical\" at server start." msgstr "wal_level måste vara satt till \"replica\" eller \"logical\" vid serverstart." -#: access/transam/xlog.c:9172 backup/basebackup.c:1281 +#: access/transam/xlog.c:9198 backup/basebackup.c:1281 #, c-format msgid "the standby was promoted during online backup" msgstr "standby:en befordrades under online-backup" -#: access/transam/xlog.c:9173 backup/basebackup.c:1282 +#: access/transam/xlog.c:9199 backup/basebackup.c:1282 #, c-format msgid "This means that the backup being taken is corrupt and should not be used. Try taking another online backup." msgstr "Det betyder att backupen som tas är trasig och inte skall användas. Försök ta en ny online-backup." -#: access/transam/xlog.c:9220 +#: access/transam/xlog.c:9246 #, c-format msgid "WAL generated with \"full_page_writes=off\" was replayed during online backup" msgstr "WAL skapad med \"full_page_writes=off\" återspelades under online-backup" -#: access/transam/xlog.c:9336 +#: access/transam/xlog.c:9362 #, c-format msgid "base backup done, waiting for required WAL segments to be archived" msgstr "base_backup klar, väntar på att de WAL-segment som krävs blir arkiverade" -#: access/transam/xlog.c:9350 +#: access/transam/xlog.c:9376 #, c-format msgid "still waiting for all required WAL segments to be archived (%d seconds elapsed)" msgstr "väntar fortfarande på att alla krävda WAL-segments skall bli arkiverade (%d sekunder har gått)" -#: access/transam/xlog.c:9352 +#: access/transam/xlog.c:9378 #, c-format msgid "Check that your \"archive_command\" is executing properly. You can safely cancel this backup, but the database backup will not be usable without all the WAL segments." msgstr "Kontrollera att ditt \"archive_command\" kör som det skall. Du kan avbryta denna backup på ett säkert sätt men databasbackup:en kommer inte vara användbar utan att alla WAL-segment finns." -#: access/transam/xlog.c:9359 +#: access/transam/xlog.c:9385 #, c-format msgid "all required WAL segments have been archived" msgstr "alla krävda WAL-segments har arkiverats" -#: access/transam/xlog.c:9363 +#: access/transam/xlog.c:9389 #, c-format msgid "WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup" msgstr "WAL-arkivering är inte påslagen; du måste se till att alla krävda WAL-segment har kopierats på annat sätt för att backup:en skall vara komplett" -#: access/transam/xlog.c:9402 +#: access/transam/xlog.c:9428 #, c-format msgid "aborting backup due to backend exiting before pg_backup_stop was called" msgstr "avbryter backup på grund av att backend:en stoppades innan pg_backup_stop anropades" @@ -3076,147 +3077,147 @@ msgstr "ogiltig postlängd vid %X/%X: förväntade minst %u, fick %u" msgid "contrecord is requested by %X/%X" msgstr "contrecord är begärd vid %X/%X" -#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1134 +#: access/transam/xlogreader.c:669 access/transam/xlogreader.c:1144 #, c-format msgid "invalid record length at %X/%X: expected at least %u, got %u" msgstr "ogiltig postlängd vid %X/%X: förväntade minst %u, fick %u" -#: access/transam/xlogreader.c:758 +#: access/transam/xlogreader.c:759 #, c-format msgid "there is no contrecord flag at %X/%X" msgstr "det finns ingen contrecord-flagga vid %X/%X" -#: access/transam/xlogreader.c:771 +#: access/transam/xlogreader.c:772 #, c-format msgid "invalid contrecord length %u (expected %lld) at %X/%X" msgstr "ogiltig contrecord-längd %u (förväntade %lld) vid %X/%X" -#: access/transam/xlogreader.c:1142 +#: access/transam/xlogreader.c:1152 #, c-format msgid "invalid resource manager ID %u at %X/%X" msgstr "ogiltigt resurshanterar-ID %u vid %X/%X" -#: access/transam/xlogreader.c:1155 access/transam/xlogreader.c:1171 +#: access/transam/xlogreader.c:1165 access/transam/xlogreader.c:1181 #, c-format msgid "record with incorrect prev-link %X/%X at %X/%X" msgstr "post med inkorrekt prev-link %X/%X vid %X/%X" -#: access/transam/xlogreader.c:1209 +#: access/transam/xlogreader.c:1219 #, c-format msgid "incorrect resource manager data checksum in record at %X/%X" msgstr "felaktig resurshanterardatakontrollsumma i post vid %X/%X" -#: access/transam/xlogreader.c:1243 +#: access/transam/xlogreader.c:1253 #, c-format msgid "invalid magic number %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "felaktigt magiskt nummer %04X i WAL-segment %s, LSN %X/%X, offset %u" -#: access/transam/xlogreader.c:1258 access/transam/xlogreader.c:1300 +#: access/transam/xlogreader.c:1268 access/transam/xlogreader.c:1310 #, c-format msgid "invalid info bits %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "ogiltiga infobitar %04X i WAL-segment %s, LSN %X/%X, offset %u" -#: access/transam/xlogreader.c:1274 +#: access/transam/xlogreader.c:1284 #, c-format msgid "WAL file is from different database system: WAL file database system identifier is %llu, pg_control database system identifier is %llu" msgstr "WAL-fil är från ett annat databassystem: WAL-filens databassystemidentifierare är %llu, pg_control databassystemidentifierare är %llu" -#: access/transam/xlogreader.c:1282 +#: access/transam/xlogreader.c:1292 #, c-format msgid "WAL file is from different database system: incorrect segment size in page header" msgstr "WAL-fil är från ett annat databassystem: inkorrekt segmentstorlek i sidhuvud" -#: access/transam/xlogreader.c:1288 +#: access/transam/xlogreader.c:1298 #, c-format msgid "WAL file is from different database system: incorrect XLOG_BLCKSZ in page header" msgstr "WAL-fil är från ett annat databassystem: inkorrekt XLOG_BLCKSZ i sidhuvud" -#: access/transam/xlogreader.c:1320 +#: access/transam/xlogreader.c:1330 #, c-format msgid "unexpected pageaddr %X/%X in WAL segment %s, LSN %X/%X, offset %u" msgstr "oväntad sidadress %X/%X i WAL-segment %s, LSN %X/%X, offset %u" -#: access/transam/xlogreader.c:1346 +#: access/transam/xlogreader.c:1356 #, c-format msgid "out-of-sequence timeline ID %u (after %u) in WAL segment %s, LSN %X/%X, offset %u" msgstr "ej-i-sekvens för tidslinje-ID %u (efter %u) i WAL-segment %s, LSN %X/%X, offset %u" -#: access/transam/xlogreader.c:1749 +#: access/transam/xlogreader.c:1759 #, c-format msgid "out-of-order block_id %u at %X/%X" msgstr "\"ej i sekvens\"-block_id %u vid %X/%X" -#: access/transam/xlogreader.c:1773 +#: access/transam/xlogreader.c:1783 #, c-format msgid "BKPBLOCK_HAS_DATA set, but no data included at %X/%X" msgstr "BKPBLOCK_HAS_DATA är satt men ingen data inkluderad vid %X/%X" -#: access/transam/xlogreader.c:1780 +#: access/transam/xlogreader.c:1790 #, c-format msgid "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X" msgstr "BKPBLOCK_HAS_DATA är ej satt men datalängden är %u vid %X/%X" -#: access/transam/xlogreader.c:1816 +#: access/transam/xlogreader.c:1826 #, c-format msgid "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at %X/%X" msgstr "BKPIMAGE_HAS_HOLE är satt men håloffset %u längd %u blockavbildlängd %u vid %X/%X" -#: access/transam/xlogreader.c:1832 +#: access/transam/xlogreader.c:1842 #, c-format msgid "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X" msgstr "BKPIMAGE_HAS_HOLE är inte satt men håloffset %u längd %u vid %X/%X" -#: access/transam/xlogreader.c:1846 +#: access/transam/xlogreader.c:1856 #, c-format msgid "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X" msgstr "BKPIMAGE_COMPRESSED är satt men blockavbildlängd %u vid %X/%X" -#: access/transam/xlogreader.c:1861 +#: access/transam/xlogreader.c:1871 #, c-format msgid "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image length is %u at %X/%X" msgstr "varken BKPIMAGE_HAS_HOLE eller BKPIMAGE_COMPRESSED är satt men blockavbildlängd är %u vid %X/%X" -#: access/transam/xlogreader.c:1877 +#: access/transam/xlogreader.c:1887 #, c-format msgid "BKPBLOCK_SAME_REL set but no previous rel at %X/%X" msgstr "BKPBLOCK_SAME_REL är satt men ingen tidigare rel vid %X/%X" -#: access/transam/xlogreader.c:1889 +#: access/transam/xlogreader.c:1899 #, c-format msgid "invalid block_id %u at %X/%X" msgstr "ogiltig block_id %u vid %X/%X" -#: access/transam/xlogreader.c:1956 +#: access/transam/xlogreader.c:1966 #, c-format msgid "record with invalid length at %X/%X" msgstr "post med ogiltig längd vid %X/%X" -#: access/transam/xlogreader.c:1982 +#: access/transam/xlogreader.c:1992 #, c-format msgid "could not locate backup block with ID %d in WAL record" msgstr "kunde inte hitta backup-block med ID %d i WAL-post" -#: access/transam/xlogreader.c:2066 +#: access/transam/xlogreader.c:2076 #, c-format msgid "could not restore image at %X/%X with invalid block %d specified" msgstr "kunde inte återställa avbild vid %X/%X med ogiltigt block %d angivet" -#: access/transam/xlogreader.c:2073 +#: access/transam/xlogreader.c:2083 #, c-format msgid "could not restore image at %X/%X with invalid state, block %d" msgstr "kunde inte återställa avbild vid %X/%X med ogiltigt state, block %d" -#: access/transam/xlogreader.c:2100 access/transam/xlogreader.c:2117 +#: access/transam/xlogreader.c:2110 access/transam/xlogreader.c:2127 #, c-format msgid "could not restore image at %X/%X compressed with %s not supported by build, block %d" msgstr "kunde inte återställa avbild vid %X/%X, komprimerad med %s stöds inte av bygget, block %d" -#: access/transam/xlogreader.c:2126 +#: access/transam/xlogreader.c:2136 #, c-format msgid "could not restore image at %X/%X compressed with unknown method, block %d" msgstr "kunde inte återställa avbild vid %X/%X, komprimerad med okänd metod, block %d" -#: access/transam/xlogreader.c:2134 +#: access/transam/xlogreader.c:2144 #, c-format msgid "could not decompress image at %X/%X, block %d" msgstr "kunde inte packa upp avbild vid %X/%X, block %d" @@ -3994,7 +3995,7 @@ msgstr "relativ sökväg tillåts inte för backup som sparas på servern" #: backup/basebackup_server.c:102 commands/dbcommands.c:477 #: commands/tablespace.c:157 commands/tablespace.c:173 -#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1986 +#: commands/tablespace.c:593 commands/tablespace.c:638 replication/slot.c:1982 #: storage/file/copydir.c:47 #, c-format msgid "could not create directory \"%s\": %m" @@ -4228,19 +4229,19 @@ msgstr "kan inte använda IN SCHEMA-klausul samtidigt som GRANT/REVOKE ON SCHEMA #: catalog/aclchk.c:1617 catalog/catalog.c:659 catalog/objectaddress.c:1523 #: catalog/pg_publication.c:528 commands/analyze.c:380 commands/copy.c:951 -#: commands/sequence.c:1655 commands/tablecmds.c:7541 commands/tablecmds.c:7695 -#: commands/tablecmds.c:7745 commands/tablecmds.c:7819 -#: commands/tablecmds.c:7889 commands/tablecmds.c:8019 -#: commands/tablecmds.c:8148 commands/tablecmds.c:8242 -#: commands/tablecmds.c:8343 commands/tablecmds.c:8470 -#: commands/tablecmds.c:8500 commands/tablecmds.c:8642 -#: commands/tablecmds.c:8735 commands/tablecmds.c:8869 -#: commands/tablecmds.c:8981 commands/tablecmds.c:12797 -#: commands/tablecmds.c:12989 commands/tablecmds.c:13150 -#: commands/tablecmds.c:14339 commands/tablecmds.c:16966 commands/trigger.c:942 +#: commands/sequence.c:1655 commands/tablecmds.c:7574 commands/tablecmds.c:7728 +#: commands/tablecmds.c:7778 commands/tablecmds.c:7852 +#: commands/tablecmds.c:7922 commands/tablecmds.c:8052 +#: commands/tablecmds.c:8181 commands/tablecmds.c:8275 +#: commands/tablecmds.c:8376 commands/tablecmds.c:8503 +#: commands/tablecmds.c:8533 commands/tablecmds.c:8675 +#: commands/tablecmds.c:8768 commands/tablecmds.c:8902 +#: commands/tablecmds.c:9014 commands/tablecmds.c:12838 +#: commands/tablecmds.c:13030 commands/tablecmds.c:13191 +#: commands/tablecmds.c:14403 commands/tablecmds.c:17030 commands/trigger.c:943 #: parser/analyze.c:2530 parser/parse_relation.c:737 parser/parse_target.c:1067 #: parser/parse_type.c:144 parser/parse_utilcmd.c:3409 -#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2923 +#: parser/parse_utilcmd.c:3449 parser/parse_utilcmd.c:3491 utils/adt/acl.c:2940 #: utils/adt/ruleutils.c:2812 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist" @@ -4251,15 +4252,15 @@ msgstr "kolumn \"%s\" i relation \"%s\" existerar inte" msgid "\"%s\" is an index" msgstr "\"%s\" är ett index" -#: catalog/aclchk.c:1869 commands/tablecmds.c:14496 commands/tablecmds.c:17882 +#: catalog/aclchk.c:1869 commands/tablecmds.c:14560 commands/tablecmds.c:17946 #, c-format msgid "\"%s\" is a composite type" msgstr "\"%s\" är en composite-typ" #: catalog/aclchk.c:1877 catalog/objectaddress.c:1363 commands/tablecmds.c:263 -#: commands/tablecmds.c:17846 utils/adt/acl.c:2107 utils/adt/acl.c:2137 -#: utils/adt/acl.c:2170 utils/adt/acl.c:2206 utils/adt/acl.c:2237 -#: utils/adt/acl.c:2268 +#: commands/tablecmds.c:17910 utils/adt/acl.c:2124 utils/adt/acl.c:2154 +#: utils/adt/acl.c:2187 utils/adt/acl.c:2223 utils/adt/acl.c:2254 +#: utils/adt/acl.c:2285 #, c-format msgid "\"%s\" is not a sequence" msgstr "\"%s\" är inte en sekvens" @@ -4784,14 +4785,14 @@ msgstr "kan inte ta bort %s eftersom andra objekt beror på den" #: catalog/dependency.c:1153 catalog/dependency.c:1160 #: catalog/dependency.c:1171 commands/tablecmds.c:1459 -#: commands/tablecmds.c:15088 commands/tablespace.c:460 commands/user.c:1302 -#: commands/vacuum.c:211 commands/view.c:441 executor/execExprInterp.c:4655 -#: executor/execExprInterp.c:4663 libpq/auth.c:324 -#: replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1011 +#: commands/tablecmds.c:15152 commands/tablespace.c:460 commands/user.c:1302 +#: commands/vacuum.c:212 commands/view.c:441 executor/execExprInterp.c:4655 +#: executor/execExprInterp.c:4663 libpq/auth.c:332 +#: replication/logical/applyparallelworker.c:1041 replication/syncrep.c:1078 #: storage/lmgr/deadlock.c:1134 storage/lmgr/proc.c:1432 utils/misc/guc.c:3169 -#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6825 -#: utils/misc/guc.c:6859 utils/misc/guc.c:6893 utils/misc/guc.c:6936 -#: utils/misc/guc.c:6978 +#: utils/misc/guc.c:3210 utils/misc/guc.c:3285 utils/misc/guc.c:6836 +#: utils/misc/guc.c:6870 utils/misc/guc.c:6904 utils/misc/guc.c:6947 +#: utils/misc/guc.c:6989 #, c-format msgid "%s" msgstr "%s" @@ -4824,66 +4825,66 @@ msgstr "konstant av typen %s kan inte användas här" msgid "column %d of relation \"%s\" does not exist" msgstr "kolumn %d i relation \"%s\" finns inte" -#: catalog/heap.c:325 +#: catalog/heap.c:326 #, c-format msgid "permission denied to create \"%s.%s\"" msgstr "rättighet saknas för att skapa \"%s.%s\"" -#: catalog/heap.c:327 +#: catalog/heap.c:328 #, c-format msgid "System catalog modifications are currently disallowed." msgstr "Systemkatalogändringar är för tillfället inte tillåtna." -#: catalog/heap.c:467 commands/tablecmds.c:2495 commands/tablecmds.c:2917 +#: catalog/heap.c:468 commands/tablecmds.c:2495 commands/tablecmds.c:2917 #: commands/tablecmds.c:7163 #, c-format msgid "tables can have at most %d columns" msgstr "tabeller kan ha som mest %d kolumner" -#: catalog/heap.c:485 commands/tablecmds.c:7432 +#: catalog/heap.c:486 commands/tablecmds.c:7465 #, c-format msgid "column name \"%s\" conflicts with a system column name" msgstr "kolumnnamn \"%s\" står i konflikt med ett systemkolumnnamn" -#: catalog/heap.c:501 +#: catalog/heap.c:502 #, c-format msgid "column name \"%s\" specified more than once" msgstr "kolumnnamn \"%s\" angiven mer än en gång" #. translator: first %s is an integer not a name -#: catalog/heap.c:579 +#: catalog/heap.c:580 #, c-format msgid "partition key column %s has pseudo-type %s" msgstr "partitionsnyckelkolumn \"%s\" har pseudo-typ %s" -#: catalog/heap.c:584 +#: catalog/heap.c:585 #, c-format msgid "column \"%s\" has pseudo-type %s" msgstr "kolumn \"%s\" har pseudo-typ %s" -#: catalog/heap.c:615 +#: catalog/heap.c:616 #, c-format msgid "composite type %s cannot be made a member of itself" msgstr "composite-typ %s kan inte vara en del av sig själv" #. translator: first %s is an integer not a name -#: catalog/heap.c:670 +#: catalog/heap.c:671 #, c-format msgid "no collation was derived for partition key column %s with collatable type %s" msgstr "ingen jämförelse kunde härledas för partitionsnyckelkolumn %s med jämförelsetyp %s" -#: catalog/heap.c:676 commands/createas.c:198 commands/createas.c:505 +#: catalog/heap.c:677 commands/createas.c:198 commands/createas.c:505 #, c-format msgid "no collation was derived for column \"%s\" with collatable type %s" msgstr "ingen jämförelse kunde härledas för kolumn \"%s\" med jämförelsetyp %s" -#: catalog/heap.c:1161 catalog/index.c:899 commands/createas.c:401 +#: catalog/heap.c:1162 catalog/index.c:899 commands/createas.c:401 #: commands/tablecmds.c:4171 #, c-format msgid "relation \"%s\" already exists" msgstr "relationen \"%s\" finns redan" -#: catalog/heap.c:1177 catalog/pg_type.c:434 catalog/pg_type.c:805 +#: catalog/heap.c:1178 catalog/pg_type.c:434 catalog/pg_type.c:805 #: catalog/pg_type.c:977 commands/typecmds.c:253 commands/typecmds.c:265 #: commands/typecmds.c:758 commands/typecmds.c:1179 commands/typecmds.c:1405 #: commands/typecmds.c:1585 commands/typecmds.c:2556 @@ -4891,132 +4892,132 @@ msgstr "relationen \"%s\" finns redan" msgid "type \"%s\" already exists" msgstr "typen \"%s\" existerar redan" -#: catalog/heap.c:1178 +#: catalog/heap.c:1179 #, c-format msgid "A relation has an associated type of the same name, so you must use a name that doesn't conflict with any existing type." msgstr "En relation har en associerad typ med samma namn så du måste använda ett namn som inte krockar med någon existerande typ." -#: catalog/heap.c:1218 +#: catalog/heap.c:1219 #, c-format msgid "toast relfilenumber value not set when in binary upgrade mode" msgstr "relfile-nummervärde för toast är inte satt i binärt uppgraderingsläge" -#: catalog/heap.c:1229 +#: catalog/heap.c:1230 #, c-format msgid "pg_class heap OID value not set when in binary upgrade mode" msgstr "pg_class heap OID-värde är inte satt i binärt uppgraderingsläge" -#: catalog/heap.c:1239 +#: catalog/heap.c:1240 #, c-format msgid "relfilenumber value not set when in binary upgrade mode" msgstr "relfile-nummervärde är inte satt i binärt uppgraderingsläge" -#: catalog/heap.c:2130 +#: catalog/heap.c:2185 #, c-format msgid "cannot add NO INHERIT constraint to partitioned table \"%s\"" msgstr "kan inte lägga till NO INHERIT-villkor till partitionerad tabell \"%s\"" -#: catalog/heap.c:2402 +#: catalog/heap.c:2452 #, c-format msgid "check constraint \"%s\" already exists" msgstr "check-villkor \"%s\" finns redan" -#: catalog/heap.c:2574 catalog/index.c:913 catalog/pg_constraint.c:724 -#: commands/tablecmds.c:9356 +#: catalog/heap.c:2624 catalog/index.c:913 catalog/pg_constraint.c:725 +#: commands/tablecmds.c:9389 #, c-format msgid "constraint \"%s\" for relation \"%s\" already exists" msgstr "integritetsvillkor \"%s\" för relation \"%s\" finns redan" -#: catalog/heap.c:2581 +#: catalog/heap.c:2631 #, c-format msgid "constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"" msgstr "villkor \"%s\" står i konflikt med icke-ärvt villkor på relation \"%s\"" -#: catalog/heap.c:2592 +#: catalog/heap.c:2642 #, c-format msgid "constraint \"%s\" conflicts with inherited constraint on relation \"%s\"" msgstr "villkor \"%s\" står i konflikt med ärvt villkor på relation \"%s\"" -#: catalog/heap.c:2602 +#: catalog/heap.c:2652 #, c-format msgid "constraint \"%s\" conflicts with NOT VALID constraint on relation \"%s\"" msgstr "villkor \"%s\" står i konflikt med NOT VALID-villkor på relation \"%s\"" -#: catalog/heap.c:2607 +#: catalog/heap.c:2657 #, c-format msgid "merging constraint \"%s\" with inherited definition" msgstr "slår samman villkor \"%s\" med ärvd definition" -#: catalog/heap.c:2633 catalog/pg_constraint.c:853 commands/tablecmds.c:3074 +#: catalog/heap.c:2683 catalog/pg_constraint.c:854 commands/tablecmds.c:3074 #: commands/tablecmds.c:3377 commands/tablecmds.c:7089 -#: commands/tablecmds.c:15907 commands/tablecmds.c:16038 +#: commands/tablecmds.c:15971 commands/tablecmds.c:16102 #, c-format msgid "too many inheritance parents" msgstr "för många föräldrar i arv" -#: catalog/heap.c:2717 +#: catalog/heap.c:2767 #, c-format msgid "cannot use generated column \"%s\" in column generation expression" msgstr "kan inte använda genererad kolumn \"%s\" i kolumngenereringsuttryck" -#: catalog/heap.c:2719 +#: catalog/heap.c:2769 #, c-format msgid "A generated column cannot reference another generated column." msgstr "En genererad kolumn kan inte referera till en annan genererad kolumn." -#: catalog/heap.c:2725 +#: catalog/heap.c:2775 #, c-format msgid "cannot use whole-row variable in column generation expression" msgstr "kan inte använda hela-raden-variabel i kolumngenereringsuttryck" -#: catalog/heap.c:2726 +#: catalog/heap.c:2776 #, c-format msgid "This would cause the generated column to depend on its own value." msgstr "Detta skulle leda till att den genererade kolumnen beror på sitt eget värde." -#: catalog/heap.c:2781 +#: catalog/heap.c:2831 #, c-format msgid "generation expression is not immutable" msgstr "genereringsuttryck är inte immutable" -#: catalog/heap.c:2809 rewrite/rewriteHandler.c:1276 +#: catalog/heap.c:2859 rewrite/rewriteHandler.c:1276 #, c-format msgid "column \"%s\" is of type %s but default expression is of type %s" msgstr "kolumn \"%s\" har typ %s men default-uttryck har typen %s" -#: catalog/heap.c:2814 commands/prepare.c:331 parser/analyze.c:2758 +#: catalog/heap.c:2864 commands/prepare.c:331 parser/analyze.c:2758 #: parser/parse_target.c:592 parser/parse_target.c:882 #: parser/parse_target.c:892 rewrite/rewriteHandler.c:1281 #, c-format msgid "You will need to rewrite or cast the expression." msgstr "Du måste skriva om eller typomvandla uttrycket." -#: catalog/heap.c:2861 +#: catalog/heap.c:2911 #, c-format msgid "only table \"%s\" can be referenced in check constraint" msgstr "bara tabell \"%s\" kan refereras i check-villkoret" -#: catalog/heap.c:3167 +#: catalog/heap.c:3217 #, c-format msgid "unsupported ON COMMIT and foreign key combination" msgstr "inget stöd för kombinationen ON COMMIT och främmande nyckel" -#: catalog/heap.c:3168 +#: catalog/heap.c:3218 #, c-format msgid "Table \"%s\" references \"%s\", but they do not have the same ON COMMIT setting." msgstr "Tabell \"%s\" refererar till \"%s\", men de har inte samma ON COMMIT-inställning." -#: catalog/heap.c:3173 +#: catalog/heap.c:3223 #, c-format msgid "cannot truncate a table referenced in a foreign key constraint" msgstr "kan inte trunkera en tabell som refererars till i ett främmande nyckelvillkor" -#: catalog/heap.c:3174 +#: catalog/heap.c:3224 #, c-format msgid "Table \"%s\" references \"%s\"." msgstr "Tabell \"%s\" refererar till \"%s\"." -#: catalog/heap.c:3176 +#: catalog/heap.c:3226 #, c-format msgid "Truncate table \"%s\" at the same time, or use TRUNCATE ... CASCADE." msgstr "Trunkera tabellen \"%s\" samtidigt, eller använd TRUNCATE ... CASCADE." @@ -5092,12 +5093,12 @@ msgstr "DROP INDEX CONCURRENTLY måste vara första operationen i transaktion" msgid "cannot reindex temporary tables of other sessions" msgstr "kan inte omindexera temporära tabeller som tillhör andra sessioner" -#: catalog/index.c:3679 commands/indexcmds.c:3626 +#: catalog/index.c:3679 commands/indexcmds.c:3660 #, c-format msgid "cannot reindex invalid index on TOAST table" msgstr "kan inte omindexera angivet index i TOAST-tabell" -#: catalog/index.c:3695 commands/indexcmds.c:3504 commands/indexcmds.c:3650 +#: catalog/index.c:3695 commands/indexcmds.c:3538 commands/indexcmds.c:3684 #: commands/tablecmds.c:3581 #, c-format msgid "cannot move system relation \"%s\"" @@ -5114,7 +5115,7 @@ msgid "cannot reindex invalid index \"%s.%s\" on TOAST table, skipping" msgstr "kan inte omindexera ogiltigt index \"%s.%s\" på TOAST-tabell, hoppar över" #: catalog/namespace.c:462 catalog/namespace.c:666 catalog/namespace.c:758 -#: commands/trigger.c:5729 +#: commands/trigger.c:5817 #, c-format msgid "cross-database references are not implemented: \"%s.%s.%s\"" msgstr "referenser till andra databaser är inte implementerat: \"%s.%s.%s\"" @@ -5197,7 +5198,7 @@ msgstr "textsökkonfiguration \"%s\" finns inte" msgid "cross-database references are not implemented: %s" msgstr "referenser till andra databaser är inte implementerat: %s" -#: catalog/namespace.c:3335 gram.y:19181 gram.y:19221 parser/parse_expr.c:875 +#: catalog/namespace.c:3335 gram.y:19188 gram.y:19228 parser/parse_expr.c:875 #: parser/parse_target.c:1266 #, c-format msgid "improper qualified name (too many dotted names): %s" @@ -5251,25 +5252,25 @@ msgstr "kan inte skapa temporära tabeller under en parallell operation" #: catalog/objectaddress.c:1371 commands/policy.c:93 commands/policy.c:373 #: commands/tablecmds.c:257 commands/tablecmds.c:299 commands/tablecmds.c:2327 -#: commands/tablecmds.c:12925 +#: commands/tablecmds.c:12966 #, c-format msgid "\"%s\" is not a table" msgstr "\"%s\" är inte en tabell" #: catalog/objectaddress.c:1378 commands/tablecmds.c:269 -#: commands/tablecmds.c:17851 commands/view.c:114 +#: commands/tablecmds.c:17915 commands/view.c:114 #, c-format msgid "\"%s\" is not a view" msgstr "\"%s\" är inte en vy" #: catalog/objectaddress.c:1385 commands/matview.c:199 commands/tablecmds.c:275 -#: commands/tablecmds.c:17856 +#: commands/tablecmds.c:17920 #, c-format msgid "\"%s\" is not a materialized view" msgstr "\"%s\" är inte en materialiserad vy" #: catalog/objectaddress.c:1392 commands/tablecmds.c:293 -#: commands/tablecmds.c:17861 +#: commands/tablecmds.c:17925 #, c-format msgid "\"%s\" is not a foreign table" msgstr "\"%s\" är inte en främmande tabell" @@ -5292,7 +5293,7 @@ msgstr "standardvärde för kolumn \"%s\" i relation \"%s\" existerar inte" #: catalog/objectaddress.c:1618 commands/functioncmds.c:132 #: commands/tablecmds.c:285 commands/typecmds.c:278 commands/typecmds.c:3843 #: parser/parse_type.c:243 parser/parse_type.c:272 parser/parse_type.c:801 -#: utils/adt/acl.c:4560 +#: utils/adt/acl.c:4577 #, c-format msgid "type \"%s\" does not exist" msgstr "typen \"%s\" existerar inte" @@ -5312,8 +5313,9 @@ msgstr "funktion %d (%s, %s) för %s finns inte" msgid "user mapping for user \"%s\" on server \"%s\" does not exist" msgstr "användarmappning för användare \"%s\" på server \"%s\" finns inte" -#: catalog/objectaddress.c:1834 commands/foreigncmds.c:430 -#: commands/foreigncmds.c:993 commands/foreigncmds.c:1356 foreign/foreign.c:713 +#: catalog/objectaddress.c:1834 commands/foreigncmds.c:441 +#: commands/foreigncmds.c:1004 commands/foreigncmds.c:1367 +#: foreign/foreign.c:713 #, c-format msgid "server \"%s\" does not exist" msgstr "server \"%s\" finns inte" @@ -5950,17 +5952,17 @@ msgstr "jämförelse \"%s\" finns redan" msgid "collation \"%s\" for encoding \"%s\" already exists" msgstr "jämförelse \"%s\" för kodning \"%s\" finns redan" -#: catalog/pg_constraint.c:732 +#: catalog/pg_constraint.c:733 #, c-format msgid "constraint \"%s\" for domain %s already exists" msgstr "villkor \"%s\" för domän %s finns redan" -#: catalog/pg_constraint.c:932 catalog/pg_constraint.c:1025 +#: catalog/pg_constraint.c:933 catalog/pg_constraint.c:1026 #, c-format msgid "constraint \"%s\" for table \"%s\" does not exist" msgstr "integritetsvillkor \"%s\" för tabell \"%s\" existerar inte" -#: catalog/pg_constraint.c:1125 +#: catalog/pg_constraint.c:1126 #, c-format msgid "constraint \"%s\" for domain %s does not exist" msgstr "villkor \"%s\" för domänen %s finns inte" @@ -6046,7 +6048,7 @@ msgid "The partition is being detached concurrently or has an unfinished detach. msgstr "Partitionen kopplas loss parallellt eller har en bortkoppling som inte är slutförd." #: catalog/pg_inherits.c:595 commands/tablecmds.c:4800 -#: commands/tablecmds.c:16153 +#: commands/tablecmds.c:16217 #, c-format msgid "Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation." msgstr "Använd ALTER TABLE ... DETACH PARTITION ... FINALIZE för att slutföra den pågående bortkopplingsoperationen." @@ -6377,12 +6379,12 @@ msgstr "kan inte ta bort objekt ägda av %s eftersom de krävs av databassysteme msgid "cannot reassign ownership of objects owned by %s because they are required by the database system" msgstr "kan inte byta ägare på objekt som ägs av %s då dessa krävas av databassystemet" -#: catalog/pg_subscription.c:438 +#: catalog/pg_subscription.c:463 #, c-format msgid "could not drop relation mapping for subscription \"%s\"" msgstr "kunde inte slänga relationsmappning för prenumeration \"%s\"" -#: catalog/pg_subscription.c:440 +#: catalog/pg_subscription.c:465 #, c-format msgid "Table synchronization for relation \"%s\" is in progress and is in state \"%c\"." msgstr "Tabellsynkronisering för relation \"%s\" pågår och är i läget \"%c\"." @@ -6390,7 +6392,7 @@ msgstr "Tabellsynkronisering för relation \"%s\" pågår och är i läget \"%c\ #. translator: first %s is a SQL ALTER command and second %s is a #. SQL DROP command #. -#: catalog/pg_subscription.c:447 +#: catalog/pg_subscription.c:472 #, c-format msgid "Use %s to enable subscription if not already enabled or use %s to drop the subscription." msgstr "Använd %s för att slå på prenumerationen om den inte redan är på eller använd %s för att slänga prenumerationen." @@ -6536,12 +6538,12 @@ msgstr "parameter \"%s\" måste vara READ_ONLY, SHAREABLE eller READ_WRITE" msgid "event trigger \"%s\" already exists" msgstr "händelsetrigger \"%s\" finns redan" -#: commands/alter.c:86 commands/foreigncmds.c:593 +#: commands/alter.c:86 commands/foreigncmds.c:604 #, c-format msgid "foreign-data wrapper \"%s\" already exists" msgstr "främmande data-omvandlare \"%s\" finns redan" -#: commands/alter.c:89 commands/foreigncmds.c:884 +#: commands/alter.c:89 commands/foreigncmds.c:895 #, c-format msgid "server \"%s\" already exists" msgstr "servern \"%s\" finns redan" @@ -6642,7 +6644,7 @@ msgid "handler function is not specified" msgstr "hanterarfunktion ej angiven" #: commands/amcmds.c:264 commands/event_trigger.c:200 -#: commands/foreigncmds.c:489 commands/proclang.c:78 commands/trigger.c:702 +#: commands/foreigncmds.c:500 commands/proclang.c:78 commands/trigger.c:703 #: parser/parse_clause.c:943 #, c-format msgid "function %s must return type %s" @@ -6748,7 +6750,7 @@ msgstr "kan inte klustra temporära tabeller för andra sessioner" msgid "there is no previously clustered index for table \"%s\"" msgstr "det finns inget tidigare klustrat index för tabell \"%s\"" -#: commands/cluster.c:191 commands/tablecmds.c:14797 commands/tablecmds.c:16729 +#: commands/cluster.c:191 commands/tablecmds.c:14861 commands/tablecmds.c:16793 #, c-format msgid "index \"%s\" for table \"%s\" does not exist" msgstr "index \"%s\" för tabell \"%s\" finns inte" @@ -6763,7 +6765,7 @@ msgstr "kan inte klustra en delad katalog" msgid "cannot vacuum temporary tables of other sessions" msgstr "kan inte städa temporära tabeller för andra sessioner" -#: commands/cluster.c:513 commands/tablecmds.c:16739 +#: commands/cluster.c:513 commands/tablecmds.c:16803 #, c-format msgid "\"%s\" is not an index for table \"%s\"" msgstr "\"%s\" är inte ett index för tabell \"%s\"" @@ -6828,13 +6830,13 @@ msgid "collation attribute \"%s\" not recognized" msgstr "jämförelsesattribut \"%s\" känns inte igen" #: commands/collationcmds.c:123 commands/collationcmds.c:129 -#: commands/define.c:388 commands/tablecmds.c:8129 +#: commands/define.c:388 commands/tablecmds.c:8162 #: replication/pgoutput/pgoutput.c:314 replication/pgoutput/pgoutput.c:337 #: replication/pgoutput/pgoutput.c:351 replication/pgoutput/pgoutput.c:361 #: replication/pgoutput/pgoutput.c:371 replication/pgoutput/pgoutput.c:381 -#: replication/pgoutput/pgoutput.c:393 replication/walsender.c:1146 -#: replication/walsender.c:1168 replication/walsender.c:1178 -#: replication/walsender.c:1187 replication/walsender.c:1426 +#: replication/pgoutput/pgoutput.c:393 replication/walsender.c:1150 +#: replication/walsender.c:1172 replication/walsender.c:1182 +#: replication/walsender.c:1191 replication/walsender.c:1430 #, c-format msgid "conflicting or redundant options" msgstr "motstridiga eller redundanta inställningar" @@ -6903,9 +6905,9 @@ msgstr "kan inte refresha versionen på standardjämförelse" #. translator: %s is an SQL command #. translator: %s is an SQL ALTER command #: commands/collationcmds.c:447 commands/subscriptioncmds.c:1376 -#: commands/tablecmds.c:7905 commands/tablecmds.c:7915 -#: commands/tablecmds.c:7917 commands/tablecmds.c:14499 -#: commands/tablecmds.c:17884 commands/tablecmds.c:17905 +#: commands/tablecmds.c:7938 commands/tablecmds.c:7948 +#: commands/tablecmds.c:7950 commands/tablecmds.c:14563 +#: commands/tablecmds.c:17948 commands/tablecmds.c:17969 #: commands/typecmds.c:3787 commands/typecmds.c:3872 commands/typecmds.c:4226 #, c-format msgid "Use %s instead." @@ -7182,7 +7184,7 @@ msgstr "Genererade kolumner kan inte användas i COPY." msgid "column \"%s\" does not exist" msgstr "kolumnen \"%s\" existerar inte" -#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:951 +#: commands/copy.c:963 commands/tablecmds.c:2552 commands/trigger.c:952 #: parser/parse_target.c:1083 parser/parse_target.c:1094 #, c-format msgid "column \"%s\" specified more than once" @@ -7333,17 +7335,17 @@ msgid "could not read from COPY file: %m" msgstr "kunde inte läsa från COPY-fil: %m" #: commands/copyfromparse.c:278 commands/copyfromparse.c:303 -#: replication/walsender.c:756 replication/walsender.c:782 tcop/postgres.c:381 +#: replication/walsender.c:760 replication/walsender.c:786 tcop/postgres.c:381 #, c-format msgid "unexpected EOF on client connection with an open transaction" msgstr "oväntat EOF från klientanslutning med öppen transaktion" -#: commands/copyfromparse.c:294 replication/walsender.c:772 +#: commands/copyfromparse.c:294 replication/walsender.c:776 #, c-format msgid "unexpected message type 0x%02X during COPY from stdin" msgstr "oväntad meddelandetyp 0x%02X under COPY från stdin" -#: commands/copyfromparse.c:317 replication/walsender.c:803 +#: commands/copyfromparse.c:317 replication/walsender.c:807 #, c-format msgid "COPY from stdin failed: %s" msgstr "COPY från stdin misslyckades: %s" @@ -8014,7 +8016,7 @@ msgstr "Använd DROP AGGREGATE för att ta bort aggregatfunktioner." #: commands/dropcmds.c:153 commands/sequence.c:462 commands/tablecmds.c:3892 #: commands/tablecmds.c:4050 commands/tablecmds.c:4102 -#: commands/tablecmds.c:17161 tcop/utility.c:1325 +#: commands/tablecmds.c:17225 tcop/utility.c:1325 #, c-format msgid "relation \"%s\" does not exist, skipping" msgstr "relation \"%s\" finns inte, hoppar över" @@ -8139,7 +8141,7 @@ msgstr "regel \"%s\" för relation \"%s\" finns inte, hoppar över" msgid "foreign-data wrapper \"%s\" does not exist, skipping" msgstr "främmande data-omvandlare \"%s\" finns inte, hoppar över" -#: commands/dropcmds.c:448 commands/foreigncmds.c:1360 +#: commands/dropcmds.c:448 commands/foreigncmds.c:1371 #, c-format msgid "server \"%s\" does not exist, skipping" msgstr "servern \"%s\" finns inte, hoppar över" @@ -8549,112 +8551,112 @@ msgstr "kunde inte hitta multirange-typ för datatyp %s" msgid "file \"%s\" is too large" msgstr "filen \"%s\" är för stor" -#: commands/foreigncmds.c:148 commands/foreigncmds.c:157 +#: commands/foreigncmds.c:159 commands/foreigncmds.c:168 #, c-format msgid "option \"%s\" not found" msgstr "flaggan \"%s\" hittades inte" -#: commands/foreigncmds.c:167 +#: commands/foreigncmds.c:178 #, c-format msgid "option \"%s\" provided more than once" msgstr "flaggan \"%s\" angiven mer än en gång" -#: commands/foreigncmds.c:221 commands/foreigncmds.c:229 +#: commands/foreigncmds.c:232 commands/foreigncmds.c:240 #, c-format msgid "permission denied to change owner of foreign-data wrapper \"%s\"" msgstr "rättighet saknas för att byta ägare på främmande data-omvandlare \"%s\"" -#: commands/foreigncmds.c:223 +#: commands/foreigncmds.c:234 #, c-format msgid "Must be superuser to change owner of a foreign-data wrapper." msgstr "Måste vara superuser för att byta ägare på en främmande data-omvandlare." -#: commands/foreigncmds.c:231 +#: commands/foreigncmds.c:242 #, c-format msgid "The owner of a foreign-data wrapper must be a superuser." msgstr "Ägaren av en främmande data-omvandlare måste vara en superuser." -#: commands/foreigncmds.c:291 commands/foreigncmds.c:707 foreign/foreign.c:691 +#: commands/foreigncmds.c:302 commands/foreigncmds.c:718 foreign/foreign.c:691 #, c-format msgid "foreign-data wrapper \"%s\" does not exist" msgstr "främmande data-omvandlare \"%s\" finns inte" -#: commands/foreigncmds.c:325 +#: commands/foreigncmds.c:336 #, c-format msgid "foreign-data wrapper with OID %u does not exist" msgstr "främmande data-omvandlare med OID %u finns inte" -#: commands/foreigncmds.c:462 +#: commands/foreigncmds.c:473 #, c-format msgid "foreign server with OID %u does not exist" msgstr "främmande server med OID %u finns inte" -#: commands/foreigncmds.c:580 +#: commands/foreigncmds.c:591 #, c-format msgid "permission denied to create foreign-data wrapper \"%s\"" msgstr "rättighet saknas för att skapa främmande data-omvandlare \"%s\"" -#: commands/foreigncmds.c:582 +#: commands/foreigncmds.c:593 #, c-format msgid "Must be superuser to create a foreign-data wrapper." msgstr "Måste vara superuser för att skapa främmande data-omvandlare." -#: commands/foreigncmds.c:697 +#: commands/foreigncmds.c:708 #, c-format msgid "permission denied to alter foreign-data wrapper \"%s\"" msgstr "rättighet saknas för att ändra främmande data-omvandlare \"%s\"" -#: commands/foreigncmds.c:699 +#: commands/foreigncmds.c:710 #, c-format msgid "Must be superuser to alter a foreign-data wrapper." msgstr "Måste vara superuser för att ändra främmande data-omvandlare." -#: commands/foreigncmds.c:730 +#: commands/foreigncmds.c:741 #, c-format msgid "changing the foreign-data wrapper handler can change behavior of existing foreign tables" msgstr "att ändra främmande data-omvandlares hanterare kan byta beteende på existerande främmande tabeller" -#: commands/foreigncmds.c:745 +#: commands/foreigncmds.c:756 #, c-format msgid "changing the foreign-data wrapper validator can cause the options for dependent objects to become invalid" msgstr "att ändra främmande data-omvandlares validator kan göra att flaggor för beroende objekt invalideras" -#: commands/foreigncmds.c:876 +#: commands/foreigncmds.c:887 #, c-format msgid "server \"%s\" already exists, skipping" msgstr "server \"%s\" finns redan, hoppar över" -#: commands/foreigncmds.c:1144 +#: commands/foreigncmds.c:1155 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\", skipping" msgstr "användarmappning för \"%s\" finns redan för server \"%s\", hoppar över" -#: commands/foreigncmds.c:1154 +#: commands/foreigncmds.c:1165 #, c-format msgid "user mapping for \"%s\" already exists for server \"%s\"" msgstr "användarmappning för \"%s\" finns redan för server \"%s\"" -#: commands/foreigncmds.c:1254 commands/foreigncmds.c:1374 +#: commands/foreigncmds.c:1265 commands/foreigncmds.c:1385 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\"" msgstr "användarmappning för \"%s\" finns inte för servern \"%s\"" -#: commands/foreigncmds.c:1379 +#: commands/foreigncmds.c:1390 #, c-format msgid "user mapping for \"%s\" does not exist for server \"%s\", skipping" msgstr "användarmappning för \"%s\" finns inte för servern \"%s\", hoppar över" -#: commands/foreigncmds.c:1507 foreign/foreign.c:404 +#: commands/foreigncmds.c:1518 foreign/foreign.c:404 #, c-format msgid "foreign-data wrapper \"%s\" has no handler" msgstr "främmande data-omvandlare \"%s\" har ingen hanterare" -#: commands/foreigncmds.c:1513 +#: commands/foreigncmds.c:1524 #, c-format msgid "foreign-data wrapper \"%s\" does not support IMPORT FOREIGN SCHEMA" msgstr "främmande data-omvandlare \"%s\" stöder inte IMPORT FOREIGN SCHEMA" -#: commands/foreigncmds.c:1615 +#: commands/foreigncmds.c:1626 #, c-format msgid "importing foreign table \"%s\"" msgstr "importerar främmande tabell \"%s\"" @@ -9178,8 +9180,8 @@ msgstr "inkluderad kolumn stöder inte NULLS FIRST/LAST-flaggor" msgid "could not determine which collation to use for index expression" msgstr "kunde inte bestämma vilken jämförelse (collation) som skulle användas för indexuttryck" -#: commands/indexcmds.c:2028 commands/tablecmds.c:18185 commands/typecmds.c:811 -#: parser/parse_expr.c:2785 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 +#: commands/indexcmds.c:2028 commands/tablecmds.c:18249 commands/typecmds.c:811 +#: parser/parse_expr.c:2793 parser/parse_type.c:568 parser/parse_utilcmd.c:3771 #: utils/adt/misc.c:630 #, c-format msgid "collations are not supported by type %s" @@ -9215,8 +9217,8 @@ msgstr "accessmetod \"%s\" stöder inte ASC/DESC-flaggor" msgid "access method \"%s\" does not support NULLS FIRST/LAST options" msgstr "accessmetod \"%s\" stöder inte NULLS FIRST/LAST-flaggor" -#: commands/indexcmds.c:2210 commands/tablecmds.c:18210 -#: commands/tablecmds.c:18216 commands/typecmds.c:2311 +#: commands/indexcmds.c:2210 commands/tablecmds.c:18274 +#: commands/tablecmds.c:18280 commands/typecmds.c:2311 #, c-format msgid "data type %s has no default operator class for access method \"%s\"" msgstr "datatyp %s har ingen standardoperatorklass för accessmetod \"%s\"" @@ -9242,88 +9244,88 @@ msgstr "operatorklass \"%s\" accepterar inte datatypen %s" msgid "there are multiple default operator classes for data type %s" msgstr "det finns flera standardoperatorklasser för datatypen %s" -#: commands/indexcmds.c:2681 +#: commands/indexcmds.c:2715 #, c-format msgid "unrecognized REINDEX option \"%s\"" msgstr "okänd REINDEX-flagga \"%s\"" -#: commands/indexcmds.c:2913 +#: commands/indexcmds.c:2947 #, c-format msgid "table \"%s\" has no indexes that can be reindexed concurrently" msgstr "tabell \"%s\" har inga index som kan reindexeras parallellt" -#: commands/indexcmds.c:2927 +#: commands/indexcmds.c:2961 #, c-format msgid "table \"%s\" has no indexes to reindex" msgstr "tabell \"%s\" har inga index som kan omindexeras" -#: commands/indexcmds.c:2974 commands/indexcmds.c:3485 -#: commands/indexcmds.c:3615 +#: commands/indexcmds.c:3008 commands/indexcmds.c:3519 +#: commands/indexcmds.c:3649 #, c-format msgid "cannot reindex system catalogs concurrently" msgstr "kan inte omindexera systemkataloger parallellt" -#: commands/indexcmds.c:2998 +#: commands/indexcmds.c:3032 #, c-format msgid "can only reindex the currently open database" msgstr "kan bara omindexera den aktiva databasen" -#: commands/indexcmds.c:3090 +#: commands/indexcmds.c:3124 #, c-format msgid "cannot reindex system catalogs concurrently, skipping all" msgstr "kan inte omindexera systemkataloger parallellt, hoppar över alla" -#: commands/indexcmds.c:3123 +#: commands/indexcmds.c:3157 #, c-format msgid "cannot move system relations, skipping all" msgstr "kan inte flytta systemrelationer, hoppar över alla" -#: commands/indexcmds.c:3169 +#: commands/indexcmds.c:3203 #, c-format msgid "while reindexing partitioned table \"%s.%s\"" msgstr "vid omindexering av partitionerad tabell \"%s.%s\"" -#: commands/indexcmds.c:3172 +#: commands/indexcmds.c:3206 #, c-format msgid "while reindexing partitioned index \"%s.%s\"" msgstr "vid omindexering av partitionerat index \"%s.%s\"" -#: commands/indexcmds.c:3365 commands/indexcmds.c:4241 +#: commands/indexcmds.c:3399 commands/indexcmds.c:4283 #, c-format msgid "table \"%s.%s\" was reindexed" msgstr "tabell \"%s.%s\" omindexerades" -#: commands/indexcmds.c:3517 commands/indexcmds.c:3570 +#: commands/indexcmds.c:3551 commands/indexcmds.c:3604 #, c-format msgid "skipping reindex of invalid index \"%s.%s\"" msgstr "hoppar över omindexering av ogiltigt index \"%s.%s\"" -#: commands/indexcmds.c:3520 commands/indexcmds.c:3573 +#: commands/indexcmds.c:3554 commands/indexcmds.c:3607 #, c-format msgid "Use DROP INDEX or REINDEX INDEX." msgstr "Använd DROP INDEX eller REINDEX INDEX." -#: commands/indexcmds.c:3524 +#: commands/indexcmds.c:3558 #, c-format msgid "cannot reindex exclusion constraint index \"%s.%s\" concurrently, skipping" msgstr "kan inte parallellt omindexera uteslutningsvillkorsindex \"%s.%s\", hoppar över" -#: commands/indexcmds.c:3680 +#: commands/indexcmds.c:3714 #, c-format msgid "cannot reindex this type of relation concurrently" msgstr "kan inte parallellt omindexera denna sorts relation" -#: commands/indexcmds.c:3698 +#: commands/indexcmds.c:3732 #, c-format msgid "cannot move non-shared relation to tablespace \"%s\"" msgstr "kan inte flytta ickedelad relation till tabellutryumme \"%s\"" -#: commands/indexcmds.c:4222 commands/indexcmds.c:4234 +#: commands/indexcmds.c:4264 commands/indexcmds.c:4276 #, c-format msgid "index \"%s.%s\" was reindexed" msgstr "index \"%s.%s\" omindexerades" -#: commands/indexcmds.c:4224 commands/indexcmds.c:4243 +#: commands/indexcmds.c:4266 commands/indexcmds.c:4285 #, c-format msgid "%s." msgstr "%s." @@ -9338,7 +9340,7 @@ msgstr "kan inte låsa relationen \"%s\"" msgid "CONCURRENTLY cannot be used when the materialized view is not populated" msgstr "CONCURRENTLY kan inte användas när den materialiserade vyn inte är populerad" -#: commands/matview.c:212 gram.y:18918 +#: commands/matview.c:212 gram.y:18925 #, c-format msgid "%s and %s options cannot be used together" msgstr "flaggorna %s och %s kan inte användas ihop" @@ -9655,9 +9657,9 @@ msgstr "operatorattribut \"%s\" kan inte ändras när det redan har satts" #: commands/policy.c:86 commands/policy.c:379 commands/statscmds.c:146 #: commands/tablecmds.c:1740 commands/tablecmds.c:2340 #: commands/tablecmds.c:3702 commands/tablecmds.c:6605 -#: commands/tablecmds.c:9637 commands/tablecmds.c:17772 -#: commands/tablecmds.c:17807 commands/trigger.c:316 commands/trigger.c:1332 -#: commands/trigger.c:1442 rewrite/rewriteDefine.c:268 +#: commands/tablecmds.c:9670 commands/tablecmds.c:17836 +#: commands/tablecmds.c:17871 commands/trigger.c:317 commands/trigger.c:1333 +#: commands/trigger.c:1443 rewrite/rewriteDefine.c:268 #: rewrite/rewriteDefine.c:779 rewrite/rewriteRemove.c:74 #, c-format msgid "permission denied: \"%s\" is a system catalog" @@ -9709,7 +9711,7 @@ msgid "cannot create a cursor WITH HOLD within security-restricted operation" msgstr "kan inte skapa en WITH HOLD-markör i en säkerhetsbegränsad operation" #: commands/portalcmds.c:189 commands/portalcmds.c:242 -#: executor/execCurrent.c:70 utils/adt/xml.c:2936 utils/adt/xml.c:3106 +#: executor/execCurrent.c:70 utils/adt/xml.c:2938 utils/adt/xml.c:3108 #, c-format msgid "cursor \"%s\" does not exist" msgstr "markör \"%s\" existerar inte" @@ -10106,8 +10108,8 @@ msgstr "tabellen måste vara i samma schema som tabellen den är länkad till" msgid "cannot change ownership of identity sequence" msgstr "kan inte byta ägare på identitetssekvens" -#: commands/sequence.c:1671 commands/tablecmds.c:14486 -#: commands/tablecmds.c:17181 +#: commands/sequence.c:1671 commands/tablecmds.c:14550 +#: commands/tablecmds.c:17245 #, c-format msgid "Sequence \"%s\" is linked to table \"%s\"." msgstr "Sekvens \"%s\" är länkad till tabell \"%s\"" @@ -10177,12 +10179,12 @@ msgstr "duplicerade kolumnnamn i statistikdefinition" msgid "duplicate expression in statistics definition" msgstr "duplicerade uttryck i statistikdefinition" -#: commands/statscmds.c:628 commands/tablecmds.c:8620 +#: commands/statscmds.c:628 commands/tablecmds.c:8653 #, c-format msgid "statistics target %d is too low" msgstr "statistikmålet %d är för lågt" -#: commands/statscmds.c:636 commands/tablecmds.c:8628 +#: commands/statscmds.c:636 commands/tablecmds.c:8661 #, c-format msgid "lowering statistics target to %d" msgstr "minskar statistikmålet till %d" @@ -10244,8 +10246,8 @@ msgid "Only roles with privileges of the \"%s\" role may create subscriptions." msgstr "Bara roller med rättigheter från rollen \"%s\" får skapa prenumerationer" #: commands/subscriptioncmds.c:758 commands/subscriptioncmds.c:891 -#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1345 -#: replication/logical/worker.c:4503 +#: commands/subscriptioncmds.c:1524 replication/logical/tablesync.c:1371 +#: replication/logical/worker.c:4524 #, c-format msgid "could not connect to the publisher: %s" msgstr "kunde inte ansluta till publicerare: %s" @@ -10352,66 +10354,66 @@ msgstr "kunde inte slänga replikeringsslotten \"%s\" på publicerare: %s" msgid "subscription with OID %u does not exist" msgstr "prenumeration med OID %u existerar inte" -#: commands/subscriptioncmds.c:2076 commands/subscriptioncmds.c:2201 +#: commands/subscriptioncmds.c:2079 commands/subscriptioncmds.c:2204 #, c-format msgid "could not receive list of replicated tables from the publisher: %s" msgstr "kunde inte ta emot lista med replikerade tabeller från publiceraren: %s" -#: commands/subscriptioncmds.c:2112 +#: commands/subscriptioncmds.c:2115 #, c-format msgid "subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin" msgstr "prenumeration \"%s\" begärde copy_data med origin = NONE men kan kopiera data från en annan källa" -#: commands/subscriptioncmds.c:2114 +#: commands/subscriptioncmds.c:2117 #, c-format msgid "The subscription being created subscribes to a publication (%s) that contains tables that are written to by other subscriptions." msgid_plural "The subscription being created subscribes to publications (%s) that contain tables that are written to by other subscriptions." msgstr[0] "Prenumerationen som skapas läser från publlicering (%s) som innehåller tabell som skrivs till från andra prenumerationer." msgstr[1] "Prenumerationen som skapas läser från publliceringar (%s) som innehåller tabell som skrivs till från andra prenumerationer." -#: commands/subscriptioncmds.c:2117 +#: commands/subscriptioncmds.c:2120 #, c-format msgid "Verify that initial data copied from the publisher tables did not come from other origins." msgstr "Kontrollera att den initiala datan som kopieras från publicerade tabeller inte kom från andra källor." -#: commands/subscriptioncmds.c:2223 replication/logical/tablesync.c:906 +#: commands/subscriptioncmds.c:2226 replication/logical/tablesync.c:932 #: replication/pgoutput/pgoutput.c:1143 #, c-format msgid "cannot use different column lists for table \"%s.%s\" in different publications" msgstr "kunde inte ha olika kolumnlistor för tabellen \"%s.%s\" i olika publiceringar" -#: commands/subscriptioncmds.c:2273 +#: commands/subscriptioncmds.c:2276 #, c-format msgid "could not connect to publisher when attempting to drop replication slot \"%s\": %s" msgstr "kunde inte ansluta till publicerare vid försök att slänga replikeringsslot \"%s\": %s" #. translator: %s is an SQL ALTER command -#: commands/subscriptioncmds.c:2276 +#: commands/subscriptioncmds.c:2279 #, c-format msgid "Use %s to disable the subscription, and then use %s to disassociate it from the slot." msgstr "Använd %s för att stänga av prenumerationen och sedan %s för att dissociera den från slotten." -#: commands/subscriptioncmds.c:2307 +#: commands/subscriptioncmds.c:2310 #, c-format msgid "publication name \"%s\" used more than once" msgstr "publiceringsnamn \"%s\" använt mer än en gång" -#: commands/subscriptioncmds.c:2351 +#: commands/subscriptioncmds.c:2354 #, c-format msgid "publication \"%s\" is already in subscription \"%s\"" msgstr "publicering \"%s\" finns redan i prenumerationen \"%s\"" -#: commands/subscriptioncmds.c:2365 +#: commands/subscriptioncmds.c:2368 #, c-format msgid "publication \"%s\" is not in subscription \"%s\"" msgstr "publicering \"%s\" finns inte i prenumerationen \"%s\"" -#: commands/subscriptioncmds.c:2376 +#: commands/subscriptioncmds.c:2379 #, c-format msgid "cannot drop all the publications from a subscription" msgstr "kan inte slänga alla publiceringar från en prenumeration" -#: commands/subscriptioncmds.c:2433 +#: commands/subscriptioncmds.c:2436 #, c-format msgid "%s requires a Boolean value or \"parallel\"" msgstr "%s kräver ett booleskt värde eller \"parallel\"" @@ -10472,7 +10474,7 @@ msgstr "materialiserad vy \"%s\" finns inte, hoppar över" msgid "Use DROP MATERIALIZED VIEW to remove a materialized view." msgstr "Använd DROP MATERIALIZED VIEW för att ta bort en materialiserad vy." -#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19805 +#: commands/tablecmds.c:279 commands/tablecmds.c:303 commands/tablecmds.c:19877 #: parser/parse_utilcmd.c:2251 #, c-format msgid "index \"%s\" does not exist" @@ -10496,8 +10498,8 @@ msgstr "\"%s\" är inte en typ" msgid "Use DROP TYPE to remove a type." msgstr "Använd DROP TYPE för att ta bort en typ." -#: commands/tablecmds.c:291 commands/tablecmds.c:14325 -#: commands/tablecmds.c:16886 +#: commands/tablecmds.c:291 commands/tablecmds.c:14389 +#: commands/tablecmds.c:16950 #, c-format msgid "foreign table \"%s\" does not exist" msgstr "främmande tabell \"%s\" finns inte" @@ -10521,7 +10523,7 @@ msgstr "ON COMMIT kan bara användas på temporära tabeller" msgid "cannot create temporary table within security-restricted operation" msgstr "kan inte skapa temporär tabell i en säkerhetsbegränsad operation" -#: commands/tablecmds.c:801 commands/tablecmds.c:15745 +#: commands/tablecmds.c:801 commands/tablecmds.c:15809 #, c-format msgid "relation \"%s\" would be inherited from more than once" msgstr "relationen \"%s\" skulle ärvas mer än en gång" @@ -10546,7 +10548,7 @@ msgstr "kan inte skapa främmande partition för partitionerad tabell \"%s\"" msgid "Table \"%s\" contains indexes that are unique." msgstr "Tabell \"%s\" innehåller index som är unika." -#: commands/tablecmds.c:1338 commands/tablecmds.c:13341 +#: commands/tablecmds.c:1338 commands/tablecmds.c:13382 #, c-format msgid "too many array dimensions" msgstr "för många array-dimensioner" @@ -10597,7 +10599,7 @@ msgstr "kan inte trunkera främmande tabell \"%s\"" msgid "cannot truncate temporary tables of other sessions" msgstr "kan inte trunkera temporära tabeller tillhörande andra sessioner" -#: commands/tablecmds.c:2606 commands/tablecmds.c:15642 +#: commands/tablecmds.c:2606 commands/tablecmds.c:15706 #, c-format msgid "cannot inherit from partitioned table \"%s\"" msgstr "kan inte ärva från partitionerad tabell \"%s\"" @@ -10618,18 +10620,18 @@ msgstr "ärvd relation \"%s\" är inte en tabell eller främmande tabell" msgid "cannot create a temporary relation as partition of permanent relation \"%s\"" msgstr "kan inte skapa en temporär relation som partition till en permanent relation \"%s\"" -#: commands/tablecmds.c:2640 commands/tablecmds.c:15621 +#: commands/tablecmds.c:2640 commands/tablecmds.c:15685 #, c-format msgid "cannot inherit from temporary relation \"%s\"" msgstr "kan inte ärva från en temporär relation \"%s\"" -#: commands/tablecmds.c:2650 commands/tablecmds.c:15629 +#: commands/tablecmds.c:2650 commands/tablecmds.c:15693 #, c-format msgid "cannot inherit from temporary relation of another session" msgstr "kan inte ärva från en temporär relation i en annan session" #: commands/tablecmds.c:2791 commands/tablecmds.c:2845 -#: commands/tablecmds.c:13024 parser/parse_utilcmd.c:1265 +#: commands/tablecmds.c:13065 parser/parse_utilcmd.c:1265 #: parser/parse_utilcmd.c:1308 parser/parse_utilcmd.c:1735 #: parser/parse_utilcmd.c:1843 #, c-format @@ -10923,12 +10925,12 @@ msgstr "kan inte lägga till kolumn till typad tabell" msgid "cannot add column to a partition" msgstr "kan inte lägga till kolumn till partition" -#: commands/tablecmds.c:7072 commands/tablecmds.c:15860 +#: commands/tablecmds.c:7072 commands/tablecmds.c:15924 #, c-format msgid "child table \"%s\" has different type for column \"%s\"" msgstr "barntabell \"%s\" har annan typ på kolumn \"%s\"" -#: commands/tablecmds.c:7078 commands/tablecmds.c:15866 +#: commands/tablecmds.c:7078 commands/tablecmds.c:15930 #, c-format msgid "child table \"%s\" has different collation for column \"%s\"" msgstr "barntabell \"%s\" har annan jämförelse (collation) på kolumn \"%s\"" @@ -10943,980 +10945,980 @@ msgstr "slår samman definitionen av kolumn \"%s\" för barn \"%s\"" msgid "cannot recursively add identity column to table that has child tables" msgstr "kan inte rekursivt lägga till identitetskolumn till tabell som har barntabeller" -#: commands/tablecmds.c:7362 +#: commands/tablecmds.c:7395 #, c-format msgid "column must be added to child tables too" msgstr "kolumnen måste läggas till i barntabellerna också" -#: commands/tablecmds.c:7440 +#: commands/tablecmds.c:7473 #, c-format msgid "column \"%s\" of relation \"%s\" already exists, skipping" msgstr "kolumn \"%s\" i relation \"%s\" finns redan, hoppar över" -#: commands/tablecmds.c:7447 +#: commands/tablecmds.c:7480 #, c-format msgid "column \"%s\" of relation \"%s\" already exists" msgstr "kolumn \"%s\" i relation \"%s\" finns redan" -#: commands/tablecmds.c:7513 commands/tablecmds.c:12652 +#: commands/tablecmds.c:7546 commands/tablecmds.c:12693 #, c-format msgid "cannot remove constraint from only the partitioned table when partitions exist" msgstr "kan inte ta bort villkor från bara den partitionerade tabellen när partitioner finns" -#: commands/tablecmds.c:7514 commands/tablecmds.c:7828 -#: commands/tablecmds.c:8006 commands/tablecmds.c:8113 -#: commands/tablecmds.c:8230 commands/tablecmds.c:9049 -#: commands/tablecmds.c:12653 +#: commands/tablecmds.c:7547 commands/tablecmds.c:7861 +#: commands/tablecmds.c:8039 commands/tablecmds.c:8146 +#: commands/tablecmds.c:8263 commands/tablecmds.c:9082 +#: commands/tablecmds.c:12694 #, c-format msgid "Do not specify the ONLY keyword." msgstr "Ange inte nyckelordet ONLY." -#: commands/tablecmds.c:7550 commands/tablecmds.c:7754 -#: commands/tablecmds.c:7896 commands/tablecmds.c:8028 -#: commands/tablecmds.c:8157 commands/tablecmds.c:8251 -#: commands/tablecmds.c:8352 commands/tablecmds.c:8509 -#: commands/tablecmds.c:8662 commands/tablecmds.c:8743 -#: commands/tablecmds.c:8877 commands/tablecmds.c:12806 -#: commands/tablecmds.c:14348 commands/tablecmds.c:16975 +#: commands/tablecmds.c:7583 commands/tablecmds.c:7787 +#: commands/tablecmds.c:7929 commands/tablecmds.c:8061 +#: commands/tablecmds.c:8190 commands/tablecmds.c:8284 +#: commands/tablecmds.c:8385 commands/tablecmds.c:8542 +#: commands/tablecmds.c:8695 commands/tablecmds.c:8776 +#: commands/tablecmds.c:8910 commands/tablecmds.c:12847 +#: commands/tablecmds.c:14412 commands/tablecmds.c:17039 #, c-format msgid "cannot alter system column \"%s\"" msgstr "kan inte ändra systemkolumn \"%s\"" -#: commands/tablecmds.c:7556 commands/tablecmds.c:7902 +#: commands/tablecmds.c:7589 commands/tablecmds.c:7935 #, c-format msgid "column \"%s\" of relation \"%s\" is an identity column" msgstr "kolumn \"%s\" i relation \"%s\" är en identitetskolumn" -#: commands/tablecmds.c:7597 +#: commands/tablecmds.c:7630 #, c-format msgid "column \"%s\" is in a primary key" msgstr "kolumn \"%s\" är del av en primärnyckel" -#: commands/tablecmds.c:7602 +#: commands/tablecmds.c:7635 #, c-format msgid "column \"%s\" is in index used as replica identity" msgstr "kolumnen \"%s\" finns i ett index som används som replikaidentitet" -#: commands/tablecmds.c:7625 +#: commands/tablecmds.c:7658 #, c-format msgid "column \"%s\" is marked NOT NULL in parent table" msgstr "kolumn \"%s\" är markerad NOT NULL i föräldratabellen" -#: commands/tablecmds.c:7825 commands/tablecmds.c:9533 +#: commands/tablecmds.c:7858 commands/tablecmds.c:9566 #, c-format msgid "constraint must be added to child tables too" msgstr "villkoret måste läggas till i barntabellerna också" -#: commands/tablecmds.c:7826 +#: commands/tablecmds.c:7859 #, c-format msgid "Column \"%s\" of relation \"%s\" is not already NOT NULL." msgstr "Kolumn \"%s\" i relation \"%s\" är inte redan NOT NULL." -#: commands/tablecmds.c:7911 +#: commands/tablecmds.c:7944 #, c-format msgid "column \"%s\" of relation \"%s\" is a generated column" msgstr "kolumn \"%s\" i relation \"%s\" är en genererad kolumn" -#: commands/tablecmds.c:8005 +#: commands/tablecmds.c:8038 #, c-format msgid "cannot add identity to a column of only the partitioned table" msgstr "kan inte lägga till identitet för kolumn i bara den partitionerade tabellen" -#: commands/tablecmds.c:8011 +#: commands/tablecmds.c:8044 #, c-format msgid "cannot add identity to a column of a partition" msgstr "kan inte lägga till identitet för kolumn i en partition" -#: commands/tablecmds.c:8039 +#: commands/tablecmds.c:8072 #, c-format msgid "column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added" msgstr "kolumn \"%s\" i relation \"%s\" måste deklareras NOT NULL innan identitet kan läggas till" -#: commands/tablecmds.c:8045 +#: commands/tablecmds.c:8078 #, c-format msgid "column \"%s\" of relation \"%s\" is already an identity column" msgstr "kolumn \"%s\" i relation \"%s\" är redan en identitetskolumn" -#: commands/tablecmds.c:8051 +#: commands/tablecmds.c:8084 #, c-format msgid "column \"%s\" of relation \"%s\" already has a default value" msgstr "kolumn \"%s\" i relation \"%s\" har redan ett standardvärde" -#: commands/tablecmds.c:8112 +#: commands/tablecmds.c:8145 #, c-format msgid "cannot change identity column of only the partitioned table" msgstr "kan inte ändra identitetskolumn för bara den partitionerade tabellen" -#: commands/tablecmds.c:8118 +#: commands/tablecmds.c:8151 #, c-format msgid "cannot change identity column of a partition" msgstr "kan inte ändra identitetskolumn för en partition" -#: commands/tablecmds.c:8163 commands/tablecmds.c:8259 +#: commands/tablecmds.c:8196 commands/tablecmds.c:8292 #, c-format msgid "column \"%s\" of relation \"%s\" is not an identity column" msgstr "kolumn \"%s\" i relation \"%s\" är inte en identitetkolumn" -#: commands/tablecmds.c:8229 +#: commands/tablecmds.c:8262 #, c-format msgid "cannot drop identity from a column of only the partitioned table" msgstr "kan inte slänga identitetskolumn från bara den partitionerade tabellen" -#: commands/tablecmds.c:8235 +#: commands/tablecmds.c:8268 #, c-format msgid "cannot drop identity from a column of a partition" msgstr "kan inte slänga identitetskolumn för en partition" -#: commands/tablecmds.c:8264 +#: commands/tablecmds.c:8297 #, c-format msgid "column \"%s\" of relation \"%s\" is not an identity column, skipping" msgstr "kolumn \"%s\" i relation \"%s\" är inte en identitetkolumn, hoppar över" -#: commands/tablecmds.c:8358 +#: commands/tablecmds.c:8391 #, c-format msgid "column \"%s\" of relation \"%s\" is not a generated column" msgstr "kolumn \"%s\" i relation \"%s\" är inte en genererad kolumn" -#: commands/tablecmds.c:8456 +#: commands/tablecmds.c:8489 #, c-format msgid "ALTER TABLE / DROP EXPRESSION must be applied to child tables too" msgstr "ALTER TABLE / DROP EXPRESSION måste appliceras på barntabellerna också" -#: commands/tablecmds.c:8478 +#: commands/tablecmds.c:8511 #, c-format msgid "cannot drop generation expression from inherited column" msgstr "kan inte slänga genererat uttryck på ärvd kolumn" -#: commands/tablecmds.c:8517 +#: commands/tablecmds.c:8550 #, c-format msgid "column \"%s\" of relation \"%s\" is not a stored generated column" msgstr "kolumn \"%s\" i relation \"%s\" är inte en lagrad genererad kolumn" -#: commands/tablecmds.c:8522 +#: commands/tablecmds.c:8555 #, c-format msgid "column \"%s\" of relation \"%s\" is not a stored generated column, skipping" msgstr "kolumn \"%s\" i relation \"%s\" är inte en lagrad genererad kolumn, hoppar över" -#: commands/tablecmds.c:8600 +#: commands/tablecmds.c:8633 #, c-format msgid "cannot refer to non-index column by number" msgstr "kan inte referera per nummer till en icke-index-kolumn " -#: commands/tablecmds.c:8652 +#: commands/tablecmds.c:8685 #, c-format msgid "column number %d of relation \"%s\" does not exist" msgstr "kolumnnummer %d i relation \"%s\" finns inte" -#: commands/tablecmds.c:8671 +#: commands/tablecmds.c:8704 #, c-format msgid "cannot alter statistics on included column \"%s\" of index \"%s\"" msgstr "kan inte ändra statistik på inkluderad kolumn \"%s\" i index \"%s\"" -#: commands/tablecmds.c:8676 +#: commands/tablecmds.c:8709 #, c-format msgid "cannot alter statistics on non-expression column \"%s\" of index \"%s\"" msgstr "kan inte ändra statistik på icke-villkorskolumn \"%s\" i index \"%s\"" -#: commands/tablecmds.c:8678 +#: commands/tablecmds.c:8711 #, c-format msgid "Alter statistics on table column instead." msgstr "Ändra statistik på tabellkolumn istället." -#: commands/tablecmds.c:8924 +#: commands/tablecmds.c:8957 #, c-format msgid "cannot drop column from typed table" msgstr "kan inte ta bort kolumn från typad tabell" -#: commands/tablecmds.c:8987 +#: commands/tablecmds.c:9020 #, c-format msgid "column \"%s\" of relation \"%s\" does not exist, skipping" msgstr "kolumn \"%s\" i relation \"%s\" finns inte, hoppar över" -#: commands/tablecmds.c:9000 +#: commands/tablecmds.c:9033 #, c-format msgid "cannot drop system column \"%s\"" msgstr "kan inte ta bort systemkolumn \"%s\"" -#: commands/tablecmds.c:9010 +#: commands/tablecmds.c:9043 #, c-format msgid "cannot drop inherited column \"%s\"" msgstr "kan inte ta bort ärvd kolumn \"%s\"" -#: commands/tablecmds.c:9023 +#: commands/tablecmds.c:9056 #, c-format msgid "cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"" msgstr "kan inte slänga kolumnen \"%s\" då den är del av partitionsnyckeln för relationen \"%s\"" -#: commands/tablecmds.c:9048 +#: commands/tablecmds.c:9081 #, c-format msgid "cannot drop column from only the partitioned table when partitions exist" msgstr "kan inte slänga kolumn från bara den partitionerade tabellen när partitioner finns" -#: commands/tablecmds.c:9253 +#: commands/tablecmds.c:9286 #, c-format msgid "ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables" msgstr "ALTER TABLE / ADD CONSTRAINT USING INDEX stöds inte på partionerade tabeller" -#: commands/tablecmds.c:9278 +#: commands/tablecmds.c:9311 #, c-format msgid "ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"" msgstr "ALTER TABLE / ADD CONSTRAINT USING INDEX kommer byta namn på index \"%s\" till \"%s\"" -#: commands/tablecmds.c:9615 +#: commands/tablecmds.c:9648 #, c-format msgid "cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"" msgstr "kan inte använda ONLY på främmande nyckel för partitionerad tabell \"%s\" som refererar till relationen \"%s\"" -#: commands/tablecmds.c:9621 +#: commands/tablecmds.c:9654 #, c-format msgid "cannot add NOT VALID foreign key on partitioned table \"%s\" referencing relation \"%s\"" msgstr "kan inte lägga till NOT VALID främmande nyckel till partitionerad tabell \"%s\" som refererar till relationen \"%s\"" -#: commands/tablecmds.c:9624 +#: commands/tablecmds.c:9657 #, c-format msgid "This feature is not yet supported on partitioned tables." msgstr "Denna finess stöds inte än på partitionerade tabeller." -#: commands/tablecmds.c:9631 commands/tablecmds.c:10092 +#: commands/tablecmds.c:9664 commands/tablecmds.c:10146 #, c-format msgid "referenced relation \"%s\" is not a table" msgstr "refererad relation \"%s\" är inte en tabell" -#: commands/tablecmds.c:9654 +#: commands/tablecmds.c:9687 #, c-format msgid "constraints on permanent tables may reference only permanent tables" msgstr "villkor på permanenta tabeller får bara referera till permanenta tabeller" -#: commands/tablecmds.c:9661 +#: commands/tablecmds.c:9694 #, c-format msgid "constraints on unlogged tables may reference only permanent or unlogged tables" msgstr "villkor på ologgade tabeller får bara referera till permanenta eller ologgade tabeller" -#: commands/tablecmds.c:9667 +#: commands/tablecmds.c:9700 #, c-format msgid "constraints on temporary tables may reference only temporary tables" msgstr "villkor på temporära tabeller får bara referera till temporära tabeller" -#: commands/tablecmds.c:9671 +#: commands/tablecmds.c:9704 #, c-format msgid "constraints on temporary tables must involve temporary tables of this session" msgstr "villkor på temporära tabeller får bara ta med temporära tabeller från denna session" -#: commands/tablecmds.c:9735 commands/tablecmds.c:9741 +#: commands/tablecmds.c:9769 commands/tablecmds.c:9775 #, c-format msgid "invalid %s action for foreign key constraint containing generated column" msgstr "ogiltig %s-aktion för främmande nyckelvillkor som innehåller genererad kolumn" -#: commands/tablecmds.c:9757 +#: commands/tablecmds.c:9791 #, c-format msgid "number of referencing and referenced columns for foreign key disagree" msgstr "antalet refererande och refererade kolumner för främmande nyckel stämmer ej överens" -#: commands/tablecmds.c:9864 +#: commands/tablecmds.c:9898 #, c-format msgid "foreign key constraint \"%s\" cannot be implemented" msgstr "främmande nyckelvillkor \"%s\" kan inte implementeras" -#: commands/tablecmds.c:9866 +#: commands/tablecmds.c:9900 #, c-format msgid "Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s." msgstr "Nyckelkolumner \"%s\" och \"%s\" har inkompatibla typer %s och %s." -#: commands/tablecmds.c:10035 +#: commands/tablecmds.c:10075 #, c-format msgid "column \"%s\" referenced in ON DELETE SET action must be part of foreign key" msgstr "kolumn \"%s\" refererad i ON DELETE SET-aktion måste vara en del av en främmande nyckel" -#: commands/tablecmds.c:10392 commands/tablecmds.c:10832 +#: commands/tablecmds.c:10446 commands/tablecmds.c:10873 #: parser/parse_utilcmd.c:822 parser/parse_utilcmd.c:945 #, c-format msgid "foreign key constraints are not supported on foreign tables" msgstr "främmande nyckel-villkor stöds inte för främmande tabeller" -#: commands/tablecmds.c:10815 +#: commands/tablecmds.c:10856 #, c-format msgid "cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"" msgstr "kan inte ansluta tabell \"%s\" som en partition då den refereras av främmande nyckel \"%s\"" -#: commands/tablecmds.c:11416 commands/tablecmds.c:11697 -#: commands/tablecmds.c:12609 commands/tablecmds.c:12683 +#: commands/tablecmds.c:11457 commands/tablecmds.c:11738 +#: commands/tablecmds.c:12650 commands/tablecmds.c:12724 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist" msgstr "villkor \"%s\" i relation \"%s\" finns inte" -#: commands/tablecmds.c:11423 +#: commands/tablecmds.c:11464 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key constraint" msgstr "villkor \"%s\" i relation \"%s\" är inte ett främmande nyckelvillkor" -#: commands/tablecmds.c:11461 +#: commands/tablecmds.c:11502 #, c-format msgid "cannot alter constraint \"%s\" on relation \"%s\"" msgstr "kan inte ändra villkoret \"%s\" i relation \"%s\"" -#: commands/tablecmds.c:11464 +#: commands/tablecmds.c:11505 #, c-format msgid "Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\"." msgstr "Villkoret \"%s\" är härlett från villkoret \"%s\" i relation \"%s\"" -#: commands/tablecmds.c:11466 +#: commands/tablecmds.c:11507 #, c-format msgid "You may alter the constraint it derives from instead." msgstr "Du kan istället ändra på villkoret det är härlett från." -#: commands/tablecmds.c:11705 +#: commands/tablecmds.c:11746 #, c-format msgid "constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint" msgstr "villkor \"%s\" i relation \"%s\" är inte en främmande nyckel eller ett check-villkor" -#: commands/tablecmds.c:11782 +#: commands/tablecmds.c:11823 #, c-format msgid "constraint must be validated on child tables too" msgstr "villkoret måste valideras för barntabellerna också" -#: commands/tablecmds.c:11869 +#: commands/tablecmds.c:11910 #, c-format msgid "column \"%s\" referenced in foreign key constraint does not exist" msgstr "kolumn \"%s\" som refereras till i främmande nyckelvillkor finns inte" -#: commands/tablecmds.c:11875 +#: commands/tablecmds.c:11916 #, c-format msgid "system columns cannot be used in foreign keys" msgstr "systemkolumner kan inte användas i främmande nycklar" -#: commands/tablecmds.c:11879 +#: commands/tablecmds.c:11920 #, c-format msgid "cannot have more than %d keys in a foreign key" msgstr "kan inte ha mer än %d nycklar i en främmande nyckel" -#: commands/tablecmds.c:11944 +#: commands/tablecmds.c:11985 #, c-format msgid "cannot use a deferrable primary key for referenced table \"%s\"" msgstr "kan inte använda en \"deferrable\" primärnyckel för refererad tabell \"%s\"" -#: commands/tablecmds.c:11961 +#: commands/tablecmds.c:12002 #, c-format msgid "there is no primary key for referenced table \"%s\"" msgstr "det finns ingen primärnyckel för refererad tabell \"%s\"" -#: commands/tablecmds.c:12029 +#: commands/tablecmds.c:12070 #, c-format msgid "foreign key referenced-columns list must not contain duplicates" msgstr "främmande nyckel-refererade kolumnlistor får inte innehålla duplikat" -#: commands/tablecmds.c:12121 +#: commands/tablecmds.c:12162 #, c-format msgid "cannot use a deferrable unique constraint for referenced table \"%s\"" msgstr "kan inte använda ett \"deferrable\" unikt integritetsvillkor för refererad tabell \"%s\"" -#: commands/tablecmds.c:12126 +#: commands/tablecmds.c:12167 #, c-format msgid "there is no unique constraint matching given keys for referenced table \"%s\"" msgstr "finns inget unique-villkor som matchar de givna nycklarna i den refererade tabellen \"%s\"" -#: commands/tablecmds.c:12565 +#: commands/tablecmds.c:12606 #, c-format msgid "cannot drop inherited constraint \"%s\" of relation \"%s\"" msgstr "kan inte ta bort ärvt villkor \"%s\" i relation \"%s\"" -#: commands/tablecmds.c:12615 +#: commands/tablecmds.c:12656 #, c-format msgid "constraint \"%s\" of relation \"%s\" does not exist, skipping" msgstr "villkor \"%s\" i relation \"%s\" finns inte, hoppar över" -#: commands/tablecmds.c:12790 +#: commands/tablecmds.c:12831 #, c-format msgid "cannot alter column type of typed table" msgstr "kan inte ändra kolumntyp på typad tabell" -#: commands/tablecmds.c:12816 +#: commands/tablecmds.c:12857 #, c-format msgid "cannot specify USING when altering type of generated column" msgstr "kan inte ange USING när man ändrar typ på en genererad kolumn" -#: commands/tablecmds.c:12817 commands/tablecmds.c:18028 -#: commands/tablecmds.c:18118 commands/trigger.c:656 +#: commands/tablecmds.c:12858 commands/tablecmds.c:18092 +#: commands/tablecmds.c:18182 commands/trigger.c:657 #: rewrite/rewriteHandler.c:941 rewrite/rewriteHandler.c:976 #, c-format msgid "Column \"%s\" is a generated column." msgstr "Kolumnen \"%s\" är en genererad kolumn." -#: commands/tablecmds.c:12827 +#: commands/tablecmds.c:12868 #, c-format msgid "cannot alter inherited column \"%s\"" msgstr "kan inte ändra ärvd kolumn \"%s\"" -#: commands/tablecmds.c:12836 +#: commands/tablecmds.c:12877 #, c-format msgid "cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"" msgstr "kan inte ändra kolumnen \"%s\" då den är del av partitionsnyckeln för relationen \"%s\"" -#: commands/tablecmds.c:12886 +#: commands/tablecmds.c:12927 #, c-format msgid "result of USING clause for column \"%s\" cannot be cast automatically to type %s" msgstr "resultatet av USING-klausul för kolumn \"%s\" kan inte automatiskt typomvandlas till typen %s" -#: commands/tablecmds.c:12889 +#: commands/tablecmds.c:12930 #, c-format msgid "You might need to add an explicit cast." msgstr "Du kan behöva lägga till en explicit typomvandling." -#: commands/tablecmds.c:12893 +#: commands/tablecmds.c:12934 #, c-format msgid "column \"%s\" cannot be cast automatically to type %s" msgstr "kolumn \"%s\" kan inte automatiskt typomvandlas till typ %s" #. translator: USING is SQL, don't translate it -#: commands/tablecmds.c:12897 +#: commands/tablecmds.c:12938 #, c-format msgid "You might need to specify \"USING %s::%s\"." msgstr "Du kan behöva ange \"USING %s::%s\"." -#: commands/tablecmds.c:12996 +#: commands/tablecmds.c:13037 #, c-format msgid "cannot alter inherited column \"%s\" of relation \"%s\"" msgstr "kan inte ändra ärvd kolumn \"%s\" i relation \"%s\"" -#: commands/tablecmds.c:13025 +#: commands/tablecmds.c:13066 #, c-format msgid "USING expression contains a whole-row table reference." msgstr "USING-uttryck innehåller en hela-raden-tabellreferens." -#: commands/tablecmds.c:13036 +#: commands/tablecmds.c:13077 #, c-format msgid "type of inherited column \"%s\" must be changed in child tables too" msgstr "typen av den ärvda kolumnen \"%s\" måste ändras i barntabellerna också" -#: commands/tablecmds.c:13161 +#: commands/tablecmds.c:13202 #, c-format msgid "cannot alter type of column \"%s\" twice" msgstr "kan inte ändra typen på kolumn \"%s\" två gånger" -#: commands/tablecmds.c:13199 +#: commands/tablecmds.c:13240 #, c-format msgid "generation expression for column \"%s\" cannot be cast automatically to type %s" msgstr "genereringsuttryck för kolumn \"%s\" kan inte automatiskt typomvandlas till typ %s" -#: commands/tablecmds.c:13204 +#: commands/tablecmds.c:13245 #, c-format msgid "default for column \"%s\" cannot be cast automatically to type %s" msgstr "\"default\" för kolumn \"%s\" kan inte automatiskt typomvandlas till typ \"%s\"" -#: commands/tablecmds.c:13508 +#: commands/tablecmds.c:13549 #, c-format msgid "cannot alter type of a column used by a function or procedure" msgstr "kan inte ändra typ på en kolumn som används av en funktion eller procedur" -#: commands/tablecmds.c:13509 commands/tablecmds.c:13524 -#: commands/tablecmds.c:13544 commands/tablecmds.c:13563 -#: commands/tablecmds.c:13622 +#: commands/tablecmds.c:13550 commands/tablecmds.c:13565 +#: commands/tablecmds.c:13585 commands/tablecmds.c:13604 +#: commands/tablecmds.c:13663 #, c-format msgid "%s depends on column \"%s\"" msgstr "%s beror på kolumn \"%s\"" -#: commands/tablecmds.c:13523 +#: commands/tablecmds.c:13564 #, c-format msgid "cannot alter type of a column used by a view or rule" msgstr "kan inte ändra typ på en kolumn som används av en vy eller en regel" -#: commands/tablecmds.c:13543 +#: commands/tablecmds.c:13584 #, c-format msgid "cannot alter type of a column used in a trigger definition" msgstr "kan inte ändra typ på en kolumn som används i en triggerdefinition" -#: commands/tablecmds.c:13562 +#: commands/tablecmds.c:13603 #, c-format msgid "cannot alter type of a column used in a policy definition" msgstr "kan inte ändra typ på en kolumn som används av i en policydefinition" -#: commands/tablecmds.c:13593 +#: commands/tablecmds.c:13634 #, c-format msgid "cannot alter type of a column used by a generated column" msgstr "kan inte ändra typ på en kolumn som används av en genererad kolumn" -#: commands/tablecmds.c:13594 +#: commands/tablecmds.c:13635 #, c-format msgid "Column \"%s\" is used by generated column \"%s\"." msgstr "Kolumn \"%s\" används av genererad kolumn \"%s\"." -#: commands/tablecmds.c:13621 +#: commands/tablecmds.c:13662 #, c-format msgid "cannot alter type of a column used by a publication WHERE clause" msgstr "kan inte ändra typ på en kolumn som används av en publicerings WHERE-klausul" -#: commands/tablecmds.c:14456 commands/tablecmds.c:14468 +#: commands/tablecmds.c:14520 commands/tablecmds.c:14532 #, c-format msgid "cannot change owner of index \"%s\"" msgstr "kan inte byta ägare på index \"%s\"" -#: commands/tablecmds.c:14458 commands/tablecmds.c:14470 +#: commands/tablecmds.c:14522 commands/tablecmds.c:14534 #, c-format msgid "Change the ownership of the index's table instead." msgstr "Byt ägare på indexets tabell istället." -#: commands/tablecmds.c:14484 +#: commands/tablecmds.c:14548 #, c-format msgid "cannot change owner of sequence \"%s\"" msgstr "kan inte byta ägare på sekvens \"%s\"" -#: commands/tablecmds.c:14509 +#: commands/tablecmds.c:14573 #, c-format msgid "cannot change owner of relation \"%s\"" msgstr "kan inte byta ägare på relationen \"%s\"" -#: commands/tablecmds.c:14976 +#: commands/tablecmds.c:15040 #, c-format msgid "cannot have multiple SET TABLESPACE subcommands" msgstr "kan inte ha flera underkommandon SET TABLESPACE" -#: commands/tablecmds.c:15053 +#: commands/tablecmds.c:15117 #, c-format msgid "cannot set options for relation \"%s\"" msgstr "kan inte sätta inställningar på relationen \"%s\"" -#: commands/tablecmds.c:15087 commands/view.c:440 +#: commands/tablecmds.c:15151 commands/view.c:440 #, c-format msgid "WITH CHECK OPTION is supported only on automatically updatable views" msgstr "WITH CHECK OPTION stöds bara på automatiskt uppdateringsbara vyer" -#: commands/tablecmds.c:15338 +#: commands/tablecmds.c:15402 #, c-format msgid "only tables, indexes, and materialized views exist in tablespaces" msgstr "bara tabeller, index och materialiserade vyer finns i tablespace:er" -#: commands/tablecmds.c:15350 +#: commands/tablecmds.c:15414 #, c-format msgid "cannot move relations in to or out of pg_global tablespace" msgstr "kan inte flytta relationer in eller ut från tablespace pg_global" -#: commands/tablecmds.c:15442 +#: commands/tablecmds.c:15506 #, c-format msgid "aborting because lock on relation \"%s.%s\" is not available" msgstr "avbryter då lås på relation \"%s.%s\" inte är tillgängligt" -#: commands/tablecmds.c:15458 +#: commands/tablecmds.c:15522 #, c-format msgid "no matching relations in tablespace \"%s\" found" msgstr "inga matchande relationer i tablespace \"%s\" hittades" -#: commands/tablecmds.c:15580 +#: commands/tablecmds.c:15644 #, c-format msgid "cannot change inheritance of typed table" msgstr "kan inte ändra arv på en typad tabell" -#: commands/tablecmds.c:15585 commands/tablecmds.c:16085 +#: commands/tablecmds.c:15649 commands/tablecmds.c:16149 #, c-format msgid "cannot change inheritance of a partition" msgstr "kan inte ändra arv på en partition" -#: commands/tablecmds.c:15590 +#: commands/tablecmds.c:15654 #, c-format msgid "cannot change inheritance of partitioned table" msgstr "kan inte ändra arv på en partitionerad tabell" -#: commands/tablecmds.c:15636 +#: commands/tablecmds.c:15700 #, c-format msgid "cannot inherit to temporary relation of another session" msgstr "kan inte ärva av en temporär tabell för en annan session" -#: commands/tablecmds.c:15649 +#: commands/tablecmds.c:15713 #, c-format msgid "cannot inherit from a partition" msgstr "kan inte ärva från en partition" -#: commands/tablecmds.c:15671 commands/tablecmds.c:18529 +#: commands/tablecmds.c:15735 commands/tablecmds.c:18593 #, c-format msgid "circular inheritance not allowed" msgstr "cirkulärt arv är inte tillåtet" -#: commands/tablecmds.c:15672 commands/tablecmds.c:18530 +#: commands/tablecmds.c:15736 commands/tablecmds.c:18594 #, c-format msgid "\"%s\" is already a child of \"%s\"." msgstr "\"%s\" är redan ett barn till \"%s\"" -#: commands/tablecmds.c:15685 +#: commands/tablecmds.c:15749 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming an inheritance child" msgstr "trigger \"%s\" förhindrar tabell \"%s\" från att bli ett arvsbarn" -#: commands/tablecmds.c:15687 +#: commands/tablecmds.c:15751 #, c-format msgid "ROW triggers with transition tables are not supported in inheritance hierarchies." msgstr "ROW-triggrar med övergångstabeller stöds inte i arvshierarkier." -#: commands/tablecmds.c:15876 +#: commands/tablecmds.c:15940 #, c-format msgid "column \"%s\" in child table must be marked NOT NULL" msgstr "kolumn \"%s\" i barntabell måste vara markerad NOT NULL" -#: commands/tablecmds.c:15885 +#: commands/tablecmds.c:15949 #, c-format msgid "column \"%s\" in child table must be a generated column" msgstr "kolumn \"%s\" i barntabell måste vara en genererad kolumn" -#: commands/tablecmds.c:15889 +#: commands/tablecmds.c:15953 #, c-format msgid "column \"%s\" in child table must not be a generated column" msgstr "kolumn \"%s\" i barntabell kan inte vara en genererad kolumn" -#: commands/tablecmds.c:15927 +#: commands/tablecmds.c:15991 #, c-format msgid "child table is missing column \"%s\"" msgstr "barntabell saknar kolumn \"%s\"" -#: commands/tablecmds.c:16008 +#: commands/tablecmds.c:16072 #, c-format msgid "child table \"%s\" has different definition for check constraint \"%s\"" msgstr "barntabell \"%s\" har annan definition av check-villkor \"%s\"" -#: commands/tablecmds.c:16015 +#: commands/tablecmds.c:16079 #, c-format msgid "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"" msgstr "villkor \"%s\" står i konflikt med icke-ärvt villkor på barntabell \"%s\"" -#: commands/tablecmds.c:16025 +#: commands/tablecmds.c:16089 #, c-format msgid "constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"" msgstr "villkor \"%s\" står i konflikt med NOT VALID-villkor på barntabell \"%s\"" -#: commands/tablecmds.c:16063 +#: commands/tablecmds.c:16127 #, c-format msgid "child table is missing constraint \"%s\"" msgstr "barntabell saknar riktighetsvillkor \"%s\"" -#: commands/tablecmds.c:16149 +#: commands/tablecmds.c:16213 #, c-format msgid "partition \"%s\" already pending detach in partitioned table \"%s.%s\"" msgstr "partition \"%s\" har redan en pågående bortkoppling i partitionerad tabell \"%s.%s\"" -#: commands/tablecmds.c:16178 commands/tablecmds.c:16224 +#: commands/tablecmds.c:16242 commands/tablecmds.c:16288 #, c-format msgid "relation \"%s\" is not a partition of relation \"%s\"" msgstr "relationen \"%s\" är inte partition av relationen \"%s\"" -#: commands/tablecmds.c:16230 +#: commands/tablecmds.c:16294 #, c-format msgid "relation \"%s\" is not a parent of relation \"%s\"" msgstr "relationen \"%s\" är inte en förälder till relationen \"%s\"" -#: commands/tablecmds.c:16457 +#: commands/tablecmds.c:16521 #, c-format msgid "typed tables cannot inherit" msgstr "typade tabeller kan inte ärva" -#: commands/tablecmds.c:16487 +#: commands/tablecmds.c:16551 #, c-format msgid "table is missing column \"%s\"" msgstr "tabell saknar kolumn \"%s\"" -#: commands/tablecmds.c:16498 +#: commands/tablecmds.c:16562 #, c-format msgid "table has column \"%s\" where type requires \"%s\"" msgstr "tabell har kolumn \"%s\" där typen kräver \"%s\"" -#: commands/tablecmds.c:16507 +#: commands/tablecmds.c:16571 #, c-format msgid "table \"%s\" has different type for column \"%s\"" msgstr "tabell \"%s\" har annan typ på kolumn \"%s\"" -#: commands/tablecmds.c:16521 +#: commands/tablecmds.c:16585 #, c-format msgid "table has extra column \"%s\"" msgstr "tabell har extra kolumn \"%s\"" -#: commands/tablecmds.c:16573 +#: commands/tablecmds.c:16637 #, c-format msgid "\"%s\" is not a typed table" msgstr "\"%s\" är inte en typad tabell" -#: commands/tablecmds.c:16747 +#: commands/tablecmds.c:16811 #, c-format msgid "cannot use non-unique index \"%s\" as replica identity" msgstr "kan inte använda icke-unikt index \"%s\" som replikaidentitet" -#: commands/tablecmds.c:16753 +#: commands/tablecmds.c:16817 #, c-format msgid "cannot use non-immediate index \"%s\" as replica identity" msgstr "kan inte använda icke-immediate-index \"%s\" som replikaidentitiet" -#: commands/tablecmds.c:16759 +#: commands/tablecmds.c:16823 #, c-format msgid "cannot use expression index \"%s\" as replica identity" msgstr "kan inte använda uttrycksindex \"%s\" som replikaidentitiet" -#: commands/tablecmds.c:16765 +#: commands/tablecmds.c:16829 #, c-format msgid "cannot use partial index \"%s\" as replica identity" msgstr "kan inte använda partiellt index \"%s\" som replikaidentitiet" -#: commands/tablecmds.c:16782 +#: commands/tablecmds.c:16846 #, c-format msgid "index \"%s\" cannot be used as replica identity because column %d is a system column" msgstr "index \"%s\" kan inte användas som replikaidentitet då kolumn %d är en systemkolumn" -#: commands/tablecmds.c:16789 +#: commands/tablecmds.c:16853 #, c-format msgid "index \"%s\" cannot be used as replica identity because column \"%s\" is nullable" msgstr "index \"%s\" kan inte användas som replikaidentitet då kolumn \"%s\" kan vare null" -#: commands/tablecmds.c:17041 +#: commands/tablecmds.c:17105 #, c-format msgid "cannot change logged status of table \"%s\" because it is temporary" msgstr "kan inte ändra loggningsstatus för tabell \"%s\" då den är temporär" -#: commands/tablecmds.c:17065 +#: commands/tablecmds.c:17129 #, c-format msgid "cannot change table \"%s\" to unlogged because it is part of a publication" msgstr "kan inte ändra tabell \"%s\" till ologgad då den är del av en publicering" -#: commands/tablecmds.c:17067 +#: commands/tablecmds.c:17131 #, c-format msgid "Unlogged relations cannot be replicated." msgstr "Ologgade relatrioner kan inte replikeras." -#: commands/tablecmds.c:17112 +#: commands/tablecmds.c:17176 #, c-format msgid "could not change table \"%s\" to logged because it references unlogged table \"%s\"" msgstr "kunde inte ändra tabell \"%s\" till loggad då den refererar till ologgad tabell \"%s\"" -#: commands/tablecmds.c:17122 +#: commands/tablecmds.c:17186 #, c-format msgid "could not change table \"%s\" to unlogged because it references logged table \"%s\"" msgstr "kunde inte ändra tabell \"%s\" till ologgad då den refererar till loggad tabell \"%s\"" -#: commands/tablecmds.c:17180 +#: commands/tablecmds.c:17244 #, c-format msgid "cannot move an owned sequence into another schema" msgstr "kan inte flytta en ägd sekvens till ett annan schema." -#: commands/tablecmds.c:17288 +#: commands/tablecmds.c:17352 #, c-format msgid "relation \"%s\" already exists in schema \"%s\"" msgstr "relationen \"%s\" finns redan i schema \"%s\"" -#: commands/tablecmds.c:17713 +#: commands/tablecmds.c:17777 #, c-format msgid "\"%s\" is not a table or materialized view" msgstr "\"%s\" är inte en tabell eller materialiserad vy" -#: commands/tablecmds.c:17866 +#: commands/tablecmds.c:17930 #, c-format msgid "\"%s\" is not a composite type" msgstr "\"%s\" är inte en composite-typ" -#: commands/tablecmds.c:17896 +#: commands/tablecmds.c:17960 #, c-format msgid "cannot change schema of index \"%s\"" msgstr "kan inte byta schema på indexet \"%s\"" -#: commands/tablecmds.c:17898 commands/tablecmds.c:17912 +#: commands/tablecmds.c:17962 commands/tablecmds.c:17976 #, c-format msgid "Change the schema of the table instead." msgstr "Byt ägare på tabellen istället." -#: commands/tablecmds.c:17902 +#: commands/tablecmds.c:17966 #, c-format msgid "cannot change schema of composite type \"%s\"" msgstr "kan inte byta schema på composite-typen \"%s\"." -#: commands/tablecmds.c:17910 +#: commands/tablecmds.c:17974 #, c-format msgid "cannot change schema of TOAST table \"%s\"" msgstr "kan inte byta schema på TOAST-tabellen \"%s\"" -#: commands/tablecmds.c:17942 +#: commands/tablecmds.c:18006 #, c-format msgid "cannot use \"list\" partition strategy with more than one column" msgstr "kan inte använda list-partioneringsstrategi med mer än en kolumn" -#: commands/tablecmds.c:18008 +#: commands/tablecmds.c:18072 #, c-format msgid "column \"%s\" named in partition key does not exist" msgstr "kolumn \"%s\" angiven i partitioneringsnyckel existerar inte" -#: commands/tablecmds.c:18016 +#: commands/tablecmds.c:18080 #, c-format msgid "cannot use system column \"%s\" in partition key" msgstr "kan inte använda systemkolumn \"%s\" i partitioneringsnyckel" -#: commands/tablecmds.c:18027 commands/tablecmds.c:18117 +#: commands/tablecmds.c:18091 commands/tablecmds.c:18181 #, c-format msgid "cannot use generated column in partition key" msgstr "kan inte använda genererad kolumn i partitioneringsnyckel" -#: commands/tablecmds.c:18100 +#: commands/tablecmds.c:18164 #, c-format msgid "partition key expressions cannot contain system column references" msgstr "partitioneringsnyckeluttryck kan inte innehålla systemkolumnreferenser" -#: commands/tablecmds.c:18147 +#: commands/tablecmds.c:18211 #, c-format msgid "functions in partition key expression must be marked IMMUTABLE" msgstr "funktioner i partitioneringsuttryck måste vara markerade IMMUTABLE" -#: commands/tablecmds.c:18156 +#: commands/tablecmds.c:18220 #, c-format msgid "cannot use constant expression as partition key" msgstr "kan inte använda konstant uttryck som partitioneringsnyckel" -#: commands/tablecmds.c:18177 +#: commands/tablecmds.c:18241 #, c-format msgid "could not determine which collation to use for partition expression" msgstr "kunde inte lista vilken jämförelse (collation) som skulle användas för partitionsuttryck" -#: commands/tablecmds.c:18212 +#: commands/tablecmds.c:18276 #, c-format msgid "You must specify a hash operator class or define a default hash operator class for the data type." msgstr "Du måste ange en hash-operatorklass eller definiera en default hash-operatorklass för datatypen." -#: commands/tablecmds.c:18218 +#: commands/tablecmds.c:18282 #, c-format msgid "You must specify a btree operator class or define a default btree operator class for the data type." msgstr "Du måste ange en btree-operatorklass eller definiera en default btree-operatorklass för datatypen." -#: commands/tablecmds.c:18469 +#: commands/tablecmds.c:18533 #, c-format msgid "\"%s\" is already a partition" msgstr "\"%s\" är redan en partition" -#: commands/tablecmds.c:18475 +#: commands/tablecmds.c:18539 #, c-format msgid "cannot attach a typed table as partition" msgstr "kan inte ansluta en typad tabell som partition" -#: commands/tablecmds.c:18491 +#: commands/tablecmds.c:18555 #, c-format msgid "cannot attach inheritance child as partition" msgstr "kan inte ansluta ett arvsbarn som partition" -#: commands/tablecmds.c:18505 +#: commands/tablecmds.c:18569 #, c-format msgid "cannot attach inheritance parent as partition" msgstr "kan inte ansluta en arvsförälder som partition" -#: commands/tablecmds.c:18539 +#: commands/tablecmds.c:18603 #, c-format msgid "cannot attach a temporary relation as partition of permanent relation \"%s\"" msgstr "kan inte ansluta en temporär relation som partition till en permanent relation \"%s\"" -#: commands/tablecmds.c:18547 +#: commands/tablecmds.c:18611 #, c-format msgid "cannot attach a permanent relation as partition of temporary relation \"%s\"" msgstr "kan inte ansluta en permanent relation som partition till en temporär relation \"%s\"" -#: commands/tablecmds.c:18555 +#: commands/tablecmds.c:18619 #, c-format msgid "cannot attach as partition of temporary relation of another session" msgstr "kan inte ansluta en partition från en temporär relation som tillhör en annan session" -#: commands/tablecmds.c:18562 +#: commands/tablecmds.c:18626 #, c-format msgid "cannot attach temporary relation of another session as partition" msgstr "kan inte ansluta en temporär relation tillhörande en annan session som partition" -#: commands/tablecmds.c:18582 +#: commands/tablecmds.c:18646 #, c-format msgid "table \"%s\" being attached contains an identity column \"%s\"" msgstr "tabell \"%s\" som ansluts innehåller en identitetskolumn \"%s\"" -#: commands/tablecmds.c:18584 +#: commands/tablecmds.c:18648 #, c-format msgid "The new partition may not contain an identity column." msgstr "Den nya partitionen får inte innehålla en identitetskolumn." -#: commands/tablecmds.c:18592 +#: commands/tablecmds.c:18656 #, c-format msgid "table \"%s\" contains column \"%s\" not found in parent \"%s\"" msgstr "tabell \"%s\" innehåller kolumn \"%s\" som inte finns i föräldern \"%s\"" -#: commands/tablecmds.c:18595 +#: commands/tablecmds.c:18659 #, c-format msgid "The new partition may contain only the columns present in parent." msgstr "Den nya partitionen får bara innehålla kolumner som finns i föräldern." -#: commands/tablecmds.c:18607 +#: commands/tablecmds.c:18671 #, c-format msgid "trigger \"%s\" prevents table \"%s\" from becoming a partition" msgstr "trigger \"%s\" förhindrar att tabell \"%s\" blir en partition" -#: commands/tablecmds.c:18609 +#: commands/tablecmds.c:18673 #, c-format msgid "ROW triggers with transition tables are not supported on partitions." msgstr "ROW-triggrar med övergångstabeller stöds inte för partitioner." -#: commands/tablecmds.c:18785 +#: commands/tablecmds.c:18849 #, c-format msgid "cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"" msgstr "kan inte ansluta främmande tabell \"%s\" som en partition till partitionerad tabell \"%s\"" -#: commands/tablecmds.c:18788 +#: commands/tablecmds.c:18852 #, c-format msgid "Partitioned table \"%s\" contains unique indexes." msgstr "Partitionerad tabell \"%s\" innehåller unika index." -#: commands/tablecmds.c:19110 +#: commands/tablecmds.c:19174 #, c-format msgid "cannot detach partitions concurrently when a default partition exists" msgstr "kan inte parallellt koppla bort en partitionerad tabell när en default-partition finns" -#: commands/tablecmds.c:19219 +#: commands/tablecmds.c:19283 #, c-format msgid "partitioned table \"%s\" was removed concurrently" msgstr "partitionerad tabell \"%s\" togs bort parallellt" -#: commands/tablecmds.c:19225 +#: commands/tablecmds.c:19289 #, c-format msgid "partition \"%s\" was removed concurrently" msgstr "partition \"%s\" togs bort parallellt" -#: commands/tablecmds.c:19839 commands/tablecmds.c:19859 -#: commands/tablecmds.c:19880 commands/tablecmds.c:19899 -#: commands/tablecmds.c:19941 +#: commands/tablecmds.c:19911 commands/tablecmds.c:19931 +#: commands/tablecmds.c:19952 commands/tablecmds.c:19971 +#: commands/tablecmds.c:20013 #, c-format msgid "cannot attach index \"%s\" as a partition of index \"%s\"" msgstr "kan inte ansluta index \"%s\" som en partition till index \"%s\"" -#: commands/tablecmds.c:19842 +#: commands/tablecmds.c:19914 #, c-format msgid "Index \"%s\" is already attached to another index." msgstr "Index \"%s\" är redan ansluten till ett annat index." -#: commands/tablecmds.c:19862 +#: commands/tablecmds.c:19934 #, c-format msgid "Index \"%s\" is not an index on any partition of table \"%s\"." msgstr "Index \"%s\" är inte ett index för någon partition av tabell \"%s\"." -#: commands/tablecmds.c:19883 +#: commands/tablecmds.c:19955 #, c-format msgid "The index definitions do not match." msgstr "Indexdefinitionerna matchar inte." -#: commands/tablecmds.c:19902 +#: commands/tablecmds.c:19974 #, c-format msgid "The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\"." msgstr "Indexet \"%s\" tillhör ett villkor på tabell \"%s\" men det finns inga villkor för indexet \"%s\"." -#: commands/tablecmds.c:19944 +#: commands/tablecmds.c:20016 #, c-format msgid "Another index is already attached for partition \"%s\"." msgstr "Ett annat index är redan anslutet för partition \"%s\"." -#: commands/tablecmds.c:20180 +#: commands/tablecmds.c:20252 #, c-format msgid "column data type %s does not support compression" msgstr "kolumndatatypen %s stöder inte komprimering" -#: commands/tablecmds.c:20187 +#: commands/tablecmds.c:20259 #, c-format msgid "invalid compression method \"%s\"" msgstr "ogiltig komprimeringsmetod \"%s\"" -#: commands/tablecmds.c:20213 +#: commands/tablecmds.c:20285 #, c-format msgid "invalid storage type \"%s\"" msgstr "ogiltig lagringstyp \"%s\"" -#: commands/tablecmds.c:20223 +#: commands/tablecmds.c:20295 #, c-format msgid "column data type %s can only have storage PLAIN" msgstr "kolumndatatyp %s kan bara ha lagringsmetod PLAIN" @@ -12043,281 +12045,286 @@ msgstr "kataloger för %u kan inte tas bort" msgid "You can remove the directories manually if necessary." msgstr "Du kan ta bort dessa kataloger på egen hand om nödvändigt." -#: commands/trigger.c:225 commands/trigger.c:236 +#: commands/trigger.c:226 commands/trigger.c:237 #, c-format msgid "\"%s\" is a table" msgstr "\"%s\" är en tabell" -#: commands/trigger.c:227 commands/trigger.c:238 +#: commands/trigger.c:228 commands/trigger.c:239 #, c-format msgid "Tables cannot have INSTEAD OF triggers." msgstr "Tabeller kan inte ha INSTEAD OF-triggrar." -#: commands/trigger.c:259 +#: commands/trigger.c:260 #, c-format msgid "\"%s\" is a partitioned table" msgstr "\"%s\" är en partitionerad tabell" -#: commands/trigger.c:261 +#: commands/trigger.c:262 #, c-format msgid "ROW triggers with transition tables are not supported on partitioned tables." msgstr "ROW-triggrar med övergångstabeller stöds inte för partitionerade tabeller." -#: commands/trigger.c:273 commands/trigger.c:280 commands/trigger.c:444 +#: commands/trigger.c:274 commands/trigger.c:281 commands/trigger.c:445 #, c-format msgid "\"%s\" is a view" msgstr "\"%s\" är en vy" -#: commands/trigger.c:275 +#: commands/trigger.c:276 #, c-format msgid "Views cannot have row-level BEFORE or AFTER triggers." msgstr "Vyer kan inte ha BEFORE- eller AFTER-triggrar på radnivå." -#: commands/trigger.c:282 +#: commands/trigger.c:283 #, c-format msgid "Views cannot have TRUNCATE triggers." msgstr "Vyer kan inte ha TRUNCATE-triggrar." -#: commands/trigger.c:290 commands/trigger.c:302 commands/trigger.c:437 +#: commands/trigger.c:291 commands/trigger.c:303 commands/trigger.c:438 #, c-format msgid "\"%s\" is a foreign table" msgstr "\"%s\" är en främmande tabell" -#: commands/trigger.c:292 +#: commands/trigger.c:293 #, c-format msgid "Foreign tables cannot have INSTEAD OF triggers." msgstr "Främmande tabeller kan inte ha INSTEAD OF-triggrar." -#: commands/trigger.c:304 +#: commands/trigger.c:305 #, c-format msgid "Foreign tables cannot have constraint triggers." msgstr "Främmande tabeller kan inte ha villkorstriggrar." -#: commands/trigger.c:309 commands/trigger.c:1325 commands/trigger.c:1432 +#: commands/trigger.c:310 commands/trigger.c:1326 commands/trigger.c:1433 #, c-format msgid "relation \"%s\" cannot have triggers" msgstr "relationen \"%s\" kan inte ha triggrar" -#: commands/trigger.c:380 +#: commands/trigger.c:381 #, c-format msgid "TRUNCATE FOR EACH ROW triggers are not supported" msgstr "TRUNCATE FOR EACH ROW-triggrar stöds inte" -#: commands/trigger.c:388 +#: commands/trigger.c:389 #, c-format msgid "INSTEAD OF triggers must be FOR EACH ROW" msgstr "INSTEAD OF-trigger måste vara FOR EACH ROW" -#: commands/trigger.c:392 +#: commands/trigger.c:393 #, c-format msgid "INSTEAD OF triggers cannot have WHEN conditions" msgstr "INSTEAD OF-trigger kan inte ha WHEN-villkor" -#: commands/trigger.c:396 +#: commands/trigger.c:397 #, c-format msgid "INSTEAD OF triggers cannot have column lists" msgstr "INSTEAD OF-trigger kan inte ha en kolumnlista" -#: commands/trigger.c:425 +#: commands/trigger.c:426 #, c-format msgid "ROW variable naming in the REFERENCING clause is not supported" msgstr "ROW-variabel namngiven i REFERENCING-klausul stöds inte" -#: commands/trigger.c:426 +#: commands/trigger.c:427 #, c-format msgid "Use OLD TABLE or NEW TABLE for naming transition tables." msgstr "Använd OLD TABLE eller NEW TABLE för att namnge övergångstabeller." -#: commands/trigger.c:439 +#: commands/trigger.c:440 #, c-format msgid "Triggers on foreign tables cannot have transition tables." msgstr "Triggrar på främmande tabeller kan inte ha övergångstabeller." -#: commands/trigger.c:446 +#: commands/trigger.c:447 #, c-format msgid "Triggers on views cannot have transition tables." msgstr "Triggrar på vyer kan inte ha övergångstabeller." -#: commands/trigger.c:462 +#: commands/trigger.c:463 #, c-format msgid "ROW triggers with transition tables are not supported on partitions" msgstr "ROW-triggrar med övergångstabeller stöds inte för partitioner" -#: commands/trigger.c:466 +#: commands/trigger.c:467 #, c-format msgid "ROW triggers with transition tables are not supported on inheritance children" msgstr "ROW-triggrar med övergångstabeller stöds inte på arvsbarn" -#: commands/trigger.c:472 +#: commands/trigger.c:473 #, c-format msgid "transition table name can only be specified for an AFTER trigger" msgstr "övergångstabellnamn kan bara anges för en AFTER-trigger" -#: commands/trigger.c:477 +#: commands/trigger.c:478 #, c-format msgid "TRUNCATE triggers with transition tables are not supported" msgstr "TRUNCATE-triggrar med övergångstabeller stöds inte" -#: commands/trigger.c:494 +#: commands/trigger.c:495 #, c-format msgid "transition tables cannot be specified for triggers with more than one event" msgstr "övergångstabeller kan inte anges för triggrar med mer än ett event" -#: commands/trigger.c:505 +#: commands/trigger.c:506 #, c-format msgid "transition tables cannot be specified for triggers with column lists" msgstr "övergångstabeller kan inte anges för triggrar med kolumnlistor" -#: commands/trigger.c:522 +#: commands/trigger.c:523 #, c-format msgid "NEW TABLE can only be specified for an INSERT or UPDATE trigger" msgstr "NEW TABLE kan bara anges för en INSERT- eller UPDATE-trigger" -#: commands/trigger.c:527 +#: commands/trigger.c:528 #, c-format msgid "NEW TABLE cannot be specified multiple times" msgstr "NEW TABLE kan inte anges flera gånger" -#: commands/trigger.c:537 +#: commands/trigger.c:538 #, c-format msgid "OLD TABLE can only be specified for a DELETE or UPDATE trigger" msgstr "OLD TABLE kan bara anges för en DELETE- eller UPDATE-trigger" -#: commands/trigger.c:542 +#: commands/trigger.c:543 #, c-format msgid "OLD TABLE cannot be specified multiple times" msgstr "OLD TABLE får inte anges flera gånger" -#: commands/trigger.c:552 +#: commands/trigger.c:553 #, c-format msgid "OLD TABLE name and NEW TABLE name cannot be the same" msgstr "OLD TABLE-namn och NEW TABLE-namn får inte vara samma" -#: commands/trigger.c:616 commands/trigger.c:629 +#: commands/trigger.c:617 commands/trigger.c:630 #, c-format msgid "statement trigger's WHEN condition cannot reference column values" msgstr "satstriggrars WHEN-villkor kan inte referera till kolumnvärden" -#: commands/trigger.c:621 +#: commands/trigger.c:622 #, c-format msgid "INSERT trigger's WHEN condition cannot reference OLD values" msgstr "INSERT-triggrars WHEN-villkor kan inte referera till OLD-värden" -#: commands/trigger.c:634 +#: commands/trigger.c:635 #, c-format msgid "DELETE trigger's WHEN condition cannot reference NEW values" msgstr "DELETE-triggrars WHEN-villkor kan inte referera till NEW-värden" -#: commands/trigger.c:639 +#: commands/trigger.c:640 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW system columns" msgstr "BEFORE-triggrars WHEN-villkor kan inte referera till NEW-systemkolumner" -#: commands/trigger.c:647 commands/trigger.c:655 +#: commands/trigger.c:648 commands/trigger.c:656 #, c-format msgid "BEFORE trigger's WHEN condition cannot reference NEW generated columns" msgstr "BEFORE-triggrars WHEN-villkor kan inte referera till genererade NEW-kolumner" -#: commands/trigger.c:648 +#: commands/trigger.c:649 #, c-format msgid "A whole-row reference is used and the table contains generated columns." msgstr "En hela-raden-referens används och tabellen innehåller genererade kolumner." -#: commands/trigger.c:763 commands/trigger.c:1607 +#: commands/trigger.c:764 commands/trigger.c:1608 #, c-format msgid "trigger \"%s\" for relation \"%s\" already exists" msgstr "trigger \"%s\" för relation \"%s\" existerar redan" -#: commands/trigger.c:776 +#: commands/trigger.c:777 #, c-format msgid "trigger \"%s\" for relation \"%s\" is an internal or a child trigger" msgstr "trigger \"%s\" för relation \"%s\" är en intern eller barntrigger" -#: commands/trigger.c:795 +#: commands/trigger.c:796 #, c-format msgid "trigger \"%s\" for relation \"%s\" is a constraint trigger" msgstr "trigger \"%s\" för relation \"%s\" är en villkorstrigger" -#: commands/trigger.c:1397 commands/trigger.c:1550 commands/trigger.c:1831 +#: commands/trigger.c:1398 commands/trigger.c:1551 commands/trigger.c:1832 #, c-format msgid "trigger \"%s\" for table \"%s\" does not exist" msgstr "trigger \"%s\" för tabell \"%s\" finns inte" -#: commands/trigger.c:1522 +#: commands/trigger.c:1523 #, c-format msgid "cannot rename trigger \"%s\" on table \"%s\"" msgstr "kan inte byta namn på triggern \"%s\" på tabell \"%s\"" -#: commands/trigger.c:1524 +#: commands/trigger.c:1525 #, c-format msgid "Rename the trigger on the partitioned table \"%s\" instead." msgstr "Byt istället namn på triggern i den partitionerade tabellen \"%s\"." -#: commands/trigger.c:1624 +#: commands/trigger.c:1625 #, c-format msgid "renamed trigger \"%s\" on relation \"%s\"" msgstr "bytte namn på triggern \"%s\" i relationen \"%s\"" -#: commands/trigger.c:1770 +#: commands/trigger.c:1771 #, c-format msgid "permission denied: \"%s\" is a system trigger" msgstr "rättighet saknas: \"%s\" är en systemtrigger" -#: commands/trigger.c:2379 +#: commands/trigger.c:2382 #, c-format msgid "trigger function %u returned null value" msgstr "triggerfunktionen %u returnerade null-värde" -#: commands/trigger.c:2439 commands/trigger.c:2657 commands/trigger.c:2910 -#: commands/trigger.c:3263 +#: commands/trigger.c:2442 commands/trigger.c:2669 commands/trigger.c:2959 +#: commands/trigger.c:3349 #, c-format msgid "BEFORE STATEMENT trigger cannot return a value" msgstr "BEFORE STATEMENT-trigger kan inte returnera ett värde" -#: commands/trigger.c:2515 +#: commands/trigger.c:2518 #, c-format msgid "moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported" msgstr "flytta en rad från en annan partition under en BEFORE FOR EACH ROW-trigger stöds inte" -#: commands/trigger.c:2516 +#: commands/trigger.c:2519 #, c-format msgid "Before executing trigger \"%s\", the row was to be in partition \"%s.%s\"." msgstr "Innan exekvering av triggern \"%s\" så var raden i partition \"%s.%s\"." -#: commands/trigger.c:3341 executor/nodeModifyTable.c:1541 -#: executor/nodeModifyTable.c:1615 executor/nodeModifyTable.c:2377 -#: executor/nodeModifyTable.c:2468 executor/nodeModifyTable.c:3132 -#: executor/nodeModifyTable.c:3302 +#: commands/trigger.c:2548 commands/trigger.c:2827 commands/trigger.c:3190 +#, c-format +msgid "cannot collect transition tuples from child foreign tables" +msgstr "kan inte samla in övergångstupler från främmande barntabeller" + +#: commands/trigger.c:3428 executor/nodeModifyTable.c:1563 +#: executor/nodeModifyTable.c:1637 executor/nodeModifyTable.c:2400 +#: executor/nodeModifyTable.c:2491 executor/nodeModifyTable.c:3155 +#: executor/nodeModifyTable.c:3325 #, c-format msgid "Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows." msgstr "Överväg att använda en AFTER-trigger istället för en BEFORE-trigger för att propagera ändringar till andra rader." -#: commands/trigger.c:3382 executor/nodeLockRows.c:228 -#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:314 -#: executor/nodeModifyTable.c:1557 executor/nodeModifyTable.c:2394 -#: executor/nodeModifyTable.c:2618 +#: commands/trigger.c:3470 executor/nodeLockRows.c:228 +#: executor/nodeLockRows.c:237 executor/nodeModifyTable.c:335 +#: executor/nodeModifyTable.c:1579 executor/nodeModifyTable.c:2417 +#: executor/nodeModifyTable.c:2641 #, c-format msgid "could not serialize access due to concurrent update" msgstr "kunde inte serialisera åtkomst på grund av samtidig uppdatering" -#: commands/trigger.c:3390 executor/nodeModifyTable.c:1647 -#: executor/nodeModifyTable.c:2485 executor/nodeModifyTable.c:2642 -#: executor/nodeModifyTable.c:3150 +#: commands/trigger.c:3478 executor/nodeModifyTable.c:1669 +#: executor/nodeModifyTable.c:2508 executor/nodeModifyTable.c:2665 +#: executor/nodeModifyTable.c:3173 #, c-format msgid "could not serialize access due to concurrent delete" msgstr "kunde inte serialisera åtkomst på grund av samtidig borttagning" -#: commands/trigger.c:4599 +#: commands/trigger.c:4687 #, c-format msgid "cannot fire deferred trigger within security-restricted operation" msgstr "kan inte trigga uppskjuten trigger i en säkerhetsbegränsad operation" -#: commands/trigger.c:5780 +#: commands/trigger.c:5868 #, c-format msgid "constraint \"%s\" is not deferrable" msgstr "integritetsvillkor \"%s\" är inte \"deferrable\"" -#: commands/trigger.c:5803 +#: commands/trigger.c:5891 #, c-format msgid "constraint \"%s\" does not exist" msgstr "integritetsvillkor \"%s\" existerar inte" @@ -12785,8 +12792,8 @@ msgstr "Bara roller med attributet %s får skapa roller." msgid "Only roles with the %s attribute may create roles with the %s attribute." msgstr "Bara roller med attributet %s får skapa roller med attributet %s." -#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17310 -#: gram.y:17356 utils/adt/acl.c:5574 utils/adt/acl.c:5580 +#: commands/user.c:354 commands/user.c:1386 commands/user.c:1393 gram.y:17317 +#: gram.y:17363 utils/adt/acl.c:5591 utils/adt/acl.c:5597 #, c-format msgid "role name \"%s\" is reserved" msgstr "rollnamnet \"%s\" är reserverat" @@ -12881,8 +12888,8 @@ msgstr "kan inte används speciell rollangivelse i DROP ROLE" #: commands/user.c:1135 commands/user.c:1357 commands/variable.c:851 #: commands/variable.c:854 commands/variable.c:971 commands/variable.c:974 -#: utils/adt/acl.c:365 utils/adt/acl.c:385 utils/adt/acl.c:5429 -#: utils/adt/acl.c:5477 utils/adt/acl.c:5505 utils/adt/acl.c:5524 +#: utils/adt/acl.c:382 utils/adt/acl.c:402 utils/adt/acl.c:5446 +#: utils/adt/acl.c:5494 utils/adt/acl.c:5522 utils/adt/acl.c:5541 #: utils/adt/regproc.c:1571 utils/init/miscinit.c:799 #, c-format msgid "role \"%s\" does not exist" @@ -13073,122 +13080,122 @@ msgstr "rättighet saknas att ta bort rättighet som utfärdats av roll \"%s\"" msgid "Only roles with privileges of role \"%s\" may revoke privileges granted by this role." msgstr "Bara roller med rättigheter från rollen \"%s\" får ta bort rättigheter som delats ut av den rollen." -#: commands/user.c:2491 utils/adt/acl.c:1324 +#: commands/user.c:2491 utils/adt/acl.c:1341 #, c-format msgid "dependent privileges exist" msgstr "det finns beroende privilegier" -#: commands/user.c:2492 utils/adt/acl.c:1325 +#: commands/user.c:2492 utils/adt/acl.c:1342 #, c-format msgid "Use CASCADE to revoke them too." msgstr "Använd CASCADE för att återkalla dem med." -#: commands/vacuum.c:134 +#: commands/vacuum.c:135 #, c-format msgid "\"vacuum_buffer_usage_limit\" must be 0 or between %d kB and %d kB" msgstr "\"vacuum_buffer_usage_limit\" måste vara 0 eller mellan %d kB och %d kB" -#: commands/vacuum.c:209 +#: commands/vacuum.c:210 #, c-format msgid "BUFFER_USAGE_LIMIT option must be 0 or between %d kB and %d kB" msgstr "flaggan BUFFER_USAGE_LIMIT måste vara 0 eller mellan %d kB och %d kB" -#: commands/vacuum.c:219 +#: commands/vacuum.c:220 #, c-format msgid "unrecognized ANALYZE option \"%s\"" msgstr "okänd ANALYZE-flagga \"%s\"" -#: commands/vacuum.c:259 +#: commands/vacuum.c:260 #, c-format msgid "parallel option requires a value between 0 and %d" msgstr "parallell-flaggan kräver ett värde mellan 0 och %d" -#: commands/vacuum.c:271 +#: commands/vacuum.c:272 #, c-format msgid "parallel workers for vacuum must be between 0 and %d" msgstr "parallella arbetare för vacuum måste vara mellan 0 och %d" -#: commands/vacuum.c:292 +#: commands/vacuum.c:293 #, c-format msgid "unrecognized VACUUM option \"%s\"" msgstr "okänd VACUUM-flagga \"%s\"" -#: commands/vacuum.c:318 +#: commands/vacuum.c:319 #, c-format msgid "VACUUM FULL cannot be performed in parallel" msgstr "'VACUUM FULL kan inte köras parallellt" -#: commands/vacuum.c:329 +#: commands/vacuum.c:330 #, c-format msgid "BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL" msgstr "BUFFER_USAGE_LIMIT kan inte anges för VACUUM FULL" -#: commands/vacuum.c:343 +#: commands/vacuum.c:344 #, c-format msgid "ANALYZE option must be specified when a column list is provided" msgstr "ANALYZE-flaggan måste anges när en kolumnlista används" -#: commands/vacuum.c:355 +#: commands/vacuum.c:356 #, c-format msgid "VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL" msgstr "VACUUM-flagga DISABLE_PAGE_SKIPPING kan inte anges med FULL" -#: commands/vacuum.c:362 +#: commands/vacuum.c:363 #, c-format msgid "PROCESS_TOAST required with VACUUM FULL" msgstr "PROCESS_TOAST krävs med VACUUM FULL" -#: commands/vacuum.c:371 +#: commands/vacuum.c:372 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with a list of tables" msgstr "ONLY_DATABASE_STATS kan inte anges med en lista av tabeller" -#: commands/vacuum.c:380 +#: commands/vacuum.c:381 #, c-format msgid "ONLY_DATABASE_STATS cannot be specified with other VACUUM options" msgstr "ONLY_DATABASE_STATS kan inte anges tillsammans med andra VACUUM-flaggor" -#: commands/vacuum.c:515 +#: commands/vacuum.c:516 #, c-format msgid "%s cannot be executed from VACUUM or ANALYZE" msgstr "%s kan inte köras från VACUUM eller ANALYZE" -#: commands/vacuum.c:730 +#: commands/vacuum.c:741 #, c-format msgid "permission denied to vacuum \"%s\", skipping it" msgstr "rättighet saknas för att städa \"%s\", hoppar över det" -#: commands/vacuum.c:743 +#: commands/vacuum.c:754 #, c-format msgid "permission denied to analyze \"%s\", skipping it" msgstr "rättighet saknas för att analysera \"%s\", hoppar över det" -#: commands/vacuum.c:821 commands/vacuum.c:918 +#: commands/vacuum.c:832 commands/vacuum.c:929 #, c-format msgid "skipping vacuum of \"%s\" --- lock not available" msgstr "hoppar över vacuum av \"%s\" --- lås ej tillgängligt" -#: commands/vacuum.c:826 +#: commands/vacuum.c:837 #, c-format msgid "skipping vacuum of \"%s\" --- relation no longer exists" msgstr "hoppar över vacuum av \"%s\" --- relationen finns inte längre" -#: commands/vacuum.c:842 commands/vacuum.c:923 +#: commands/vacuum.c:853 commands/vacuum.c:934 #, c-format msgid "skipping analyze of \"%s\" --- lock not available" msgstr "hoppar över analys av \"%s\" --- lås ej tillgängligt" -#: commands/vacuum.c:847 +#: commands/vacuum.c:858 #, c-format msgid "skipping analyze of \"%s\" --- relation no longer exists" msgstr "hoppar över analys av \"%s\" --- relationen finns inte längre" -#: commands/vacuum.c:1139 +#: commands/vacuum.c:1150 #, c-format msgid "cutoff for removing and freezing tuples is far in the past" msgstr "gräns för borttagning och frysande av tupler är i dåtid" -#: commands/vacuum.c:1140 commands/vacuum.c:1145 +#: commands/vacuum.c:1151 commands/vacuum.c:1156 #, c-format msgid "" "Close open transactions soon to avoid wraparound problems.\n" @@ -13197,37 +13204,37 @@ msgstr "" "Stäng öppna transaktioner för att undvika problem med wraparound.\n" "Du kan också behöva commit:a eller rulla tillbaka gamla förberedda transaktiooner alternativt slänga stillastående replikeringsslottar." -#: commands/vacuum.c:1144 +#: commands/vacuum.c:1155 #, c-format msgid "cutoff for freezing multixacts is far in the past" msgstr "gräns för frysning av multixact är från dåtid" -#: commands/vacuum.c:1900 +#: commands/vacuum.c:1911 #, c-format msgid "some databases have not been vacuumed in over 2 billion transactions" msgstr "några databaser har inte städats (vacuum) på över 2 miljarder transaktioner" -#: commands/vacuum.c:1901 +#: commands/vacuum.c:1912 #, c-format msgid "You might have already suffered transaction-wraparound data loss." msgstr "Du kan redan ha fått dataförlust på grund av transaktions-wraparound." -#: commands/vacuum.c:2080 +#: commands/vacuum.c:2098 #, c-format msgid "skipping \"%s\" --- cannot vacuum non-tables or special system tables" msgstr "hoppar över \"%s\" --- kan inte köra vacuum på icke-tabeller eller speciella systemtabeller" -#: commands/vacuum.c:2512 +#: commands/vacuum.c:2545 #, c-format msgid "scanned index \"%s\" to remove %lld row versions" msgstr "genomsökte index \"%s\" för att ta bort %lld radversioner" -#: commands/vacuum.c:2531 +#: commands/vacuum.c:2564 #, c-format msgid "index \"%s\" now contains %.0f row versions in %u pages" msgstr "index \"%s\" innehåller nu %.0f radversioner i %u sidor" -#: commands/vacuum.c:2535 +#: commands/vacuum.c:2568 #, c-format msgid "" "%.0f index row versions were removed.\n" @@ -13479,26 +13486,26 @@ msgid "no value found for parameter %d" msgstr "hittade inget värde för parameter %d" #: executor/execExpr.c:642 executor/execExpr.c:649 executor/execExpr.c:655 -#: executor/execExprInterp.c:4852 executor/execExprInterp.c:4869 -#: executor/execExprInterp.c:4968 executor/nodeModifyTable.c:203 -#: executor/nodeModifyTable.c:214 executor/nodeModifyTable.c:231 -#: executor/nodeModifyTable.c:239 +#: executor/execExprInterp.c:4851 executor/execExprInterp.c:4868 +#: executor/execExprInterp.c:4967 executor/nodeModifyTable.c:204 +#: executor/nodeModifyTable.c:223 executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:250 executor/nodeModifyTable.c:260 #, c-format msgid "table row type and query-specified row type do not match" msgstr "tabellens radtyp och frågans radtyp matchar inte" -#: executor/execExpr.c:643 executor/nodeModifyTable.c:204 +#: executor/execExpr.c:643 executor/nodeModifyTable.c:205 #, c-format msgid "Query has too many columns." msgstr "Fråga har för många kolumner." -#: executor/execExpr.c:650 executor/nodeModifyTable.c:232 +#: executor/execExpr.c:650 executor/nodeModifyTable.c:224 #, c-format msgid "Query provides a value for a dropped column at ordinal position %d." msgstr "Fråga levererar ett värde för en borttagen kolumn vid position %d." -#: executor/execExpr.c:656 executor/execExprInterp.c:4870 -#: executor/nodeModifyTable.c:215 +#: executor/execExpr.c:656 executor/execExprInterp.c:4869 +#: executor/nodeModifyTable.c:251 #, c-format msgid "Table has type %s at ordinal position %d, but query expects %s." msgstr "Tabellen har typ %s vid position %d, men frågan förväntar sig %s." @@ -13583,7 +13590,7 @@ msgstr "Array med elementtyp %s kan inte inkluderas i ARRAY-konstruktion med ele #: executor/execExprInterp.c:2945 utils/adt/arrayfuncs.c:1305 #: utils/adt/arrayfuncs.c:3503 utils/adt/arrayfuncs.c:5593 -#: utils/adt/arrayfuncs.c:6110 utils/adt/arraysubs.c:150 +#: utils/adt/arrayfuncs.c:6112 utils/adt/arraysubs.c:150 #: utils/adt/arraysubs.c:488 #, c-format msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)" @@ -13602,7 +13609,7 @@ msgstr "flerdimensionella vektorer måste ha array-uttryck av passande dimension #: utils/adt/arrayfuncs.c:2895 utils/adt/arrayfuncs.c:2949 #: utils/adt/arrayfuncs.c:2964 utils/adt/arrayfuncs.c:3305 #: utils/adt/arrayfuncs.c:3533 utils/adt/arrayfuncs.c:5365 -#: utils/adt/arrayfuncs.c:6202 utils/adt/arrayfuncs.c:6546 +#: utils/adt/arrayfuncs.c:6204 utils/adt/arrayfuncs.c:6548 #: utils/adt/arrayutils.c:83 utils/adt/arrayutils.c:92 #: utils/adt/arrayutils.c:99 #, c-format @@ -13640,14 +13647,14 @@ msgstr "ingen SQL/JSON-post hittades i angiven sökväg" msgid "could not coerce %s expression (%s) to the RETURNING type" msgstr "kunde inte omvandla %s-uttryck (%s) till typen för RETURNING" -#: executor/execExprInterp.c:4853 +#: executor/execExprInterp.c:4852 #, c-format msgid "Table row contains %d attribute, but query expects %d." msgid_plural "Table row contains %d attributes, but query expects %d." msgstr[0] "Tabellrad har %d attribut, men frågan förväntar sig %d." msgstr[1] "Tabellrad har %d attribut, men frågan förväntar sig %d." -#: executor/execExprInterp.c:4969 executor/execSRF.c:977 +#: executor/execExprInterp.c:4968 executor/execSRF.c:977 #, c-format msgid "Physical storage mismatch on dropped attribute at ordinal position %d." msgstr "Fysisk lagrings matchar inte för borttaget attribut på position %d." @@ -13757,7 +13764,7 @@ msgstr "kan inte låsa rader i vy \"%s\"" msgid "cannot lock rows in materialized view \"%s\"" msgstr "kan inte låsa rader i materialiserad vy \"%s\"" -#: executor/execMain.c:1177 executor/execMain.c:2687 +#: executor/execMain.c:1177 executor/execMain.c:2689 #: executor/nodeLockRows.c:135 #, c-format msgid "cannot lock rows in foreign table \"%s\"" @@ -13849,10 +13856,10 @@ msgstr "samtidig uppdatering, försöker igen" msgid "concurrent delete, retrying" msgstr "samtidig borttagning, försöker igen" -#: executor/execReplication.c:352 parser/parse_cte.c:302 +#: executor/execReplication.c:352 parser/parse_cte.c:303 #: parser/parse_oper.c:221 utils/adt/array_userfuncs.c:1334 #: utils/adt/array_userfuncs.c:1477 utils/adt/arrayfuncs.c:3852 -#: utils/adt/arrayfuncs.c:4407 utils/adt/arrayfuncs.c:6426 +#: utils/adt/arrayfuncs.c:4407 utils/adt/arrayfuncs.c:6428 #: utils/adt/rowtypes.c:1220 #, c-format msgid "could not identify an equality operator for type %s" @@ -14095,64 +14102,69 @@ msgstr "RIGHT JOIN stöds bara med merge-joinbara join-villor" msgid "FULL JOIN is only supported with merge-joinable join conditions" msgstr "FULL JOIN stöds bara med merge-joinbara join-villkor" -#: executor/nodeModifyTable.c:240 +#: executor/nodeModifyTable.c:241 +#, c-format +msgid "Query provides a value for a generated column at ordinal position %d." +msgstr "Fråga levererar ett värde för en genererad kolumn vid position %d." + +#: executor/nodeModifyTable.c:261 #, c-format msgid "Query has too few columns." msgstr "Frågan har för få kolumner" -#: executor/nodeModifyTable.c:1540 executor/nodeModifyTable.c:1614 +#: executor/nodeModifyTable.c:1562 executor/nodeModifyTable.c:1636 #, c-format msgid "tuple to be deleted was already modified by an operation triggered by the current command" msgstr "tupel som skall tas bort hade redan ändrats av en operation som triggats av aktuellt kommando" -#: executor/nodeModifyTable.c:1769 +#: executor/nodeModifyTable.c:1791 #, c-format msgid "invalid ON UPDATE specification" msgstr "ogiltig ON UPDATE-angivelse" -#: executor/nodeModifyTable.c:1770 +#: executor/nodeModifyTable.c:1792 #, c-format msgid "The result tuple would appear in a different partition than the original tuple." msgstr "Resultattupeln kommer dyka upp i en annan partition än originaltupeln." -#: executor/nodeModifyTable.c:2226 +#: executor/nodeModifyTable.c:2249 #, c-format msgid "cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key" msgstr "kan inte flytta en tupel mellan partitioner när en icke-root-förälder av källpartitionen direkt refereras av en främmande nyckel" -#: executor/nodeModifyTable.c:2227 +#: executor/nodeModifyTable.c:2250 #, c-format msgid "A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\"." msgstr "En främmande nyckel pekar på förfadern \"%s\" men inte på root-förfadern \"%s\"." -#: executor/nodeModifyTable.c:2230 +#: executor/nodeModifyTable.c:2253 #, c-format msgid "Consider defining the foreign key on table \"%s\"." msgstr "Överväg att skapa den främmande nyckeln på tabellen \"%s\"." #. translator: %s is a SQL command name -#: executor/nodeModifyTable.c:2596 executor/nodeModifyTable.c:3138 -#: executor/nodeModifyTable.c:3308 +#: executor/nodeModifyTable.c:2619 executor/nodeModifyTable.c:3161 +#: executor/nodeModifyTable.c:3331 #, c-format msgid "%s command cannot affect row a second time" msgstr "%s-kommandot kan inte påverka raden en andra gång" -#: executor/nodeModifyTable.c:2598 +#: executor/nodeModifyTable.c:2621 #, c-format msgid "Ensure that no rows proposed for insertion within the same command have duplicate constrained values." msgstr "Säkerställ att inga rader föreslagna för \"insert\" inom samma kommando har upprepade villkorsvärden." -#: executor/nodeModifyTable.c:3131 executor/nodeModifyTable.c:3301 +#: executor/nodeModifyTable.c:3154 executor/nodeModifyTable.c:3324 #, c-format msgid "tuple to be updated or deleted was already modified by an operation triggered by the current command" msgstr "tupel som skall uppdateras eller raderas hade redan ändrats av en operation som triggats av aktuellt kommando" -#: executor/nodeModifyTable.c:3140 executor/nodeModifyTable.c:3310 +#: executor/nodeModifyTable.c:3163 executor/nodeModifyTable.c:3333 #, c-format msgid "Ensure that not more than one source row matches any one target row." msgstr "Säkerställ att inte mer än en källrad matchar någon målrad." -#: executor/nodeModifyTable.c:3209 +#: executor/nodeModifyTable.c:3232 #, c-format msgid "tuple to be merged was already moved to another partition due to concurrent update" msgstr "tupel som skall slås samman har redan flyttats till en annan partition på grund av samtidig update" @@ -14315,7 +14327,7 @@ msgstr "kunde inte skicka tupel till kö i delat minne: %m" msgid "user mapping not found for user \"%s\", server \"%s\"" msgstr "användarmappning hittades inte för användare \"%s\", server \"%s\"" -#: foreign/foreign.c:336 optimizer/plan/createplan.c:7153 +#: foreign/foreign.c:336 optimizer/plan/createplan.c:7155 #: optimizer/util/plancat.c:540 #, c-format msgid "access to non-system foreign table is restricted" @@ -14530,200 +14542,205 @@ msgstr "motstridiga eller överflödiga NULL / NOT NULL-deklarationer för kolum msgid "unrecognized column option \"%s\"" msgstr "okänd kolumnflagga \"%s\"" -#: gram.y:14147 +#: gram.y:14098 +#, c-format +msgid "option name \"%s\" cannot be used in XMLTABLE" +msgstr "flaggnamn \"%s\" kan inte användas i XMLTABLE" + +#: gram.y:14154 #, c-format msgid "only string constants are supported in JSON_TABLE path specification" msgstr "enbart strängkonstanter stöds i angiven sökväg för JSON_TABLE" -#: gram.y:14469 +#: gram.y:14476 #, c-format msgid "precision for type float must be at least 1 bit" msgstr "precisionen för typen float måste vara minst 1 bit" -#: gram.y:14478 +#: gram.y:14485 #, c-format msgid "precision for type float must be less than 54 bits" msgstr "precisionen för typen float måste vara mindre än 54 bits" -#: gram.y:14995 +#: gram.y:15002 #, c-format msgid "wrong number of parameters on left side of OVERLAPS expression" msgstr "fel antal parametrar på vänster sida om OVERLAPS-uttryck" -#: gram.y:15000 +#: gram.y:15007 #, c-format msgid "wrong number of parameters on right side of OVERLAPS expression" msgstr "fel antal parametrar på höger sida om OVERLAPS-uttryck" -#: gram.y:15177 +#: gram.y:15184 #, c-format msgid "UNIQUE predicate is not yet implemented" msgstr "UNIQUE-predikat är inte implementerat ännu" -#: gram.y:15591 +#: gram.y:15598 #, c-format msgid "cannot use multiple ORDER BY clauses with WITHIN GROUP" msgstr "kan inte ha multipla ORDER BY-klausuler med WITHIN GROUP" -#: gram.y:15596 +#: gram.y:15603 #, c-format msgid "cannot use DISTINCT with WITHIN GROUP" msgstr "kan inte använda DISTINCT med WITHIN GROUP" -#: gram.y:15601 +#: gram.y:15608 #, c-format msgid "cannot use VARIADIC with WITHIN GROUP" msgstr "kan inte använda VARIADIC med WITHIN GROUP" -#: gram.y:16328 gram.y:16352 +#: gram.y:16335 gram.y:16359 #, c-format msgid "frame start cannot be UNBOUNDED FOLLOWING" msgstr "fönsterramstart kan inte vara UNBOUNDED FOLLOWING" -#: gram.y:16333 +#: gram.y:16340 #, c-format msgid "frame starting from following row cannot end with current row" msgstr "fönsterram som startar på efterföljande rad kan inte sluta på nuvarande rad" -#: gram.y:16357 +#: gram.y:16364 #, c-format msgid "frame end cannot be UNBOUNDED PRECEDING" msgstr "fönsterramslut kan inte vara UNBOUNDED PRECEDING" -#: gram.y:16363 +#: gram.y:16370 #, c-format msgid "frame starting from current row cannot have preceding rows" msgstr "fönsterram som startar på aktuell rad kan inte ha föregående rader" -#: gram.y:16370 +#: gram.y:16377 #, c-format msgid "frame starting from following row cannot have preceding rows" msgstr "fönsterram som startar på efterföljande rad kan inte ha föregående rader" -#: gram.y:16919 +#: gram.y:16926 #, c-format msgid "unrecognized JSON encoding: %s" msgstr "okänd JSON-kodning: %s" -#: gram.y:17243 +#: gram.y:17250 #, c-format msgid "type modifier cannot have parameter name" msgstr "typmodifierare kan inte ha paremeternamn" -#: gram.y:17249 +#: gram.y:17256 #, c-format msgid "type modifier cannot have ORDER BY" msgstr "typmodifierare kan inte ha ORDER BY" -#: gram.y:17317 gram.y:17324 gram.y:17331 +#: gram.y:17324 gram.y:17331 gram.y:17338 #, c-format msgid "%s cannot be used as a role name here" msgstr "%s kan inte användas som ett rollnamn här" -#: gram.y:17421 gram.y:18906 +#: gram.y:17428 gram.y:18913 #, c-format msgid "WITH TIES cannot be specified without ORDER BY clause" msgstr "WITH TIES kan inte anges utan en ORDER BY-klausul" -#: gram.y:18597 gram.y:18772 +#: gram.y:18604 gram.y:18779 msgid "improper use of \"*\"" msgstr "felaktig användning av \"*\"" -#: gram.y:18735 gram.y:18752 tsearch/spell.c:963 tsearch/spell.c:980 -#: tsearch/spell.c:997 tsearch/spell.c:1014 tsearch/spell.c:1079 +#: gram.y:18742 gram.y:18759 tsearch/spell.c:964 tsearch/spell.c:981 +#: tsearch/spell.c:998 tsearch/spell.c:1015 tsearch/spell.c:1081 #, c-format msgid "syntax error" msgstr "syntaxfel" -#: gram.y:18836 +#: gram.y:18843 #, c-format msgid "an ordered-set aggregate with a VARIADIC direct argument must have one VARIADIC aggregated argument of the same data type" msgstr "ett sorterad-mängd-aggregat med ett direkt VARIADIC-argument måste ha ett aggregerat VARIADIC-argument av samma datatype" -#: gram.y:18873 +#: gram.y:18880 #, c-format msgid "multiple ORDER BY clauses not allowed" msgstr "multipla ORDER BY-klausuler tillåts inte" -#: gram.y:18884 +#: gram.y:18891 #, c-format msgid "multiple OFFSET clauses not allowed" msgstr "multipla OFFSET-klausuler tillåts inte" -#: gram.y:18893 +#: gram.y:18900 #, c-format msgid "multiple LIMIT clauses not allowed" msgstr "multipla LIMIT-klausuler tillåts inte" -#: gram.y:18902 +#: gram.y:18909 #, c-format msgid "multiple limit options not allowed" msgstr "multipla limit-alternativ tillåts inte" -#: gram.y:18929 +#: gram.y:18936 #, c-format msgid "multiple WITH clauses not allowed" msgstr "multipla WITH-klausuler tillåts inte" -#: gram.y:19122 +#: gram.y:19129 #, c-format msgid "OUT and INOUT arguments aren't allowed in TABLE functions" msgstr "OUT och INOUT-argument tillåts inte i TABLE-funktioner" -#: gram.y:19255 +#: gram.y:19262 #, c-format msgid "multiple COLLATE clauses not allowed" msgstr "multipla COLLATE-klausuler tillåts inte" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19293 gram.y:19306 +#: gram.y:19300 gram.y:19313 #, c-format msgid "%s constraints cannot be marked DEFERRABLE" msgstr "%s-villkor kan inte markeras DEFERRABLE" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19319 +#: gram.y:19326 #, c-format msgid "%s constraints cannot be marked NOT VALID" msgstr "%s-villkor kan inte markeras NOT VALID" #. translator: %s is CHECK, UNIQUE, or similar -#: gram.y:19332 +#: gram.y:19339 #, c-format msgid "%s constraints cannot be marked NO INHERIT" msgstr "%s-villkor kan inte markeras NO INHERIT" -#: gram.y:19354 +#: gram.y:19361 #, c-format msgid "unrecognized partitioning strategy \"%s\"" msgstr "okänd partitioneringsstrategi \"%s\"" -#: gram.y:19378 +#: gram.y:19385 #, c-format msgid "invalid publication object list" msgstr "ogiltig objektlista för publicering" -#: gram.y:19379 +#: gram.y:19386 #, c-format msgid "One of TABLE or TABLES IN SCHEMA must be specified before a standalone table or schema name." msgstr "En av TABLE eller ALL TABLES IN SCHEMA måste anges innan en enskild tabell eller ett schemanamn." -#: gram.y:19395 +#: gram.y:19402 #, c-format msgid "invalid table name" msgstr "ogiltigt tabellnamn" -#: gram.y:19416 +#: gram.y:19423 #, c-format msgid "WHERE clause not allowed for schema" msgstr "WHERE-klausul tillåts inte för schema" -#: gram.y:19423 +#: gram.y:19430 #, c-format msgid "column specification not allowed for schema" msgstr "kolumnspecifikation tillåts inte för schema" -#: gram.y:19437 +#: gram.y:19444 #, c-format msgid "invalid schema name" msgstr "ogiltigt schemanamn" @@ -14786,7 +14803,7 @@ msgstr "A list of the problematic subscriptions is in the file:" msgid "Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate." msgstr "okänt flaggtecken \"%.*s\" i LIKE_REGEX-predikat." -#: jsonpath_gram.y:629 tsearch/spell.c:749 utils/adt/regexp.c:223 +#: jsonpath_gram.y:629 tsearch/spell.c:750 utils/adt/regexp.c:223 #, c-format msgid "invalid regular expression: %s" msgstr "ogiltigt reguljärt uttryck: %s" @@ -15021,545 +15038,545 @@ msgstr "Trasigt bevis i klient-slut-meddelande." msgid "Garbage found at the end of client-final-message." msgstr "Hittade skräp i slutet av klient-slut-meddelande." -#: libpq/auth.c:269 +#: libpq/auth.c:277 #, c-format msgid "authentication failed for user \"%s\": host rejected" msgstr "autentisering misslyckades för användare \"%s\": host blockerad" -#: libpq/auth.c:272 +#: libpq/auth.c:280 #, c-format msgid "\"trust\" authentication failed for user \"%s\"" msgstr "\"trust\"-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:275 +#: libpq/auth.c:283 #, c-format msgid "Ident authentication failed for user \"%s\"" msgstr "Ident-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:278 +#: libpq/auth.c:286 #, c-format msgid "Peer authentication failed for user \"%s\"" msgstr "Peer-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:283 +#: libpq/auth.c:291 #, c-format msgid "password authentication failed for user \"%s\"" msgstr "Lösenordsautentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:288 +#: libpq/auth.c:296 #, c-format msgid "GSSAPI authentication failed for user \"%s\"" msgstr "GSSAPI-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:291 +#: libpq/auth.c:299 #, c-format msgid "SSPI authentication failed for user \"%s\"" msgstr "SSPI-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:294 +#: libpq/auth.c:302 #, c-format msgid "PAM authentication failed for user \"%s\"" msgstr "PAM-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:297 +#: libpq/auth.c:305 #, c-format msgid "BSD authentication failed for user \"%s\"" msgstr "BSD-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:300 +#: libpq/auth.c:308 #, c-format msgid "LDAP authentication failed for user \"%s\"" msgstr "LDAP-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:303 +#: libpq/auth.c:311 #, c-format msgid "certificate authentication failed for user \"%s\"" msgstr "certifikat-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:306 +#: libpq/auth.c:314 #, c-format msgid "RADIUS authentication failed for user \"%s\"" msgstr "RADOUS-autentisering misslyckades för användare \"%s\"" -#: libpq/auth.c:309 +#: libpq/auth.c:317 #, c-format msgid "authentication failed for user \"%s\": invalid authentication method" msgstr "autentisering misslyckades för användare \"%s\": okänd autentiseringsmetod" -#: libpq/auth.c:313 +#: libpq/auth.c:321 #, c-format msgid "Connection matched file \"%s\" line %d: \"%s\"" msgstr "Anslutning matchade filen \"%s\", rad %d: \"%s\"" -#: libpq/auth.c:357 +#: libpq/auth.c:365 #, c-format msgid "authentication identifier set more than once" msgstr "identifierare för autentisering satt mer än en gång" -#: libpq/auth.c:358 +#: libpq/auth.c:366 #, c-format msgid "previous identifier: \"%s\"; new identifier: \"%s\"" msgstr "föregående identifierare: \"%s\"; ny identifierare: \"%s\"" -#: libpq/auth.c:368 +#: libpq/auth.c:376 #, c-format msgid "connection authenticated: identity=\"%s\" method=%s (%s:%d)" msgstr "anslutning autentiserad: identitet=\"%s\" metod=%s (%s:%d)" -#: libpq/auth.c:408 +#: libpq/auth.c:416 #, c-format msgid "client certificates can only be checked if a root certificate store is available" msgstr "klientcertifikat kan bara kontrolleras om lagrade root-certifikat finns tillgängligt" -#: libpq/auth.c:419 +#: libpq/auth.c:427 #, c-format msgid "connection requires a valid client certificate" msgstr "Anslutning kräver ett giltigt klientcertifikat" -#: libpq/auth.c:450 libpq/auth.c:496 +#: libpq/auth.c:458 libpq/auth.c:504 msgid "GSS encryption" msgstr "GSS-kryptering" -#: libpq/auth.c:453 libpq/auth.c:499 +#: libpq/auth.c:461 libpq/auth.c:507 msgid "SSL encryption" msgstr "SSL-kryptering" -#: libpq/auth.c:455 libpq/auth.c:501 +#: libpq/auth.c:463 libpq/auth.c:509 msgid "no encryption" msgstr "ingen kryptering" #. translator: last %s describes encryption state -#: libpq/auth.c:461 +#: libpq/auth.c:469 #, c-format msgid "pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s" msgstr "pg_hba.conf avvisar replikeringsanslutning för värd \"%s\", användare \"%s\", %s" #. translator: last %s describes encryption state -#: libpq/auth.c:468 +#: libpq/auth.c:476 #, c-format msgid "pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s" msgstr "pg_hba.conf avvisar anslutning för värd \"%s\", användare \"%s\", databas \"%s\", %s" -#: libpq/auth.c:506 +#: libpq/auth.c:514 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup matches." msgstr "Klient-IP-adress uppslagen till \"%s\", skickat uppslag matchar." -#: libpq/auth.c:509 +#: libpq/auth.c:517 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup not checked." msgstr "Klient-IP-adress uppslagen till \"%s\", skickat uppslag är inte kontrollerat." -#: libpq/auth.c:512 +#: libpq/auth.c:520 #, c-format msgid "Client IP address resolved to \"%s\", forward lookup does not match." msgstr "Klient-IP-adress uppslagen till \"%s\", skickat uppslag matchar inte." -#: libpq/auth.c:515 +#: libpq/auth.c:523 #, c-format msgid "Could not translate client host name \"%s\" to IP address: %s." msgstr "Kunde inte översätta klientvärdnamn \"%s\" till IP-adress: %s" -#: libpq/auth.c:520 +#: libpq/auth.c:528 #, c-format msgid "Could not resolve client IP address to a host name: %s." msgstr "Kunde inte slå upp klient-IP-adress och få värdnamn: %s." #. translator: last %s describes encryption state -#: libpq/auth.c:528 +#: libpq/auth.c:536 #, c-format msgid "no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s" msgstr "ingen rad i pg_hba.conf för replikeringsanslutning från värd \"%s\", användare \"%s\", %s" #. translator: last %s describes encryption state -#: libpq/auth.c:536 +#: libpq/auth.c:544 #, c-format msgid "no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s" msgstr "ingen rad i pg_hba.conf för värd \"%s\", användare \"%s\", databas \"%s\", %s" -#: libpq/auth.c:656 +#: libpq/auth.c:664 #, c-format msgid "connection authenticated: user=\"%s\" method=%s (%s:%d)" msgstr "anslutning autentiserad: användare=\"%s\" metod=%s (%s:%d)" -#: libpq/auth.c:725 +#: libpq/auth.c:733 #, c-format msgid "expected password response, got message type %d" msgstr "förväntade lösenordssvar, fick meddelandetyp %d" -#: libpq/auth.c:746 +#: libpq/auth.c:754 #, c-format msgid "invalid password packet size" msgstr "felaktig storlek på lösenordspaket" -#: libpq/auth.c:764 +#: libpq/auth.c:772 #, c-format msgid "empty password returned by client" msgstr "tomt lösenord returnerat av klient" -#: libpq/auth.c:892 +#: libpq/auth.c:900 #, c-format msgid "could not generate random MD5 salt" msgstr "kunde inte generera slumpmässigt MD5-salt" -#: libpq/auth.c:943 libpq/be-secure-gssapi.c:540 +#: libpq/auth.c:951 libpq/be-secure-gssapi.c:550 #, c-format msgid "could not set environment: %m" msgstr "kunde inte sätta omgivningsvariabel: %m" -#: libpq/auth.c:982 +#: libpq/auth.c:990 #, c-format msgid "expected GSS response, got message type %d" msgstr "förväntade GSS-svar, fick meddelandetyp %d" -#: libpq/auth.c:1048 +#: libpq/auth.c:1056 msgid "accepting GSS security context failed" msgstr "accepterande av GSS-säkerhetskontext misslyckades" -#: libpq/auth.c:1089 +#: libpq/auth.c:1097 msgid "retrieving GSS user name failed" msgstr "mottagande av GSS-användarnamn misslyckades" -#: libpq/auth.c:1235 +#: libpq/auth.c:1243 msgid "could not acquire SSPI credentials" msgstr "kunde inte hämta SSPI-referenser" -#: libpq/auth.c:1260 +#: libpq/auth.c:1268 #, c-format msgid "expected SSPI response, got message type %d" msgstr "förväntade SSPI-svar, fick meddelandetyp %d" -#: libpq/auth.c:1338 +#: libpq/auth.c:1346 msgid "could not accept SSPI security context" msgstr "kunde inte acceptera SSPI-säkerhetskontext" -#: libpq/auth.c:1379 +#: libpq/auth.c:1387 msgid "could not get token from SSPI security context" msgstr "kunde inte hämta token från SSPI-säkerhetskontext" -#: libpq/auth.c:1515 libpq/auth.c:1534 +#: libpq/auth.c:1523 libpq/auth.c:1542 #, c-format msgid "could not translate name" msgstr "kunde inte översätta namn" -#: libpq/auth.c:1547 +#: libpq/auth.c:1555 #, c-format msgid "realm name too long" msgstr "realm-namn för långt" -#: libpq/auth.c:1562 +#: libpq/auth.c:1570 #, c-format msgid "translated account name too long" msgstr "översatt kontonamn för långt" -#: libpq/auth.c:1741 +#: libpq/auth.c:1749 #, c-format msgid "could not create socket for Ident connection: %m" msgstr "kunde inte skapa uttag (socket) för Ident-anslutning: %m" -#: libpq/auth.c:1756 +#: libpq/auth.c:1764 #, c-format msgid "could not bind to local address \"%s\": %m" msgstr "kunde inte binda till lokal adress \"%s\": %m" -#: libpq/auth.c:1768 +#: libpq/auth.c:1776 #, c-format msgid "could not connect to Ident server at address \"%s\", port %s: %m" msgstr "kunde inte ansluta till Ident-server på adress \"%s\", port %s: %m" -#: libpq/auth.c:1790 +#: libpq/auth.c:1798 #, c-format msgid "could not send query to Ident server at address \"%s\", port %s: %m" msgstr "kunde inte skicka fråga till Ident-server på adress \"%s\", port %s: %m" -#: libpq/auth.c:1807 +#: libpq/auth.c:1815 #, c-format msgid "could not receive response from Ident server at address \"%s\", port %s: %m" msgstr "kunde inte ta emot svar från Ident-server på adress \"%s\", port %s: %m" -#: libpq/auth.c:1817 +#: libpq/auth.c:1825 #, c-format msgid "invalidly formatted response from Ident server: \"%s\"" msgstr "ogiltigt formatterat svar från Ident-server: \"%s\"" -#: libpq/auth.c:1870 +#: libpq/auth.c:1878 #, c-format msgid "peer authentication is not supported on this platform" msgstr "peer-autentisering stöds inte på denna plattform" -#: libpq/auth.c:1874 +#: libpq/auth.c:1882 #, c-format msgid "could not get peer credentials: %m" msgstr "kunde inte hämta peer-referenser: %m" -#: libpq/auth.c:1886 +#: libpq/auth.c:1894 #, c-format msgid "could not look up local user ID %ld: %s" msgstr "kunde inte slå upp lokalt användar-id %ld: %s" -#: libpq/auth.c:1988 +#: libpq/auth.c:1996 #, c-format msgid "error from underlying PAM layer: %s" msgstr "fel från underliggande PAM-lager: %s" -#: libpq/auth.c:1999 +#: libpq/auth.c:2007 #, c-format msgid "unsupported PAM conversation %d/\"%s\"" msgstr "ej stödd PAM-konversation: %d/\"%s\"" -#: libpq/auth.c:2056 +#: libpq/auth.c:2064 #, c-format msgid "could not create PAM authenticator: %s" msgstr "kunde inte skapa PAM-autentiserare: %s" -#: libpq/auth.c:2067 +#: libpq/auth.c:2075 #, c-format msgid "pam_set_item(PAM_USER) failed: %s" msgstr "pam_set_item(PAM_USER) misslyckades: %s" -#: libpq/auth.c:2099 +#: libpq/auth.c:2107 #, c-format msgid "pam_set_item(PAM_RHOST) failed: %s" msgstr "pam_set_item(PAM_RHOST) misslyckades: %s" -#: libpq/auth.c:2111 +#: libpq/auth.c:2119 #, c-format msgid "pam_set_item(PAM_CONV) failed: %s" msgstr "pam_set_item(PAM_CONV) misslyckades: %s" -#: libpq/auth.c:2124 +#: libpq/auth.c:2132 #, c-format msgid "pam_authenticate failed: %s" msgstr "pam_authenticate misslyckades: %s" -#: libpq/auth.c:2137 +#: libpq/auth.c:2145 #, c-format msgid "pam_acct_mgmt failed: %s" msgstr "pam_acct_mgmt misslyckades: %s" -#: libpq/auth.c:2148 +#: libpq/auth.c:2156 #, c-format msgid "could not release PAM authenticator: %s" msgstr "kunde inte fria PAM-autentiserare: %s" -#: libpq/auth.c:2228 +#: libpq/auth.c:2236 #, c-format msgid "could not initialize LDAP: error code %d" msgstr "kunde inte initiera LDAP: felkod %d" -#: libpq/auth.c:2265 +#: libpq/auth.c:2273 #, c-format msgid "could not extract domain name from ldapbasedn" msgstr "kunde inte extrahera domännamn från ldapbasedn" -#: libpq/auth.c:2273 +#: libpq/auth.c:2281 #, c-format msgid "LDAP authentication could not find DNS SRV records for \"%s\"" msgstr "LDAP-autentisering kunde inte hitta DNS SRV-poster för \"%s\"" -#: libpq/auth.c:2275 +#: libpq/auth.c:2283 #, c-format msgid "Set an LDAP server name explicitly." msgstr "Ange LDAP-servernamnet explicit." -#: libpq/auth.c:2327 +#: libpq/auth.c:2335 #, c-format msgid "could not initialize LDAP: %s" msgstr "kunde inte initiera LDAP: %s" -#: libpq/auth.c:2337 +#: libpq/auth.c:2345 #, c-format msgid "ldaps not supported with this LDAP library" msgstr "ldaps stöds inte med detta LDAP-bibliotek" -#: libpq/auth.c:2345 +#: libpq/auth.c:2353 #, c-format msgid "could not initialize LDAP: %m" msgstr "kunde inte initiera LDAP: %m" -#: libpq/auth.c:2355 +#: libpq/auth.c:2363 #, c-format msgid "could not set LDAP protocol version: %s" msgstr "kunde inte sätta LDAP-protokollversion: %s" -#: libpq/auth.c:2371 +#: libpq/auth.c:2379 #, c-format msgid "could not start LDAP TLS session: %s" msgstr "kunde inte starta LDAP TLS-session: %s" -#: libpq/auth.c:2448 +#: libpq/auth.c:2456 #, c-format msgid "LDAP server not specified, and no ldapbasedn" msgstr "LDAP-server inte angiven och ingen ldapbasedn" -#: libpq/auth.c:2455 +#: libpq/auth.c:2463 #, c-format msgid "LDAP server not specified" msgstr "LDAP-server inte angiven" -#: libpq/auth.c:2517 +#: libpq/auth.c:2525 #, c-format msgid "invalid character in user name for LDAP authentication" msgstr "ogiltigt tecken i användarnamn för LDAP-autentisering" -#: libpq/auth.c:2534 +#: libpq/auth.c:2542 #, c-format msgid "could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s" msgstr "kunde inte utföra initial LDAP-bindning med ldapbinddn \"%s\" på server \"%s\": %s" -#: libpq/auth.c:2564 +#: libpq/auth.c:2572 #, c-format msgid "could not search LDAP for filter \"%s\" on server \"%s\": %s" msgstr "kunde inte söka i LDAP med filter \"%s\" på server \"%s\": %s" -#: libpq/auth.c:2580 +#: libpq/auth.c:2588 #, c-format msgid "LDAP user \"%s\" does not exist" msgstr "LDAP-användare \"%s\" finns inte" -#: libpq/auth.c:2581 +#: libpq/auth.c:2589 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned no entries." msgstr "LDAP-sökning med filter \"%s\" på server \"%s\" returnerade inga poster." -#: libpq/auth.c:2585 +#: libpq/auth.c:2593 #, c-format msgid "LDAP user \"%s\" is not unique" msgstr "LDAP-användare \"%s\" är inte unik" -#: libpq/auth.c:2586 +#: libpq/auth.c:2594 #, c-format msgid "LDAP search for filter \"%s\" on server \"%s\" returned %d entry." msgid_plural "LDAP search for filter \"%s\" on server \"%s\" returned %d entries." msgstr[0] "LDAP-sökning med filter \"%s\" på server \"%s\" returnerade %d post." msgstr[1] "LDAP-sökning med filter \"%s\" på server \"%s\" returnerade %d poster." -#: libpq/auth.c:2606 +#: libpq/auth.c:2614 #, c-format msgid "could not get dn for the first entry matching \"%s\" on server \"%s\": %s" msgstr "kunde inte hämta dn för första posten som matchar \"%s\" på värd \"%s\": %s" -#: libpq/auth.c:2633 +#: libpq/auth.c:2641 #, c-format msgid "LDAP login failed for user \"%s\" on server \"%s\": %s" msgstr "LDAP-inloggning misslyckades för användare \"%s\" på värd \"%s\": %s" -#: libpq/auth.c:2665 +#: libpq/auth.c:2673 #, c-format msgid "LDAP diagnostics: %s" msgstr "LDAP-diagnostik: %s" -#: libpq/auth.c:2703 +#: libpq/auth.c:2711 #, c-format msgid "certificate authentication failed for user \"%s\": client certificate contains no user name" msgstr "certifikatautentisering misslyckades för användare \"%s\": klientcertifikatet innehåller inget användarnamn" -#: libpq/auth.c:2724 +#: libpq/auth.c:2732 #, c-format msgid "certificate authentication failed for user \"%s\": unable to retrieve subject DN" msgstr "certifikat-autentisering misslyckades för användare \"%s\": kan inte hämta subject DN" -#: libpq/auth.c:2747 +#: libpq/auth.c:2755 #, c-format msgid "certificate validation (clientcert=verify-full) failed for user \"%s\": DN mismatch" msgstr "certifikat-validering (clientcert=verify-full) misslyckades för användare \"%s\": DN matchade inte" -#: libpq/auth.c:2752 +#: libpq/auth.c:2760 #, c-format msgid "certificate validation (clientcert=verify-full) failed for user \"%s\": CN mismatch" msgstr "certifikat-validering (clientcert=verify-full) misslyckades för användare \"%s\": CN matchade inte" -#: libpq/auth.c:2854 +#: libpq/auth.c:2862 #, c-format msgid "RADIUS server not specified" msgstr "RADIUS-server inte angiven" -#: libpq/auth.c:2861 +#: libpq/auth.c:2869 #, c-format msgid "RADIUS secret not specified" msgstr "RADIUS-hemlighet inte angiven" -#: libpq/auth.c:2875 +#: libpq/auth.c:2883 #, c-format msgid "RADIUS authentication does not support passwords longer than %d characters" msgstr "RADIUS-autentisering stöder inte längre lösenord än %d tecken" -#: libpq/auth.c:2977 libpq/hba.c:2352 +#: libpq/auth.c:2985 libpq/hba.c:2352 #, c-format msgid "could not translate RADIUS server name \"%s\" to address: %s" msgstr "kunde inte översätta RADIUS-värdnamn \"%s\" till adress: %s" -#: libpq/auth.c:2991 +#: libpq/auth.c:2999 #, c-format msgid "could not generate random encryption vector" msgstr "kunde inte generera slumpad kodningsvektor" -#: libpq/auth.c:3028 +#: libpq/auth.c:3036 #, c-format msgid "could not perform MD5 encryption of password: %s" msgstr "kunde inte utföra MD5-kryptering av lösenord: %s" -#: libpq/auth.c:3055 +#: libpq/auth.c:3063 #, c-format msgid "could not create RADIUS socket: %m" msgstr "kunde inte skapa RADIUS-uttag (socket): %m" -#: libpq/auth.c:3071 +#: libpq/auth.c:3079 #, c-format msgid "could not bind local RADIUS socket: %m" msgstr "kunde inte binda lokalt RADIUS-uttag (socket): %m" -#: libpq/auth.c:3081 +#: libpq/auth.c:3089 #, c-format msgid "could not send RADIUS packet: %m" msgstr "kan inte skicka RADIUS-paketet: %m" -#: libpq/auth.c:3115 libpq/auth.c:3141 +#: libpq/auth.c:3123 libpq/auth.c:3149 #, c-format msgid "timeout waiting for RADIUS response from %s" msgstr "timeout vid väntande på RADIUS-svar från %s" -#: libpq/auth.c:3134 +#: libpq/auth.c:3142 #, c-format msgid "could not check status on RADIUS socket: %m" msgstr "kunde inte kontrollera status på RADIUS-uttag (socket): %m" -#: libpq/auth.c:3164 +#: libpq/auth.c:3172 #, c-format msgid "could not read RADIUS response: %m" msgstr "kunde inte läsa RADIUS-svar: %m" -#: libpq/auth.c:3172 +#: libpq/auth.c:3180 #, c-format msgid "RADIUS response from %s was sent from incorrect port: %d" msgstr "RADIUS-svar från %s skickades från fel port: %d" -#: libpq/auth.c:3180 +#: libpq/auth.c:3188 #, c-format msgid "RADIUS response from %s too short: %d" msgstr "RADIUS-svar från %s är för kort: %d" -#: libpq/auth.c:3187 +#: libpq/auth.c:3195 #, c-format msgid "RADIUS response from %s has corrupt length: %d (actual length %d)" msgstr "RADIUS-svar från %s har felaktig längd: %d (riktig längd %d)" -#: libpq/auth.c:3195 +#: libpq/auth.c:3203 #, c-format msgid "RADIUS response from %s is to a different request: %d (should be %d)" msgstr "RADIUS-svar från %s tillhör en annan förfrågan: %d (skall vara %d)" -#: libpq/auth.c:3220 +#: libpq/auth.c:3228 #, c-format msgid "could not perform MD5 encryption of received packet: %s" msgstr "kunde inte utföra MD5-kryptering på mottaget paket: %s" -#: libpq/auth.c:3230 +#: libpq/auth.c:3238 #, c-format msgid "RADIUS response from %s has incorrect MD5 signature" msgstr "RADIUS-svar från %s har inkorrekt MD5-signatur" -#: libpq/auth.c:3248 +#: libpq/auth.c:3256 #, c-format msgid "RADIUS response from %s has invalid code (%d) for user \"%s\"" msgstr "RADIUS-svar från %s har ogiltig kod (%d) för användare \"%s\"" @@ -15651,44 +15668,39 @@ msgstr "privat nyckelfil \"%s\" har grupp eller världsaccess" msgid "File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root." msgstr "Filen måste ha rättigheterna u=rw (0600) eller mindre om den ägs av databasanvändaren eller rättigheterna u=rw,g=r (0640) eller mindre om den ägs av root." -#: libpq/be-secure-gssapi.c:201 +#: libpq/be-secure-gssapi.c:208 msgid "GSSAPI wrap error" msgstr "GSSAPI-fel vid inpackning" -#: libpq/be-secure-gssapi.c:208 +#: libpq/be-secure-gssapi.c:215 #, c-format msgid "outgoing GSSAPI message would not use confidentiality" msgstr "utående GSSAPI-meddelande skulle inte använda sekretess" -#: libpq/be-secure-gssapi.c:215 libpq/be-secure-gssapi.c:634 +#: libpq/be-secure-gssapi.c:222 libpq/be-secure-gssapi.c:644 #, c-format msgid "server tried to send oversize GSSAPI packet (%zu > %zu)" msgstr "servern försöke skicka för stort GSSAPI-paket (%zu > %zu)" -#: libpq/be-secure-gssapi.c:351 +#: libpq/be-secure-gssapi.c:358 libpq/be-secure-gssapi.c:585 #, c-format msgid "oversize GSSAPI packet sent by the client (%zu > %zu)" msgstr "för stort GSSAPI-paket skickat av klienten (%zu > %zu)" -#: libpq/be-secure-gssapi.c:389 +#: libpq/be-secure-gssapi.c:396 msgid "GSSAPI unwrap error" msgstr "GSSAPI-fel vid uppackning" -#: libpq/be-secure-gssapi.c:396 +#: libpq/be-secure-gssapi.c:403 #, c-format msgid "incoming GSSAPI message did not use confidentiality" msgstr "inkommande GSSAPI-meddelande använde inte sekretess" -#: libpq/be-secure-gssapi.c:575 -#, c-format -msgid "oversize GSSAPI packet sent by the client (%zu > %d)" -msgstr "för stort GSSAPI-paket skickat av klienten (%zu > %d)" - -#: libpq/be-secure-gssapi.c:600 +#: libpq/be-secure-gssapi.c:610 msgid "could not accept GSSAPI security context" msgstr "kunde inte acceptera GSSSPI-säkerhetskontext" -#: libpq/be-secure-gssapi.c:701 +#: libpq/be-secure-gssapi.c:728 msgid "GSSAPI size check error" msgstr "GSSAPI-fel vid kontroll av storlek" @@ -16781,14 +16793,14 @@ msgstr "utökningsbar nodtyp \"%s\" finns redan" msgid "ExtensibleNodeMethods \"%s\" was not registered" msgstr "ExtensibleNodeMethods \"%s\" har inte registerats" -#: nodes/makefuncs.c:152 statistics/extended_stats.c:2310 +#: nodes/makefuncs.c:152 nodes/makefuncs.c:178 statistics/extended_stats.c:2310 #, c-format msgid "relation \"%s\" does not have a composite type" msgstr "relationen \"%s\" har ingen composite-typ" #: nodes/nodeFuncs.c:118 nodes/nodeFuncs.c:149 parser/parse_coerce.c:2604 #: parser/parse_coerce.c:2742 parser/parse_coerce.c:2789 -#: parser/parse_expr.c:2112 parser/parse_func.c:710 parser/parse_oper.c:869 +#: parser/parse_expr.c:2120 parser/parse_func.c:710 parser/parse_oper.c:869 #: utils/fmgr/funcapi.c:669 #, c-format msgid "could not find array type for data type %s" @@ -16804,12 +16816,12 @@ msgstr "portal \"%s\" med parametrar: %s" msgid "unnamed portal with parameters: %s" msgstr "ej namngiven portal med parametrar: %s" -#: optimizer/path/joinrels.c:972 +#: optimizer/path/joinrels.c:973 #, c-format msgid "FULL JOIN is only supported with merge-joinable or hash-joinable join conditions" msgstr "FULL JOIN stöds bara med villkor som är merge-joinbara eller hash-joinbara" -#: optimizer/plan/createplan.c:7175 parser/parse_merge.c:203 +#: optimizer/plan/createplan.c:7177 parser/parse_merge.c:203 #: rewrite/rewriteHandler.c:1680 #, c-format msgid "cannot execute MERGE on relation \"%s\"" @@ -16828,38 +16840,38 @@ msgstr "%s kan inte appliceras på den nullbara sidan av en outer join" msgid "%s is not allowed with UNION/INTERSECT/EXCEPT" msgstr "%s tillåts inte med UNION/INTERSECT/EXCEPT" -#: optimizer/plan/planner.c:2121 optimizer/plan/planner.c:4108 +#: optimizer/plan/planner.c:2121 optimizer/plan/planner.c:4151 #, c-format msgid "could not implement GROUP BY" msgstr "kunde inte implementera GROUP BY" -#: optimizer/plan/planner.c:2122 optimizer/plan/planner.c:4109 -#: optimizer/plan/planner.c:4790 optimizer/prep/prepunion.c:1320 +#: optimizer/plan/planner.c:2122 optimizer/plan/planner.c:4152 +#: optimizer/plan/planner.c:4833 optimizer/prep/prepunion.c:1320 #, c-format msgid "Some of the datatypes only support hashing, while others only support sorting." msgstr "Några av datatyperna stöder bara hash:ning medan andra bara stöder sortering." -#: optimizer/plan/planner.c:4789 +#: optimizer/plan/planner.c:4832 #, c-format msgid "could not implement DISTINCT" msgstr "kunde inte implementera DISTINCT" -#: optimizer/plan/planner.c:6134 +#: optimizer/plan/planner.c:6177 #, c-format msgid "could not implement window PARTITION BY" msgstr "kunde inte implementera fönster-PARTITION BY" -#: optimizer/plan/planner.c:6135 +#: optimizer/plan/planner.c:6178 #, c-format msgid "Window partitioning columns must be of sortable datatypes." msgstr "Fönsterpartitioneringskolumner måsta ha en sorterbar datatyp." -#: optimizer/plan/planner.c:6139 +#: optimizer/plan/planner.c:6182 #, c-format msgid "could not implement window ORDER BY" msgstr "kunde inte implementera fönster-ORDER BY" -#: optimizer/plan/planner.c:6140 +#: optimizer/plan/planner.c:6183 #, c-format msgid "Window ordering columns must be of sortable datatypes." msgstr "Fönsterordningskolumner måste ha en sorterbar datatyp." @@ -17335,7 +17347,7 @@ msgstr "yttre aggregat kan inte innehålla inre variabel i sitt direkta argument msgid "aggregate function calls cannot contain set-returning function calls" msgstr "aggregatfunktionsanrop kan inte innehålla mängdreturnerande funktionsanrop" -#: parser/parse_agg.c:780 parser/parse_expr.c:1762 parser/parse_expr.c:2245 +#: parser/parse_agg.c:780 parser/parse_expr.c:1762 parser/parse_expr.c:2253 #: parser/parse_func.c:885 #, c-format msgid "You might be able to move the set-returning function into a LATERAL FROM item." @@ -17732,8 +17744,8 @@ msgstr "Typomvandla offset-värdet till exakt den önskade typen." #: parser/parse_coerce.c:1050 parser/parse_coerce.c:1088 #: parser/parse_coerce.c:1106 parser/parse_coerce.c:1121 -#: parser/parse_expr.c:2146 parser/parse_expr.c:2754 parser/parse_expr.c:3405 -#: parser/parse_expr.c:3634 parser/parse_target.c:998 +#: parser/parse_expr.c:2154 parser/parse_expr.c:2762 parser/parse_expr.c:3413 +#: parser/parse_expr.c:3642 parser/parse_target.c:998 #, c-format msgid "cannot cast type %s to %s" msgstr "kan inte omvandla typ %s till %s" @@ -17928,147 +17940,147 @@ msgstr "rekursiv referens till fråga \"%s\" får inte finnas i en INTERSECT" msgid "recursive reference to query \"%s\" must not appear within EXCEPT" msgstr "rekursiv referens till fråga \"%s\" får inte finnas i en EXCEPT" -#: parser/parse_cte.c:136 +#: parser/parse_cte.c:137 #, c-format msgid "WITH query name \"%s\" specified more than once" msgstr "WITH-frågenamn \"%s\" angivet mer än en gång" -#: parser/parse_cte.c:308 +#: parser/parse_cte.c:309 #, c-format msgid "could not identify an inequality operator for type %s" msgstr "kunde inte hitta en olikhetsoperator för typ %s" -#: parser/parse_cte.c:335 +#: parser/parse_cte.c:336 #, c-format msgid "WITH clause containing a data-modifying statement must be at the top level" msgstr "WITH-klausul som innehåller en datamodifierande sats måste vara på toppnivå" -#: parser/parse_cte.c:384 +#: parser/parse_cte.c:385 #, c-format msgid "recursive query \"%s\" column %d has type %s in non-recursive term but type %s overall" msgstr "rekursiv fråga \"%s\" kolumn %d har typ %s i den ickerekursiva termen med typ %s totalt sett" -#: parser/parse_cte.c:390 +#: parser/parse_cte.c:391 #, c-format msgid "Cast the output of the non-recursive term to the correct type." msgstr "Typomvandla utdatan för den ickerekursiva termen till korrekt typ." -#: parser/parse_cte.c:395 +#: parser/parse_cte.c:396 #, c-format msgid "recursive query \"%s\" column %d has collation \"%s\" in non-recursive term but collation \"%s\" overall" msgstr "rekursiv fråga \"%s\" kolumn %d har jämförelse (collation) \"%s\" i en icke-rekursiv term men jämförelse \"%s\" totalt sett" -#: parser/parse_cte.c:399 +#: parser/parse_cte.c:400 #, c-format msgid "Use the COLLATE clause to set the collation of the non-recursive term." msgstr "Använd en COLLATE-klausul för att sätta jämförelse för den icke-rekursiva termen." -#: parser/parse_cte.c:420 +#: parser/parse_cte.c:421 #, c-format msgid "WITH query is not recursive" msgstr "WITH-fråga är inte rekursiv" -#: parser/parse_cte.c:451 +#: parser/parse_cte.c:452 #, c-format msgid "with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT" msgstr "med en SEARCH- eller CYCLE-klausul så måste vänstersidan av en UNION vara en SELECT" -#: parser/parse_cte.c:456 +#: parser/parse_cte.c:457 #, c-format msgid "with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT" msgstr "med en SEARCH- eller CYCLE-klausul så måste högersidan av en UNION vara en SELECT" -#: parser/parse_cte.c:471 +#: parser/parse_cte.c:472 #, c-format msgid "search column \"%s\" not in WITH query column list" msgstr "sökkolumn \"%s\" finns inte med i kolumnlistan för WITH-fråga" -#: parser/parse_cte.c:478 +#: parser/parse_cte.c:479 #, c-format msgid "search column \"%s\" specified more than once" msgstr "sökkolumn \"%s\" angiven mer än en gång" -#: parser/parse_cte.c:487 +#: parser/parse_cte.c:488 #, c-format msgid "search sequence column name \"%s\" already used in WITH query column list" msgstr "namn på söksekvensenskolumn \"%s\" används redan i kolumnlistan till WITH-fråga" -#: parser/parse_cte.c:504 +#: parser/parse_cte.c:505 #, c-format msgid "cycle column \"%s\" not in WITH query column list" msgstr "cycle-kolumn \"%s\" finns inte i kolumnlistan i WITH-fråga" -#: parser/parse_cte.c:511 +#: parser/parse_cte.c:512 #, c-format msgid "cycle column \"%s\" specified more than once" msgstr "cycle-kolumn \"%s\" angiven mer än en gång" -#: parser/parse_cte.c:520 +#: parser/parse_cte.c:521 #, c-format msgid "cycle mark column name \"%s\" already used in WITH query column list" msgstr "mark-kolumnnamn \"%s\" för cycle används redan i kolumnlistan i WITH-fråga" -#: parser/parse_cte.c:527 +#: parser/parse_cte.c:528 #, c-format msgid "cycle path column name \"%s\" already used in WITH query column list" msgstr "path-kolumnnamn \"%s\" för cycle används redan i kolumnlistan i WITH-fråga" -#: parser/parse_cte.c:535 +#: parser/parse_cte.c:536 #, c-format msgid "cycle mark column name and cycle path column name are the same" msgstr "mark-kolumnnamn och path-kolumnnamn i cycle är båda samma" -#: parser/parse_cte.c:545 +#: parser/parse_cte.c:546 #, c-format msgid "search sequence column name and cycle mark column name are the same" msgstr "namn på söksekvenskolumn och namn på mark-kolumn i cycle är båda samma" -#: parser/parse_cte.c:552 +#: parser/parse_cte.c:553 #, c-format msgid "search sequence column name and cycle path column name are the same" msgstr "namn på söksekvenskolumn och namn på path-kolumn i cycle är båda samma" -#: parser/parse_cte.c:636 +#: parser/parse_cte.c:637 #, c-format msgid "WITH query \"%s\" has %d columns available but %d columns specified" msgstr "WITH-fråga \"%s\" har %d kolumner tillgängliga men %d kolumner angivna" -#: parser/parse_cte.c:816 +#: parser/parse_cte.c:882 #, c-format msgid "mutual recursion between WITH items is not implemented" msgstr "ömsesidig rekursion mellan WITH-poster är inte implementerat" -#: parser/parse_cte.c:868 +#: parser/parse_cte.c:934 #, c-format msgid "recursive query \"%s\" must not contain data-modifying statements" msgstr "rekursiv fråga \"%s\" får inte innehålla datamodifierande satser" -#: parser/parse_cte.c:876 +#: parser/parse_cte.c:942 #, c-format msgid "recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term" msgstr "rekursiv fråga \"%s\" är inte på formen icke-rekursiv-term UNION [ALL] rekursiv-term" -#: parser/parse_cte.c:911 +#: parser/parse_cte.c:977 #, c-format msgid "ORDER BY in a recursive query is not implemented" msgstr "ORDER BY i en rekursiv fråga är inte implementerat" -#: parser/parse_cte.c:917 +#: parser/parse_cte.c:983 #, c-format msgid "OFFSET in a recursive query is not implemented" msgstr "OFFSET i en rekursiv fråga är inte implementerat" -#: parser/parse_cte.c:923 +#: parser/parse_cte.c:989 #, c-format msgid "LIMIT in a recursive query is not implemented" msgstr "LIMIT i en rekursiv fråga är inte implementerat" -#: parser/parse_cte.c:929 +#: parser/parse_cte.c:995 #, c-format msgid "FOR UPDATE/SHARE in a recursive query is not implemented" msgstr "FOR UPDATE/SHARE i en rekursiv fråga är inte implementerat" -#: parser/parse_cte.c:1008 +#: parser/parse_cte.c:1074 #, c-format msgid "recursive reference to query \"%s\" must not appear more than once" msgstr "rekursiv referens till fråga \"%s\" får inte finnas med mer än en gång" @@ -18126,13 +18138,13 @@ msgid "there is no parameter $%d" msgstr "det finns ingen parameter $%d" #. translator: %s is name of a SQL construct, eg NULLIF -#: parser/parse_expr.c:1103 parser/parse_expr.c:3065 +#: parser/parse_expr.c:1103 parser/parse_expr.c:3073 #, c-format msgid "%s requires = operator to yield boolean" msgstr "%s kräver att operatorn = returnerar boolean" #. translator: %s is name of a SQL construct, eg NULLIF -#: parser/parse_expr.c:1109 parser/parse_expr.c:3072 +#: parser/parse_expr.c:1109 parser/parse_expr.c:3080 #, c-format msgid "%s must not return a set" msgstr "%s får inte returnera en mängd" @@ -18153,7 +18165,7 @@ msgid "source for a multiple-column UPDATE item must be a sub-SELECT or ROW() ex msgstr "källa till en multiple-kolumn-UPDATE-post måste vara en sub-SELECT eller ROW()-uttryck" #. translator: %s is name of a SQL construct, eg GROUP BY -#: parser/parse_expr.c:1760 parser/parse_expr.c:2243 parser/parse_func.c:2679 +#: parser/parse_expr.c:1760 parser/parse_expr.c:2251 parser/parse_func.c:2679 #, c-format msgid "set-returning functions are not allowed in %s" msgstr "mängdreturnerande funktioner tillåts inte i %s" @@ -18210,7 +18222,7 @@ msgstr "kan inte använda subfråga i COPY FROM WHERE-villkor" msgid "cannot use subquery in column generation expression" msgstr "kan inte använda subfråga i kolumngenereringsuttryck" -#: parser/parse_expr.c:1914 parser/parse_expr.c:3764 +#: parser/parse_expr.c:1914 parser/parse_expr.c:3772 #, c-format msgid "subquery must return only one column" msgstr "underfråga kan bara returnera en kolumn" @@ -18225,174 +18237,174 @@ msgstr "underfråga har för många kolumner" msgid "subquery has too few columns" msgstr "underfråga har för få kolumner" -#: parser/parse_expr.c:2086 +#: parser/parse_expr.c:2094 #, c-format msgid "cannot determine type of empty array" msgstr "kan inte bestämma typen av en tom array" -#: parser/parse_expr.c:2087 +#: parser/parse_expr.c:2095 #, c-format msgid "Explicitly cast to the desired type, for example ARRAY[]::integer[]." msgstr "Typomvandla explicit till den önskade typen, till exempel ARRAY[]::integer[]." -#: parser/parse_expr.c:2101 +#: parser/parse_expr.c:2109 #, c-format msgid "could not find element type for data type %s" msgstr "kunde inte hitta elementtyp för datatyp %s" -#: parser/parse_expr.c:2184 +#: parser/parse_expr.c:2192 #, c-format msgid "ROW expressions can have at most %d entries" msgstr "ROW-uttryck kan ha som mest %d poster" -#: parser/parse_expr.c:2389 +#: parser/parse_expr.c:2397 #, c-format msgid "unnamed XML attribute value must be a column reference" msgstr "onamnat XML-attributvärde måste vara en kolumnreferens" -#: parser/parse_expr.c:2390 +#: parser/parse_expr.c:2398 #, c-format msgid "unnamed XML element value must be a column reference" msgstr "onamnat XML-elementvärde måste vara en kolumnreferens" -#: parser/parse_expr.c:2405 +#: parser/parse_expr.c:2413 #, c-format msgid "XML attribute name \"%s\" appears more than once" msgstr "XML-attributnamn \"%s\" finns med mer än en gång" -#: parser/parse_expr.c:2513 +#: parser/parse_expr.c:2521 #, c-format msgid "cannot cast XMLSERIALIZE result to %s" msgstr "kan inte typomvandla XMLSERIALIZE-resultat till %s" -#: parser/parse_expr.c:2827 parser/parse_expr.c:3023 +#: parser/parse_expr.c:2835 parser/parse_expr.c:3031 #, c-format msgid "unequal number of entries in row expressions" msgstr "olika antal element i raduttryck" -#: parser/parse_expr.c:2837 +#: parser/parse_expr.c:2845 #, c-format msgid "cannot compare rows of zero length" msgstr "kan inte jämföra rader med längden noll" -#: parser/parse_expr.c:2862 +#: parser/parse_expr.c:2870 #, c-format msgid "row comparison operator must yield type boolean, not type %s" msgstr "operator för radjämförelse måste resultera i typen boolean, inte %s" -#: parser/parse_expr.c:2869 +#: parser/parse_expr.c:2877 #, c-format msgid "row comparison operator must not return a set" msgstr "radjämförelseoperator får inte returnera en mängd" -#: parser/parse_expr.c:2928 parser/parse_expr.c:2969 +#: parser/parse_expr.c:2936 parser/parse_expr.c:2977 #, c-format msgid "could not determine interpretation of row comparison operator %s" msgstr "kunde inte lista ut tolkning av radjämförelseoperator %s" -#: parser/parse_expr.c:2930 +#: parser/parse_expr.c:2938 #, c-format msgid "Row comparison operators must be associated with btree operator families." msgstr "Radjämförelseoperatorer måste vara associerade med btreee-operatorfamiljer." -#: parser/parse_expr.c:2971 +#: parser/parse_expr.c:2979 #, c-format msgid "There are multiple equally-plausible candidates." msgstr "Det finns flera lika sannolika kandidater." -#: parser/parse_expr.c:3306 +#: parser/parse_expr.c:3314 #, c-format msgid "JSON ENCODING clause is only allowed for bytea input type" msgstr "JSON ENCODING tillåts bara för input-typen bytea" -#: parser/parse_expr.c:3370 +#: parser/parse_expr.c:3378 #, c-format msgid "cannot use non-string types with implicit FORMAT JSON clause" msgstr "kan inte använda icke-strängtyper med implicit FORMAT JSON-klausul" -#: parser/parse_expr.c:3371 +#: parser/parse_expr.c:3379 #, c-format msgid "cannot use non-string types with explicit FORMAT JSON clause" msgstr "kan inte använda icke-strängtyper med explicit FORMAT JSON-klausul" -#: parser/parse_expr.c:3460 +#: parser/parse_expr.c:3468 #, c-format msgid "cannot use JSON format with non-string output types" msgstr "kan inte använda JSON-formatet för utddata som inte är strängar" -#: parser/parse_expr.c:3473 +#: parser/parse_expr.c:3481 #, c-format msgid "cannot set JSON encoding for non-bytea output types" msgstr "kan inte sätta JSON-kodning för utdata-typer som inte är bytea" -#: parser/parse_expr.c:3478 +#: parser/parse_expr.c:3486 #, c-format msgid "unsupported JSON encoding" msgstr "ej stödd JSON-kodning" -#: parser/parse_expr.c:3479 +#: parser/parse_expr.c:3487 #, c-format msgid "Only UTF8 JSON encoding is supported." msgstr "Enbart JSON-kodningen UTF8 stöds." -#: parser/parse_expr.c:3516 +#: parser/parse_expr.c:3524 #, c-format msgid "returning SETOF types is not supported in SQL/JSON functions" msgstr "returtyp SETOF stöds inte för SQL/JSON-funktioner" -#: parser/parse_expr.c:3521 +#: parser/parse_expr.c:3529 #, c-format msgid "returning pseudo-types is not supported in SQL/JSON functions" msgstr "pseudo-typer stöds inte som resultat för SQL/JSON-funktioner" -#: parser/parse_expr.c:3849 parser/parse_func.c:866 +#: parser/parse_expr.c:3857 parser/parse_func.c:866 #, c-format msgid "aggregate ORDER BY is not implemented for window functions" msgstr "aggregat-ORDER BY är inte implementerat för fönsterfunktioner" -#: parser/parse_expr.c:4072 +#: parser/parse_expr.c:4080 #, c-format msgid "cannot use JSON FORMAT ENCODING clause for non-bytea input types" msgstr "kan inte använda JSON FORMAT ENCODING för indatatyper som inte är bytea" -#: parser/parse_expr.c:4092 +#: parser/parse_expr.c:4100 #, c-format msgid "cannot use type %s in IS JSON predicate" msgstr "kan inte använda typen %s i ett IS JSON-predikat" -#: parser/parse_expr.c:4118 parser/parse_expr.c:4239 +#: parser/parse_expr.c:4126 parser/parse_expr.c:4247 #, c-format msgid "cannot use type %s in RETURNING clause of %s" msgstr "kan inte använda typen %s i RETURNING-klausul för %s" -#: parser/parse_expr.c:4120 +#: parser/parse_expr.c:4128 #, c-format msgid "Try returning json or jsonb." msgstr "Försök returnera json eller jsonb." -#: parser/parse_expr.c:4168 +#: parser/parse_expr.c:4176 #, c-format msgid "cannot use non-string types with WITH UNIQUE KEYS clause" msgstr "kan inte använda icke-strängtyper med WITH UNIQUE KEYS-klausul" -#: parser/parse_expr.c:4242 +#: parser/parse_expr.c:4250 #, c-format msgid "Try returning a string type or bytea." msgstr "Försök returnera en strängtyp eller bytea." -#: parser/parse_expr.c:4307 +#: parser/parse_expr.c:4315 #, c-format msgid "cannot specify FORMAT JSON in RETURNING clause of %s()" msgstr "kan inte ange FORMAT JSON i RETURNING-klausul för %s()" -#: parser/parse_expr.c:4320 +#: parser/parse_expr.c:4328 #, c-format msgid "SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used" msgstr "SQL/JSON QUOTES får inte anges tillsammans med WITH WRAPPER" #. translator: %s is name of a SQL/JSON clause (eg. ON EMPTY) -#: parser/parse_expr.c:4334 parser/parse_expr.c:4363 parser/parse_expr.c:4394 -#: parser/parse_expr.c:4420 parser/parse_expr.c:4446 +#: parser/parse_expr.c:4342 parser/parse_expr.c:4371 parser/parse_expr.c:4402 +#: parser/parse_expr.c:4428 parser/parse_expr.c:4454 #: parser/parse_jsontable.c:94 #, c-format msgid "invalid %s behavior" @@ -18400,7 +18412,7 @@ msgstr "ogiltig %s-angivelse" #. translator: first %s is name of a SQL/JSON clause (eg. ON EMPTY), #. second %s is a SQL/JSON function name (e.g. JSON_QUERY) -#: parser/parse_expr.c:4337 parser/parse_expr.c:4366 +#: parser/parse_expr.c:4345 parser/parse_expr.c:4374 #, c-format msgid "Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in %s for %s." msgstr "Bara uttrycken ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT eller DEFAULT tillåts i %s för %s." @@ -18408,68 +18420,68 @@ msgstr "Bara uttrycken ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT eller DEFAULT till #. translator: first %s is name of a SQL/JSON clause (eg. ON EMPTY) #. translator: first %s is name a SQL/JSON clause (eg. ON EMPTY) #. translator: first %s is name of a SQL/JSON clause (eg. ON EMPTY) -#: parser/parse_expr.c:4344 parser/parse_expr.c:4373 parser/parse_expr.c:4402 -#: parser/parse_expr.c:4430 parser/parse_expr.c:4456 +#: parser/parse_expr.c:4352 parser/parse_expr.c:4381 parser/parse_expr.c:4410 +#: parser/parse_expr.c:4438 parser/parse_expr.c:4464 #, c-format msgid "invalid %s behavior for column \"%s\"" msgstr "ogiltig %s-angivelse för kolumn \"%s\"" #. translator: %s is name of a SQL/JSON clause (eg. ON EMPTY) -#: parser/parse_expr.c:4347 parser/parse_expr.c:4376 +#: parser/parse_expr.c:4355 parser/parse_expr.c:4384 #, c-format msgid "Only ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT, or DEFAULT expression is allowed in %s for formatted columns." msgstr "Bara uttrycken ERROR, NULL, EMPTY ARRAY, EMPTY OBJECT eller DEFAULT tillåts i %s för formatterade kolumner." -#: parser/parse_expr.c:4395 +#: parser/parse_expr.c:4403 #, c-format msgid "Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in %s for %s." msgstr "Bara ERROR, TRUE, FALSE eller UNKNOWN tillåts i %s för %s." #. translator: %s is name of a SQL/JSON clause (eg. ON EMPTY) -#: parser/parse_expr.c:4405 +#: parser/parse_expr.c:4413 #, c-format msgid "Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in %s for EXISTS columns." msgstr "Bara ERROR, TRUE, FALSE eller UNKNOWN tillåts i %s för EXISTS-kolumner." #. translator: first %s is name of a SQL/JSON clause (eg. ON EMPTY), #. second %s is a SQL/JSON function name (e.g. JSON_QUERY) -#: parser/parse_expr.c:4423 parser/parse_expr.c:4449 +#: parser/parse_expr.c:4431 parser/parse_expr.c:4457 #, c-format msgid "Only ERROR, NULL, or DEFAULT expression is allowed in %s for %s." msgstr "Bara uttrycken ERROR, NULL eller DEFAULT tillåts i %s för %s." #. translator: %s is name of a SQL/JSON clause (eg. ON EMPTY) -#: parser/parse_expr.c:4433 parser/parse_expr.c:4459 +#: parser/parse_expr.c:4441 parser/parse_expr.c:4467 #, c-format msgid "Only ERROR, NULL, or DEFAULT expression is allowed in %s for scalar columns." msgstr "Bara uttrycken ERROR, NULL eller DEFAULT tillåts i %s för skalära kolumner." -#: parser/parse_expr.c:4489 +#: parser/parse_expr.c:4497 #, c-format msgid "JSON path expression must be of type %s, not of type %s" msgstr "Uttryck för JSON-sökväg måste vara av typ %s, inte av typ %s" -#: parser/parse_expr.c:4707 +#: parser/parse_expr.c:4715 #, c-format msgid "can only specify a constant, non-aggregate function, or operator expression for DEFAULT" msgstr "kan bara ange en konstant, en icke-aggregat-funktion eller ett operatoruttryck för DEFAULT" -#: parser/parse_expr.c:4712 +#: parser/parse_expr.c:4720 #, c-format msgid "DEFAULT expression must not contain column references" msgstr "DEFAULT-uttryck får inte innehålla kolumnreferenser" -#: parser/parse_expr.c:4717 +#: parser/parse_expr.c:4725 #, c-format msgid "DEFAULT expression must not return a set" msgstr "DEFAULT-uttryck får inte returnera en mängd" -#: parser/parse_expr.c:4793 parser/parse_expr.c:4802 +#: parser/parse_expr.c:4801 parser/parse_expr.c:4810 #, c-format msgid "cannot cast behavior expression of type %s to %s" msgstr "kan inte omvandla uttryck av typ %s till %s" -#: parser/parse_expr.c:4796 +#: parser/parse_expr.c:4804 #, c-format msgid "You will need to explicitly cast the expression to type %s." msgstr "Du måste explicit omvandla uttrycket till typen %s." @@ -19804,32 +19816,32 @@ msgstr "Misslyckat systemanrop var MapViewOfFileEx." msgid "autovacuum worker took too long to start; canceled" msgstr "autovacuum-arbetaren tog för lång tid på sig att starta; avbruten" -#: postmaster/autovacuum.c:2203 +#: postmaster/autovacuum.c:2218 #, c-format msgid "autovacuum: dropping orphan temp table \"%s.%s.%s\"" msgstr "autovacuum: slänger övergiven temptabell \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2439 +#: postmaster/autovacuum.c:2461 #, c-format msgid "automatic vacuum of table \"%s.%s.%s\"" msgstr "automatisk vacuum av tabell \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2442 +#: postmaster/autovacuum.c:2464 #, c-format msgid "automatic analyze of table \"%s.%s.%s\"" msgstr "automatisk analys av tabell \"%s.%s.%s\"" -#: postmaster/autovacuum.c:2636 +#: postmaster/autovacuum.c:2660 #, c-format msgid "processing work entry for relation \"%s.%s.%s\"" msgstr "processar arbetspost för relation \"%s.%s.%s\"" -#: postmaster/autovacuum.c:3254 +#: postmaster/autovacuum.c:3291 #, c-format msgid "autovacuum not started because of misconfiguration" msgstr "autovacuum har inte startats på grund av en felkonfigurering" -#: postmaster/autovacuum.c:3255 +#: postmaster/autovacuum.c:3292 #, c-format msgid "Enable the \"track_counts\" option." msgstr "Slå på flaggan \"track_counts\"." @@ -19886,24 +19898,24 @@ msgid_plural "Up to %d background workers can be registered with the current set msgstr[0] "Upp till %d bakgrundsarbetare kan registreras med nuvarande inställning." msgstr[1] "Upp till %d bakgrundsarbetare kan registreras med nuvarande inställning." -#: postmaster/bgworker.c:931 postmaster/checkpointer.c:445 +#: postmaster/bgworker.c:931 postmaster/checkpointer.c:448 #, c-format msgid "Consider increasing the configuration parameter \"%s\"." msgstr "Överväg att öka konfigurationsparametern \"%s\"." -#: postmaster/checkpointer.c:441 +#: postmaster/checkpointer.c:444 #, c-format msgid "checkpoints are occurring too frequently (%d second apart)" msgid_plural "checkpoints are occurring too frequently (%d seconds apart)" msgstr[0] "checkpoint:s sker för ofta (%d sekund emellan)" msgstr[1] "checkpoint:s sker för ofta (%d sekunder emellan)" -#: postmaster/checkpointer.c:1067 +#: postmaster/checkpointer.c:1073 #, c-format msgid "checkpoint request failed" msgstr "checkpoint-behgäran misslyckades" -#: postmaster/checkpointer.c:1068 +#: postmaster/checkpointer.c:1074 #, c-format msgid "Consult recent messages in the server log for details." msgstr "Se senaste meddelanden i serverloggen för mer information." @@ -20709,22 +20721,22 @@ msgstr "kunde inte skicka data till kö i delat minne" msgid "logical replication apply worker will serialize the remaining changes of remote transaction %u to a file" msgstr "logiska replikeringens ändringsapplicerare kommer spara ner återstående ändringarna av fjärrtransaktion %u til en fil" -#: replication/logical/decode.c:177 replication/logical/logical.c:141 +#: replication/logical/decode.c:177 replication/logical/logical.c:143 #, c-format msgid "logical decoding on standby requires \"wal_level\" >= \"logical\" on the primary" msgstr "logisk avkodning på standby kräver \"wal_level\" >= \"logical\" på primären" -#: replication/logical/launcher.c:334 +#: replication/logical/launcher.c:347 #, c-format msgid "cannot start logical replication workers when max_replication_slots = 0" msgstr "kan inte starta logisk replikeringsarbetare när max_replication_slots = 0" -#: replication/logical/launcher.c:427 +#: replication/logical/launcher.c:440 #, c-format msgid "out of logical replication worker slots" msgstr "slut på logiska replikeringsarbetarslots" -#: replication/logical/launcher.c:428 replication/logical/launcher.c:514 +#: replication/logical/launcher.c:441 replication/logical/launcher.c:527 #: replication/slot.c:1524 storage/lmgr/lock.c:985 storage/lmgr/lock.c:1023 #: storage/lmgr/lock.c:2836 storage/lmgr/lock.c:4221 storage/lmgr/lock.c:4286 #: storage/lmgr/lock.c:4636 storage/lmgr/predicate.c:2469 @@ -20733,111 +20745,111 @@ msgstr "slut på logiska replikeringsarbetarslots" msgid "You might need to increase \"%s\"." msgstr "Du kan behöva öka \"%s\"." -#: replication/logical/launcher.c:513 +#: replication/logical/launcher.c:526 #, c-format msgid "out of background worker slots" msgstr "slut på bakgrundsarbetarslots" -#: replication/logical/launcher.c:720 +#: replication/logical/launcher.c:733 #, c-format msgid "logical replication worker slot %d is empty, cannot attach" msgstr "logisk replikeringsarbetarslot %d är tom, kan inte ansluta" -#: replication/logical/launcher.c:729 +#: replication/logical/launcher.c:742 #, c-format msgid "logical replication worker slot %d is already used by another worker, cannot attach" msgstr "logiisk replikeringsarbetarslot %d används redan av en annan arbetare, kan inte ansluta" -#: replication/logical/logical.c:121 +#: replication/logical/logical.c:123 #, c-format msgid "logical decoding requires \"wal_level\" >= \"logical\"" msgstr "logisk avkodning kräver \"wal_level\" >= \"logical\"" -#: replication/logical/logical.c:126 +#: replication/logical/logical.c:128 #, c-format msgid "logical decoding requires a database connection" msgstr "logisk avkodning kräver en databasanslutning" -#: replication/logical/logical.c:365 replication/logical/logical.c:519 +#: replication/logical/logical.c:367 replication/logical/logical.c:521 #, c-format msgid "cannot use physical replication slot for logical decoding" msgstr "kan inte använda fysisk replikeringsslot för logisk avkodning" -#: replication/logical/logical.c:370 replication/logical/logical.c:529 +#: replication/logical/logical.c:372 replication/logical/logical.c:531 #, c-format msgid "replication slot \"%s\" was not created in this database" msgstr "replikeringsslot \"%s\" har inte skapats i denna databasen" -#: replication/logical/logical.c:377 +#: replication/logical/logical.c:379 #, c-format msgid "cannot create logical replication slot in transaction that has performed writes" msgstr "kan inte skapa logisk replikeringsslot i transaktion som redan har utfört skrivningar" -#: replication/logical/logical.c:540 +#: replication/logical/logical.c:542 #, c-format msgid "cannot use replication slot \"%s\" for logical decoding" msgstr "kan inte använda replikeringsslot \"%s\" för logisk avkodning" -#: replication/logical/logical.c:542 replication/slot.c:798 +#: replication/logical/logical.c:544 replication/slot.c:798 #: replication/slot.c:829 #, c-format msgid "This replication slot is being synchronized from the primary server." msgstr "Denna replikeringsslot synkroniseras från primära servern." -#: replication/logical/logical.c:543 +#: replication/logical/logical.c:545 #, c-format msgid "Specify another replication slot." msgstr "Ange en annan replikeringsslot." -#: replication/logical/logical.c:554 replication/logical/logical.c:561 +#: replication/logical/logical.c:556 replication/logical/logical.c:563 #, c-format msgid "can no longer get changes from replication slot \"%s\"" msgstr "kan inte längre få ändringar från replikeringsslot \"%s\"" -#: replication/logical/logical.c:556 +#: replication/logical/logical.c:558 #, c-format msgid "This slot has been invalidated because it exceeded the maximum reserved size." msgstr "Denna slot har invaliderats då den överskred maximal reserverad storlek." -#: replication/logical/logical.c:563 +#: replication/logical/logical.c:565 #, c-format msgid "This slot has been invalidated because it was conflicting with recovery." msgstr "Denna slot har invaliderats den var i konflikt med återställningen." -#: replication/logical/logical.c:628 +#: replication/logical/logical.c:630 #, c-format msgid "starting logical decoding for slot \"%s\"" msgstr "startar logisk avkodning för slot \"%s\"" -#: replication/logical/logical.c:630 +#: replication/logical/logical.c:632 #, c-format msgid "Streaming transactions committing after %X/%X, reading WAL from %X/%X." msgstr "Strömmar transaktioner commit:ade efter %X/%X, läser WAL från %X/%X" -#: replication/logical/logical.c:778 +#: replication/logical/logical.c:780 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X" msgstr "slot \"%s\", utdata-plugin \"%s\", i callback:en %s, associerad LSN %X/%X" -#: replication/logical/logical.c:784 +#: replication/logical/logical.c:786 #, c-format msgid "slot \"%s\", output plugin \"%s\", in the %s callback" msgstr "slot \"%s\", utdata-plugin \"%s\", i callback:en %s" -#: replication/logical/logical.c:955 replication/logical/logical.c:1000 -#: replication/logical/logical.c:1045 replication/logical/logical.c:1091 +#: replication/logical/logical.c:957 replication/logical/logical.c:1002 +#: replication/logical/logical.c:1047 replication/logical/logical.c:1093 #, c-format msgid "logical replication at prepare time requires a %s callback" msgstr "logisk replikering vid prepare-tillfället kräver en %s-callback" -#: replication/logical/logical.c:1323 replication/logical/logical.c:1372 -#: replication/logical/logical.c:1413 replication/logical/logical.c:1499 -#: replication/logical/logical.c:1548 +#: replication/logical/logical.c:1325 replication/logical/logical.c:1374 +#: replication/logical/logical.c:1415 replication/logical/logical.c:1501 +#: replication/logical/logical.c:1550 #, c-format msgid "logical streaming requires a %s callback" msgstr "logisk strömning kräven en %s-callback" -#: replication/logical/logical.c:1458 +#: replication/logical/logical.c:1460 #, c-format msgid "logical streaming at prepare time requires a %s callback" msgstr "logisk strömning vid prepare-tillfället kräver en %s-callback" @@ -20934,7 +20946,7 @@ msgid "could not find free replication state slot for replication origin with ID msgstr "kunde inte hitta ledig replikerings-state-slot för replikerings-origin med ID %d" #: replication/logical/origin.c:957 replication/logical/origin.c:1158 -#: replication/slot.c:2384 +#: replication/slot.c:2397 #, c-format msgid "Increase \"max_replication_slots\" and try again." msgstr "Öka \"max_replication_slots\" och försök igen." @@ -20987,57 +20999,52 @@ msgstr "destinationsrelation \"%s.%s\" för logisk replikering använder systemk msgid "logical replication target relation \"%s.%s\" does not exist" msgstr "destinationsrelation \"%s.%s\" för logisk replikering finns inte" -#: replication/logical/reorderbuffer.c:3999 +#: replication/logical/reorderbuffer.c:4129 #, c-format msgid "could not write to data file for XID %u: %m" msgstr "kunde inte skriva till datafil för XID %u: %m" -#: replication/logical/reorderbuffer.c:4345 -#: replication/logical/reorderbuffer.c:4370 +#: replication/logical/reorderbuffer.c:4475 +#: replication/logical/reorderbuffer.c:4500 #, c-format msgid "could not read from reorderbuffer spill file: %m" msgstr "kunde inte läsa från reorderbuffer spill-fil: %m" -#: replication/logical/reorderbuffer.c:4349 -#: replication/logical/reorderbuffer.c:4374 +#: replication/logical/reorderbuffer.c:4479 +#: replication/logical/reorderbuffer.c:4504 #, c-format msgid "could not read from reorderbuffer spill file: read %d instead of %u bytes" msgstr "kunde inte läsa från reorderbuffer spill-fil: läste %d istället för %u byte" -#: replication/logical/reorderbuffer.c:4624 +#: replication/logical/reorderbuffer.c:4754 #, c-format msgid "could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m" msgstr "kunde inte radera fil \"%s\" vid borttagning av pg_replslot/%s/xid*: %m" -#: replication/logical/reorderbuffer.c:5120 +#: replication/logical/reorderbuffer.c:5250 #, c-format msgid "could not read from file \"%s\": read %d instead of %d bytes" msgstr "kunde inte läsa från fil \"%s\": läste %d istället för %d byte" -#: replication/logical/slotsync.c:215 +#: replication/logical/slotsync.c:215 replication/logical/slotsync.c:579 #, c-format -msgid "could not synchronize replication slot \"%s\" because remote slot precedes local slot" -msgstr "kunde inte synkronisera replikeringsslot \"%s\" då fjärrslotten ligger tidigare än den lokala slotten" +msgid "could not synchronize replication slot \"%s\"" +msgstr "kunde inte synkronisera replikeringsslot \"%s\"" #: replication/logical/slotsync.c:217 #, c-format -msgid "The remote slot has LSN %X/%X and catalog xmin %u, but the local slot has LSN %X/%X and catalog xmin %u." -msgstr "Fjärrslotten har LSN %X/%X och katalog-xmin %u men lokala slotten har LSN %X/%X och katalog-xmin %u." +msgid "Synchronization could lead to data loss, because the remote slot needs WAL at LSN %X/%X and catalog xmin %u, but the standby has LSN %X/%X and catalog xmin %u." +msgstr "Synkronisering kan leda till dataförlust eftersom fjärrslotten kräver WAL vid LSN %X/%X och katalog-xmin %u men standby har LSN %X/%X och katalog-xmin %u." #: replication/logical/slotsync.c:459 #, c-format msgid "dropped replication slot \"%s\" of database with OID %u" msgstr "slängde replikerings-slot \"%s\" för databas med OID %u" -#: replication/logical/slotsync.c:579 -#, c-format -msgid "could not synchronize replication slot \"%s\"" -msgstr "kunde inte synkronisera replikeringsslot \"%s\"" - #: replication/logical/slotsync.c:580 #, c-format -msgid "Logical decoding could not find consistent point from local slot's LSN %X/%X." -msgstr "Logisk avkodning kunde inte hitta en konsistent punkt från lokala slottens LSN %X/%X" +msgid "Synchronization could lead to data loss, because the standby could not build a consistent snapshot to decode WALs at LSN %X/%X." +msgstr "Synkronisering kan leda till dataförlust eftersom standby:en inte kunde skapa en konsistent snapshot för att avkoda WAL vid LSN %X/%X." #: replication/logical/slotsync.c:589 #, c-format @@ -21088,7 +21095,7 @@ msgstr "replikeringsslot \"%s\" angiven av %s finns inte på den primära server msgid "replication slot synchronization requires \"%s\" to be specified in \"%s\"" msgstr "synkronisering av replikeringsslot kräver att \"%s\" anges i %s" -#: replication/logical/slotsync.c:1050 +#: replication/logical/slotsync.c:1048 #, c-format msgid "replication slot synchronization requires \"wal_level\" >= \"logical\"" msgstr "synkronisering av replikeringsslot kräver at \"wal_level\" >= \"logical\"" @@ -21136,7 +21143,7 @@ msgstr "kan inte synkronisera replikeringsslottar parallellt" msgid "slot sync worker started" msgstr "arbetare för slot-synk startad" -#: replication/logical/slotsync.c:1466 replication/slotfuncs.c:900 +#: replication/logical/slotsync.c:1466 replication/slotfuncs.c:926 #: replication/walreceiver.c:307 #, c-format msgid "could not connect to the primary server: %s" @@ -21154,58 +21161,58 @@ msgid_plural "exported logical decoding snapshot: \"%s\" with %u transaction IDs msgstr[0] "exporterade logisk avkodnings-snapshot: \"%s\" med %u transaktions-ID" msgstr[1] "exporterade logisk avkodnings-snapshot: \"%s\" med %u transaktions-ID" -#: replication/logical/snapbuild.c:1404 replication/logical/snapbuild.c:1501 -#: replication/logical/snapbuild.c:2017 +#: replication/logical/snapbuild.c:1451 replication/logical/snapbuild.c:1548 +#: replication/logical/snapbuild.c:2064 #, c-format msgid "logical decoding found consistent point at %X/%X" msgstr "logisk avkodning hittade konsistent punkt vid %X/%X" -#: replication/logical/snapbuild.c:1406 +#: replication/logical/snapbuild.c:1453 #, c-format msgid "There are no running transactions." msgstr "Det finns inga körande transaktioner." -#: replication/logical/snapbuild.c:1453 +#: replication/logical/snapbuild.c:1500 #, c-format msgid "logical decoding found initial starting point at %X/%X" msgstr "logisk avkodning hittade initial startpunkt vid %X/%X" -#: replication/logical/snapbuild.c:1455 replication/logical/snapbuild.c:1479 +#: replication/logical/snapbuild.c:1502 replication/logical/snapbuild.c:1526 #, c-format msgid "Waiting for transactions (approximately %d) older than %u to end." msgstr "Väntar på att transaktioner (cirka %d) äldre än %u skall gå klart." -#: replication/logical/snapbuild.c:1477 +#: replication/logical/snapbuild.c:1524 #, c-format msgid "logical decoding found initial consistent point at %X/%X" msgstr "logisk avkodning hittade initial konsistent punkt vid %X/%X" -#: replication/logical/snapbuild.c:1503 +#: replication/logical/snapbuild.c:1550 #, c-format msgid "There are no old transactions anymore." msgstr "Det finns inte längre några gamla transaktioner." -#: replication/logical/snapbuild.c:1904 +#: replication/logical/snapbuild.c:1951 #, c-format msgid "snapbuild state file \"%s\" has wrong magic number: %u instead of %u" msgstr "snapbuild-state-fil \"%s\" har fel magiskt tal: %u istället för %u" -#: replication/logical/snapbuild.c:1910 +#: replication/logical/snapbuild.c:1957 #, c-format msgid "snapbuild state file \"%s\" has unsupported version: %u instead of %u" msgstr "snapbuild-state-fil \"%s\" har en ej stödd version: %u istället för %u" -#: replication/logical/snapbuild.c:1951 +#: replication/logical/snapbuild.c:1998 #, c-format msgid "checksum mismatch for snapbuild state file \"%s\": is %u, should be %u" msgstr "checksumma stämmer inte för snapbuild-state-fil \"%s\": är %u, skall vara %u" -#: replication/logical/snapbuild.c:2019 +#: replication/logical/snapbuild.c:2066 #, c-format msgid "Logical decoding will begin using saved snapshot." msgstr "Logisk avkodning kommer starta med sparat snapshot." -#: replication/logical/snapbuild.c:2126 +#: replication/logical/snapbuild.c:2173 #, c-format msgid "could not parse file name \"%s\"" msgstr "kunde inte parsa filnamn \"%s\"" @@ -21215,207 +21222,207 @@ msgstr "kunde inte parsa filnamn \"%s\"" msgid "logical replication table synchronization worker for subscription \"%s\", table \"%s\" has finished" msgstr "logisk replikerings tabellsynkroniseringsarbetare för prenumeration \"%s\", tabell \"%s\" är klar" -#: replication/logical/tablesync.c:641 +#: replication/logical/tablesync.c:667 #, c-format msgid "logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled" msgstr "arbetarprocess för uppspelning av logisk replikering av prenumeration \"%s\" kommer starta om så att two_phase kan slås på" -#: replication/logical/tablesync.c:827 replication/logical/tablesync.c:969 +#: replication/logical/tablesync.c:853 replication/logical/tablesync.c:995 #, c-format msgid "could not fetch table info for table \"%s.%s\" from publisher: %s" msgstr "kunde inte hämta tabellinfo för tabell \"%s.%s\" från publicerare: %s" -#: replication/logical/tablesync.c:834 +#: replication/logical/tablesync.c:860 #, c-format msgid "table \"%s.%s\" not found on publisher" msgstr "tabell \"%s.%s\" hittades inte hos publicerare" -#: replication/logical/tablesync.c:892 +#: replication/logical/tablesync.c:918 #, c-format msgid "could not fetch column list info for table \"%s.%s\" from publisher: %s" msgstr "kunde inte hämta kolumlista för tabell \"%s.%s\" från publicerare: %s" -#: replication/logical/tablesync.c:1071 +#: replication/logical/tablesync.c:1097 #, c-format msgid "could not fetch table WHERE clause info for table \"%s.%s\" from publisher: %s" msgstr "kunde inte hämta tabells WHERE-klausul för tabell \"%s.%s\" från publicerare: %s" -#: replication/logical/tablesync.c:1230 +#: replication/logical/tablesync.c:1256 #, c-format msgid "could not start initial contents copy for table \"%s.%s\": %s" msgstr "kunde inte starta initial innehållskopiering för tabell \"%s.%s\": %s" -#: replication/logical/tablesync.c:1429 +#: replication/logical/tablesync.c:1455 #, c-format msgid "table copy could not start transaction on publisher: %s" msgstr "tabellkopiering kunde inte starta transaktion på publiceraren: %s" -#: replication/logical/tablesync.c:1472 +#: replication/logical/tablesync.c:1498 #, c-format msgid "replication origin \"%s\" already exists" msgstr "replikeringsurspring \"%s\" finns redan" -#: replication/logical/tablesync.c:1505 replication/logical/worker.c:2361 +#: replication/logical/tablesync.c:1531 replication/logical/worker.c:2363 #, c-format msgid "user \"%s\" cannot replicate into relation with row-level security enabled: \"%s\"" msgstr "användaren \"%s\" kan inte replikera in i en relation med radsäkerhet påslagen: \"%s\"" -#: replication/logical/tablesync.c:1518 +#: replication/logical/tablesync.c:1544 #, c-format msgid "table copy could not finish transaction on publisher: %s" msgstr "tabellkopiering kunde inte slutföra transaktion på publiceraren: %s" -#: replication/logical/worker.c:481 +#: replication/logical/worker.c:483 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop" msgstr "logiska replikeringens parallella ändringsapplicerare för prenumeration \"%s\" kommer stoppa" -#: replication/logical/worker.c:483 +#: replication/logical/worker.c:485 #, c-format msgid "Cannot handle streamed replication transactions using parallel apply workers until all tables have been synchronized." msgstr "Kan inte hantera strömmade replikerade transaktioner med parallell ändringsapplicerare innan alla tabeller har synkroniserats." -#: replication/logical/worker.c:852 replication/logical/worker.c:967 +#: replication/logical/worker.c:854 replication/logical/worker.c:969 #, c-format msgid "incorrect binary data format in logical replication column %d" msgstr "inkorrekt binärt dataformat i logisk replikeringskolumn %d" -#: replication/logical/worker.c:2500 +#: replication/logical/worker.c:2506 #, c-format msgid "publisher did not send replica identity column expected by the logical replication target relation \"%s.%s\"" msgstr "publicerare skickade inte identitetskolumn för replika som förväntades av den logiska replikeringens målrelation \"%s.%s\"" -#: replication/logical/worker.c:2507 +#: replication/logical/worker.c:2513 #, c-format msgid "logical replication target relation \"%s.%s\" has neither REPLICA IDENTITY index nor PRIMARY KEY and published relation does not have REPLICA IDENTITY FULL" msgstr "logisk replikeringsmålrelation \"%s.%s\" har varken REPLICA IDENTITY-index eller PRIMARY KEY och den publicerade relationen har inte REPLICA IDENTITY FULL" -#: replication/logical/worker.c:3371 +#: replication/logical/worker.c:3384 #, c-format msgid "invalid logical replication message type \"??? (%d)\"" msgstr "ogiltig logisk replikeringsmeddelandetyp \"??? (%d)\"" -#: replication/logical/worker.c:3543 +#: replication/logical/worker.c:3556 #, c-format msgid "data stream from publisher has ended" msgstr "dataströmmen från publiceraren har avslutats" -#: replication/logical/worker.c:3697 +#: replication/logical/worker.c:3710 #, c-format msgid "terminating logical replication worker due to timeout" msgstr "avslutar logisk replikeringsarbetare på grund av timeout" -#: replication/logical/worker.c:3891 +#: replication/logical/worker.c:3904 #, c-format msgid "logical replication worker for subscription \"%s\" will stop because the subscription was removed" msgstr "logiska replikerings ändringsapplicerare för prenumeration \"%s\" kommer att stoppa då prenumerationen har tagits bort" -#: replication/logical/worker.c:3905 +#: replication/logical/worker.c:3918 #, c-format msgid "logical replication worker for subscription \"%s\" will stop because the subscription was disabled" msgstr "logiska replikerings ändringsapplicerare för prenumeration \"%s\" kommer att stoppa då prenumerationen har stängts av" -#: replication/logical/worker.c:3936 +#: replication/logical/worker.c:3949 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop because of a parameter change" msgstr "logiska replikeringens ändringsapplicerare för prenumeration \"%s\" kommer att stoppa på grund av ändrade parametrar" -#: replication/logical/worker.c:3940 +#: replication/logical/worker.c:3953 #, c-format msgid "logical replication worker for subscription \"%s\" will restart because of a parameter change" msgstr "logiska replikeringens ändringsapplicerare för prenumeration \"%s\" kommer att startas om på grund av ändrade parametrar" -#: replication/logical/worker.c:3954 +#: replication/logical/worker.c:3967 #, c-format msgid "logical replication parallel apply worker for subscription \"%s\" will stop because the subscription owner's superuser privileges have been revoked" msgstr "logiska replikerings parallella ändringsapplicerare för prenumeration \"%s\" kommer att stoppa då prenumerationens ägare har blivit av med superuser-rättigheten" -#: replication/logical/worker.c:3958 +#: replication/logical/worker.c:3971 #, c-format msgid "logical replication worker for subscription \"%s\" will restart because the subscription owner's superuser privileges have been revoked" msgstr "logiska replikerings ändringsapplicerare för prenumeration \"%s\" kommer att startas om då prenumerationens ägare har blivit av med superuser-rättigheten" -#: replication/logical/worker.c:4478 +#: replication/logical/worker.c:4499 #, c-format msgid "subscription has no replication slot set" msgstr "prenumeration har ingen replikeringsslot angiven" -#: replication/logical/worker.c:4591 +#: replication/logical/worker.c:4620 #, c-format msgid "logical replication worker for subscription %u will not start because the subscription was removed during startup" msgstr "logiska replikeringens ändringsapplicerare för prenumeration %u kommer inte att startas då prenumerationen togs bort i uppstarten" -#: replication/logical/worker.c:4607 +#: replication/logical/worker.c:4636 #, c-format msgid "logical replication worker for subscription \"%s\" will not start because the subscription was disabled during startup" msgstr "logiska replikeringens ändringsapplicerare för prenumeration \"%s\" kommer inte att startas då prenumerationen stängdes av i uppstarten" -#: replication/logical/worker.c:4631 +#: replication/logical/worker.c:4660 #, c-format msgid "logical replication table synchronization worker for subscription \"%s\", table \"%s\" has started" msgstr "logisk replikerings tabellsynkroniseringsarbetare för prenumeration \"%s\", tabell \"%s\" har startat" -#: replication/logical/worker.c:4636 +#: replication/logical/worker.c:4665 #, c-format msgid "logical replication apply worker for subscription \"%s\" has started" msgstr "logiska replikeringens ändringsapplicerare för prenumeration \"%s\" har startat" -#: replication/logical/worker.c:4758 +#: replication/logical/worker.c:4795 #, c-format msgid "subscription \"%s\" has been disabled because of an error" msgstr "prenumeration \"%s\" har avaktiverats på grund av ett fel" -#: replication/logical/worker.c:4806 +#: replication/logical/worker.c:4843 #, c-format msgid "logical replication starts skipping transaction at LSN %X/%X" msgstr "logisk replikering börjar hoppa över transaktion vid LSN %X/%X" -#: replication/logical/worker.c:4820 +#: replication/logical/worker.c:4857 #, c-format msgid "logical replication completed skipping transaction at LSN %X/%X" msgstr "logisk replikering har slutfört överhoppande av transaktionen vid LSN %X/%X" -#: replication/logical/worker.c:4902 +#: replication/logical/worker.c:4945 #, c-format msgid "skip-LSN of subscription \"%s\" cleared" msgstr "överhoppnings-LSN för logiska prenumerationen \"%s\" har nollställts" -#: replication/logical/worker.c:4903 +#: replication/logical/worker.c:4946 #, c-format msgid "Remote transaction's finish WAL location (LSN) %X/%X did not match skip-LSN %X/%X." msgstr "Fjärrtransaktionens slut-WAL-position (LSN) %X/%X matchade inte överhoppnings-LSN %X/%X." -#: replication/logical/worker.c:4940 +#: replication/logical/worker.c:4974 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\"" msgstr "processar fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\"" -#: replication/logical/worker.c:4944 +#: replication/logical/worker.c:4978 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u" msgstr "processar fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" i transaktion %u" -#: replication/logical/worker.c:4949 +#: replication/logical/worker.c:4983 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" in transaction %u, finished at %X/%X" msgstr "processande av fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" i transaktion %u blev klar vid %X/%X" -#: replication/logical/worker.c:4960 +#: replication/logical/worker.c:4994 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u" msgstr "processande av fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" för replikeringsmålrelation \"%s.%s\" i transaktion %u" -#: replication/logical/worker.c:4967 +#: replication/logical/worker.c:5001 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" in transaction %u, finished at %X/%X" msgstr "processande av fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" för replikeringsmålrelation \"%s.%s\" i transaktion %u blev klart vid %X/%X" -#: replication/logical/worker.c:4978 +#: replication/logical/worker.c:5012 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u" msgstr "processande av fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" för replikeringsmålrelation \"%s.%s\" kolumn \"%s\" i transaktion %u" -#: replication/logical/worker.c:4986 +#: replication/logical/worker.c:5020 #, c-format msgid "processing remote data for replication origin \"%s\" during message type \"%s\" for replication target relation \"%s.%s\" column \"%s\" in transaction %u, finished at %X/%X" msgstr "processande av fjärrdata för replikeringskälla \"%s\" vid meddelandetyp \"%s\" för replikeringsmålrelation \"%s.%s\" kolumn \"%s\" i transaktion %u blev klart vid %X/%X" @@ -21520,7 +21527,7 @@ msgstr "alla replikeringsslots används" msgid "Free one or increase \"max_replication_slots\"." msgstr "Frigör en eller öka \"max_replication_slots\"." -#: replication/slot.c:560 replication/slot.c:2450 replication/slotfuncs.c:661 +#: replication/slot.c:560 replication/slot.c:2463 replication/slotfuncs.c:661 #: utils/activity/pgstat_replslot.c:56 utils/adt/genfile.c:728 #, c-format msgid "replication slot \"%s\" does not exist" @@ -21571,7 +21578,7 @@ msgstr "kan inte ändra replikeringsslot \"%s\"" msgid "cannot enable failover for a replication slot on the standby" msgstr "kan inte aktivera failover för en replikeringsslot på standby:en" -#: replication/slot.c:969 replication/slot.c:1927 replication/slot.c:2311 +#: replication/slot.c:969 replication/slot.c:1923 replication/slot.c:2307 #, c-format msgid "could not remove directory \"%s\"" msgstr "kunde inte ta bort katalog \"%s\"" @@ -21622,102 +21629,112 @@ msgstr "avslutar process %d för att frigöra replikeringsslot \"%s\"" msgid "invalidating obsolete replication slot \"%s\"" msgstr "invaliderar obsolet replikeringssslot \"%s\"" -#: replication/slot.c:2249 +#: replication/slot.c:2245 #, c-format msgid "replication slot file \"%s\" has wrong magic number: %u instead of %u" msgstr "replikeringsslotfil \"%s\" har fel magiskt nummer: %u istället för %u" -#: replication/slot.c:2256 +#: replication/slot.c:2252 #, c-format msgid "replication slot file \"%s\" has unsupported version %u" msgstr "replikeringsslotfil \"%s\" har en icke stödd version %u" -#: replication/slot.c:2263 +#: replication/slot.c:2259 #, c-format msgid "replication slot file \"%s\" has corrupted length %u" msgstr "replikeringsslotfil \"%s\" har felaktig längd %u" -#: replication/slot.c:2299 +#: replication/slot.c:2295 #, c-format msgid "checksum mismatch for replication slot file \"%s\": is %u, should be %u" msgstr "kontrollsummefel för replikeringsslot-fil \"%s\": är %u, skall vara %u" -#: replication/slot.c:2333 +#: replication/slot.c:2331 #, c-format msgid "logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"" msgstr "logisk replikeringsslot \"%s\" finns men \"wal_level\" < \"replica\"" -#: replication/slot.c:2335 +#: replication/slot.c:2333 #, c-format msgid "Change \"wal_level\" to be \"logical\" or higher." msgstr "Ändra \"wal_level\" till \"logical\" eller högre." -#: replication/slot.c:2339 +#: replication/slot.c:2345 +#, c-format +msgid "logical replication slot \"%s\" exists on the standby, but \"hot_standby\" = \"off\"" +msgstr "logisk replikeringsslot \"%s\" finns på standby men \"hot_standby\" = \"off\"" + +#: replication/slot.c:2347 +#, c-format +msgid "Change \"hot_standby\" to be \"on\"." +msgstr "Ändra \"hot_standby\" till \"on\"." + +#: replication/slot.c:2352 #, c-format msgid "physical replication slot \"%s\" exists, but \"wal_level\" < \"replica\"" msgstr "fysisk replikeringsslot \"%s\" finns men \"wal_level\" < \"replica\"" -#: replication/slot.c:2341 +#: replication/slot.c:2354 #, c-format msgid "Change \"wal_level\" to be \"replica\" or higher." msgstr "Ändra \"wal_level\" till \"replica\" eller högre." -#: replication/slot.c:2383 +#: replication/slot.c:2396 #, c-format msgid "too many replication slots active before shutdown" msgstr "för många aktiva replikeringsslottar innan nerstängning" -#: replication/slot.c:2458 +#: replication/slot.c:2471 #, c-format msgid "\"%s\" is not a physical replication slot" msgstr "\"%s\" är inte en fysisk replikeringsslot" -#: replication/slot.c:2635 +#: replication/slot.c:2650 #, c-format msgid "replication slot \"%s\" specified in parameter \"%s\" does not exist" msgstr "replikeringsslot \"%s\" som angivits i parametern \"%s\" existerar inte" -#: replication/slot.c:2637 replication/slot.c:2671 replication/slot.c:2686 +#: replication/slot.c:2652 replication/slot.c:2686 replication/slot.c:2701 #, c-format msgid "Logical replication is waiting on the standby associated with replication slot \"%s\"." msgstr "Logisk replikering väntar på standby associerad med replikeringsslot \"%s\"." -#: replication/slot.c:2639 +#: replication/slot.c:2654 #, c-format msgid "Create the replication slot \"%s\" or amend parameter \"%s\"." msgstr "Skapa replikeringsslotten \"%s\" eller ändra parametern \"%s\"" -#: replication/slot.c:2649 +#: replication/slot.c:2664 #, c-format msgid "cannot specify logical replication slot \"%s\" in parameter \"%s\"" msgstr "kan inte ange logisk replikeringsslot \"%s\" i parametern \"%s\"" -#: replication/slot.c:2651 +#: replication/slot.c:2666 #, c-format msgid "Logical replication is waiting for correction on replication slot \"%s\"." msgstr "Logiska replikering väntar på rättning av replikeringsslot \"%s\"" -#: replication/slot.c:2653 +#: replication/slot.c:2668 #, c-format msgid "Remove the logical replication slot \"%s\" from parameter \"%s\"." msgstr "Ta bort logisk replikeringsslot \"%s\" från parametern \"%s\"" -#: replication/slot.c:2669 +#: replication/slot.c:2684 #, c-format msgid "physical replication slot \"%s\" specified in parameter \"%s\" has been invalidated" msgstr "fysisk replikeringsslot \"%s\" angiven i parametern \"%s\" har invaliderats" -#: replication/slot.c:2673 +#: replication/slot.c:2688 #, c-format msgid "Drop and recreate the replication slot \"%s\", or amend parameter \"%s\"." msgstr "Släng och återskapa replikeringsslotten \"%s\" eller ändra parametern %s." -#: replication/slot.c:2684 +#: replication/slot.c:2699 #, c-format msgid "replication slot \"%s\" specified in parameter \"%s\" does not have active_pid" msgstr "replikeringsslot \"%s\" angiven i parametern \"%s\" har inte active_pid" -#: replication/slot.c:2688 +#: replication/slot.c:2703 #, c-format msgid "Start the standby associated with the replication slot \"%s\", or amend parameter \"%s\"." msgstr "Starta standby associerad med replikeringsslot \"%s\" eller ändra parametern \"%s\"." @@ -21757,62 +21774,77 @@ msgstr "kan inte kopiera logisk replikeringsslot \"%s\" som en fysisk replikerin msgid "cannot copy a replication slot that doesn't reserve WAL" msgstr "kan inte kopiera en replikeringsslot som inte tidigare har reserverat WAL" -#: replication/slotfuncs.c:768 +#: replication/slotfuncs.c:688 +#, c-format +msgid "cannot copy invalidated replication slot \"%s\"" +msgstr "kan inte kopiera replikeringsslot \"%s\" som blivit ogiltig" + +#: replication/slotfuncs.c:780 #, c-format msgid "could not copy replication slot \"%s\"" msgstr "kunde inte kopiera replikeringsslot \"%s\"" -#: replication/slotfuncs.c:770 +#: replication/slotfuncs.c:782 #, c-format msgid "The source replication slot was modified incompatibly during the copy operation." msgstr "Källreplikeringsslotten ändrades på ett inkompatibelt sätt under copy-operationen." -#: replication/slotfuncs.c:776 +#: replication/slotfuncs.c:788 #, c-format msgid "cannot copy unfinished logical replication slot \"%s\"" msgstr "kan inte kopiera ej slutförd replikeringsslot \"%s\"" -#: replication/slotfuncs.c:778 +#: replication/slotfuncs.c:790 #, c-format msgid "Retry when the source replication slot's confirmed_flush_lsn is valid." msgstr "Försök igen när källreplikeringsslottens confirmed_flush_lsn är giltig." -#: replication/slotfuncs.c:877 +#: replication/slotfuncs.c:802 +#, c-format +msgid "cannot copy replication slot \"%s\"" +msgstr "kan inte kopiera replikeringsslot \"%s\"" + +#: replication/slotfuncs.c:804 +#, c-format +msgid "The source replication slot was invalidated during the copy operation." +msgstr "Källreplikeringsslotten gjordes ogiltig under copy-operationen." + +#: replication/slotfuncs.c:903 #, c-format msgid "replication slots can only be synchronized to a standby server" msgstr "replikerings-slot kan bara synkroniseras till en standby-server" -#: replication/syncrep.c:261 +#: replication/syncrep.c:304 #, c-format msgid "canceling the wait for synchronous replication and terminating connection due to administrator command" msgstr "avbryter väntan på synkron replikering samt avslutar anslutning på grund av ett administratörskommando" -#: replication/syncrep.c:262 replication/syncrep.c:279 +#: replication/syncrep.c:305 replication/syncrep.c:322 #, c-format msgid "The transaction has already committed locally, but might not have been replicated to the standby." msgstr "Transaktionen har redan commit:ats lokalt men har kanske inte replikerats till standby:en." -#: replication/syncrep.c:278 +#: replication/syncrep.c:321 #, c-format msgid "canceling wait for synchronous replication due to user request" msgstr "avbryter väntan på synkron replikering efter användarens önskemål" -#: replication/syncrep.c:485 +#: replication/syncrep.c:528 #, c-format msgid "standby \"%s\" is now a synchronous standby with priority %d" msgstr "standby \"%s\" är nu en synkron standby med prioritet %d" -#: replication/syncrep.c:489 +#: replication/syncrep.c:532 #, c-format msgid "standby \"%s\" is now a candidate for quorum synchronous standby" msgstr "standby \"%s\" är nu en kvorumkandidat för synkron standby" -#: replication/syncrep.c:1013 +#: replication/syncrep.c:1080 #, c-format msgid "\"synchronous_standby_names\" parser failed" msgstr "\"synchronous_standby_names\"-parser misslyckades" -#: replication/syncrep.c:1019 +#: replication/syncrep.c:1086 #, c-format msgid "number of synchronous standbys (%d) must be greater than zero" msgstr "antal synkrona standbys (%d) måste vara fler än noll" @@ -21887,125 +21919,125 @@ msgstr "hämtar tidslinjehistorikfil för tidslinje %u från primära servern" msgid "could not write to WAL segment %s at offset %d, length %lu: %m" msgstr "kunde inte skriva till WAL-segment %s på offset %d, längd %lu: %m" -#: replication/walsender.c:531 +#: replication/walsender.c:535 #, c-format msgid "cannot use %s with a logical replication slot" msgstr "kan inte använda %s med logisk replikeringsslot" -#: replication/walsender.c:635 storage/smgr/md.c:1735 +#: replication/walsender.c:639 storage/smgr/md.c:1735 #, c-format msgid "could not seek to end of file \"%s\": %m" msgstr "kunde inte söka (seek) till slutet av filen \"%s\": %m" -#: replication/walsender.c:639 +#: replication/walsender.c:643 #, c-format msgid "could not seek to beginning of file \"%s\": %m" msgstr "kunde inte söka till början av filen \"%s\": %m" -#: replication/walsender.c:853 +#: replication/walsender.c:857 #, c-format msgid "cannot use a logical replication slot for physical replication" msgstr "kan inte använda logisk replikeringsslot för fysisk replikering" -#: replication/walsender.c:919 +#: replication/walsender.c:923 #, c-format msgid "requested starting point %X/%X on timeline %u is not in this server's history" msgstr "efterfrågad startpunkt %X/%X på tidslinje %u finns inte i denna servers historik" -#: replication/walsender.c:922 +#: replication/walsender.c:926 #, c-format msgid "This server's history forked from timeline %u at %X/%X." msgstr "Denna servers historik delade sig från tidslinje %u vid %X/%X." -#: replication/walsender.c:966 +#: replication/walsender.c:970 #, c-format msgid "requested starting point %X/%X is ahead of the WAL flush position of this server %X/%X" msgstr "efterfrågad startpunkt %X/%X är längre fram än denna servers flush:ade WAL-skrivposition %X/%X" -#: replication/walsender.c:1160 +#: replication/walsender.c:1164 #, c-format msgid "unrecognized value for CREATE_REPLICATION_SLOT option \"%s\": \"%s\"" msgstr "okänt värde för CREATE_REPLICATION_SLOT-flagga \"%s\": \"%s\"" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1266 +#: replication/walsender.c:1270 #, c-format msgid "%s must not be called inside a transaction" msgstr "%s får inte anropas i en transaktion" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1276 +#: replication/walsender.c:1280 #, c-format msgid "%s must be called inside a transaction" msgstr "%s måste anropas i en transaktion" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1282 +#: replication/walsender.c:1286 #, c-format msgid "%s must be called in REPEATABLE READ isolation mode transaction" msgstr "%s måste anropas i transaktions REPEATABLE READ-isolationsläge" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1287 +#: replication/walsender.c:1291 #, c-format msgid "%s must be called in a read-only transaction" msgstr "%s måste anropas i en read-only-transaktion" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1293 +#: replication/walsender.c:1297 #, c-format msgid "%s must be called before any query" msgstr "%s måste anropas innan någon fråga" #. translator: %s is a CREATE_REPLICATION_SLOT statement -#: replication/walsender.c:1299 +#: replication/walsender.c:1303 #, c-format msgid "%s must not be called in a subtransaction" msgstr "%s får inte anropas i en undertransaktion" -#: replication/walsender.c:1472 +#: replication/walsender.c:1476 #, c-format msgid "terminating walsender process after promotion" msgstr "stänger ner walsender-process efter befordran" -#: replication/walsender.c:2000 +#: replication/walsender.c:2015 #, c-format msgid "cannot execute new commands while WAL sender is in stopping mode" msgstr "kan inte utföra nya kommandon när WAL-sändare är i stopp-läge" -#: replication/walsender.c:2035 +#: replication/walsender.c:2050 #, c-format msgid "cannot execute SQL commands in WAL sender for physical replication" msgstr "kan inte köra SQL-kommandon i WAL-sändare för fysisk replikering" -#: replication/walsender.c:2068 +#: replication/walsender.c:2083 #, c-format msgid "received replication command: %s" msgstr "tog emot replikeringskommando: %s" -#: replication/walsender.c:2076 tcop/fastpath.c:209 tcop/postgres.c:1142 +#: replication/walsender.c:2091 tcop/fastpath.c:209 tcop/postgres.c:1142 #: tcop/postgres.c:1500 tcop/postgres.c:1752 tcop/postgres.c:2234 #: tcop/postgres.c:2672 tcop/postgres.c:2749 #, c-format msgid "current transaction is aborted, commands ignored until end of transaction block" msgstr "aktuella transaktionen har avbrutits, alla kommandon ignoreras tills slutet på transaktionen" -#: replication/walsender.c:2233 replication/walsender.c:2268 +#: replication/walsender.c:2248 replication/walsender.c:2283 #, c-format msgid "unexpected EOF on standby connection" msgstr "oväntat EOF från standby-anslutning" -#: replication/walsender.c:2256 +#: replication/walsender.c:2271 #, c-format msgid "invalid standby message type \"%c\"" msgstr "ogiltigt standby-meddelandetyp \"%c\"" -#: replication/walsender.c:2345 +#: replication/walsender.c:2360 #, c-format msgid "unexpected message type \"%c\"" msgstr "oväntad meddelandetyp \"%c\"" -#: replication/walsender.c:2759 +#: replication/walsender.c:2778 #, c-format msgid "terminating walsender process due to replication timeout" msgstr "avslutar walsender-process på grund av replikerings-timeout" @@ -22651,17 +22683,17 @@ msgstr "oväntad data efter EOF i block %u för relation %s" msgid "This has been seen to occur with buggy kernels; consider updating your system." msgstr "Detta beteende har observerats med buggiga kärnor; fundera på att uppdatera ditt system." -#: storage/buffer/bufmgr.c:5653 +#: storage/buffer/bufmgr.c:5654 #, c-format msgid "could not write block %u of %s" msgstr "kunde inte skriva block %u av %s" -#: storage/buffer/bufmgr.c:5655 +#: storage/buffer/bufmgr.c:5656 #, c-format msgid "Multiple failures --- write error might be permanent." msgstr "Multipla fel --- skrivfelet kan vara permanent." -#: storage/buffer/bufmgr.c:5677 storage/buffer/bufmgr.c:5697 +#: storage/buffer/bufmgr.c:5678 storage/buffer/bufmgr.c:5698 #, c-format msgid "writing block %u of relation %s" msgstr "skriver block %u i relation %s" @@ -22711,139 +22743,139 @@ msgstr "kunde inte radera filmängd \"%s\": %m" msgid "could not truncate file \"%s\": %m" msgstr "kunde inte trunkera fil \"%s\": %m" -#: storage/file/fd.c:583 storage/file/fd.c:655 storage/file/fd.c:691 +#: storage/file/fd.c:580 storage/file/fd.c:652 storage/file/fd.c:688 #, c-format msgid "could not flush dirty data: %m" msgstr "kunde inte flush:a smutsig data: %m" -#: storage/file/fd.c:613 +#: storage/file/fd.c:610 #, c-format msgid "could not determine dirty data size: %m" msgstr "kunde inte lista ut storlek på smutsig data: %m" -#: storage/file/fd.c:665 +#: storage/file/fd.c:662 #, c-format msgid "could not munmap() while flushing data: %m" msgstr "kunde inte göra munmap() vid flush:ning av data: %m" -#: storage/file/fd.c:983 +#: storage/file/fd.c:980 #, c-format msgid "getrlimit failed: %m" msgstr "getrlimit misslyckades: %m" -#: storage/file/fd.c:1073 +#: storage/file/fd.c:1070 #, c-format msgid "insufficient file descriptors available to start server process" msgstr "otillräckligt antal fildeskriptorer tillgängligt för att starta serverprocessen" -#: storage/file/fd.c:1074 +#: storage/file/fd.c:1071 #, c-format msgid "System allows %d, server needs at least %d." msgstr "Systemet tillåter %d, servern behöver minst %d." -#: storage/file/fd.c:1162 storage/file/fd.c:2618 storage/file/fd.c:2727 -#: storage/file/fd.c:2878 +#: storage/file/fd.c:1159 storage/file/fd.c:2615 storage/file/fd.c:2724 +#: storage/file/fd.c:2875 #, c-format msgid "out of file descriptors: %m; release and retry" msgstr "slut på fildeskriptorer: %m; frigör och försök igen" -#: storage/file/fd.c:1536 +#: storage/file/fd.c:1533 #, c-format msgid "temporary file: path \"%s\", size %lu" msgstr "temporär fil: sökväg \"%s\", storlek %lu" -#: storage/file/fd.c:1675 +#: storage/file/fd.c:1672 #, c-format msgid "cannot create temporary directory \"%s\": %m" msgstr "kunde inte skapa temporär katalog \"%s\": %m" -#: storage/file/fd.c:1682 +#: storage/file/fd.c:1679 #, c-format msgid "cannot create temporary subdirectory \"%s\": %m" msgstr "kunde inte skapa temporär underkatalog \"%s\": %m" -#: storage/file/fd.c:1879 +#: storage/file/fd.c:1876 #, c-format msgid "could not create temporary file \"%s\": %m" msgstr "kan inte skapa temporär fil \"%s\": %m" -#: storage/file/fd.c:1915 +#: storage/file/fd.c:1912 #, c-format msgid "could not open temporary file \"%s\": %m" msgstr "kunde inte öppna temporär fil \"%s\": %m" # unlink refererar till unix-funktionen unlink() så den översätter vi inte -#: storage/file/fd.c:1956 +#: storage/file/fd.c:1953 #, c-format msgid "could not unlink temporary file \"%s\": %m" msgstr "kunde inte unlink:a temporär fil \"%s\": %m" -#: storage/file/fd.c:2044 +#: storage/file/fd.c:2041 #, c-format msgid "could not delete file \"%s\": %m" msgstr "kunde inte radera fil \"%s\": %m" -#: storage/file/fd.c:2234 +#: storage/file/fd.c:2231 #, c-format msgid "temporary file size exceeds temp_file_limit (%dkB)" msgstr "storlek på temporär fil överskrider temp_file_limit (%dkB)" -#: storage/file/fd.c:2594 storage/file/fd.c:2653 +#: storage/file/fd.c:2591 storage/file/fd.c:2650 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"" msgstr "överskred maxAllocatedDescs (%d) vid försök att öppna fil \"%s\"" -#: storage/file/fd.c:2698 +#: storage/file/fd.c:2695 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to execute command \"%s\"" msgstr "överskred maxAllocatedDescs (%d) vid försök att köra kommando \"%s\"" -#: storage/file/fd.c:2854 +#: storage/file/fd.c:2851 #, c-format msgid "exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"" msgstr "överskred maxAllocatedDescs (%d) vid försök att öppna katalog \"%s\"" -#: storage/file/fd.c:3384 +#: storage/file/fd.c:3381 #, c-format msgid "unexpected file found in temporary-files directory: \"%s\"" msgstr "oväntad fil hittades i katalogen för temporära filer: \"%s\"" -#: storage/file/fd.c:3502 +#: storage/file/fd.c:3499 #, c-format msgid "syncing data directory (syncfs), elapsed time: %ld.%02d s, current path: %s" msgstr "synkroniserar datakatalog (syncfs), förbrukad tid: %ld.%02d s, aktuell sökväg: %s" -#: storage/file/fd.c:3729 +#: storage/file/fd.c:3726 #, c-format msgid "syncing data directory (pre-fsync), elapsed time: %ld.%02d s, current path: %s" msgstr "synkroniserar datakatalog (pre-fsync), förbrukad tid: %ld.%02d s, aktuell sökväg: %s" -#: storage/file/fd.c:3761 +#: storage/file/fd.c:3758 #, c-format msgid "syncing data directory (fsync), elapsed time: %ld.%02d s, current path: %s" msgstr "synkroniserar datakatalog (fsync), förbrukad tid: %ld.%02d s, aktuell sökväg: %s" -#: storage/file/fd.c:3950 +#: storage/file/fd.c:3947 #, c-format msgid "\"debug_io_direct\" is not supported on this platform." msgstr "\"debug_io_direct\" stöds inte på denna plattform." -#: storage/file/fd.c:3964 +#: storage/file/fd.c:3961 #, c-format msgid "Invalid list syntax in parameter \"%s\"" msgstr "Ogiltigt listsyntax för parameter \"%s\"" -#: storage/file/fd.c:3984 +#: storage/file/fd.c:3981 #, c-format msgid "Invalid option \"%s\"" msgstr "Ogiltig flagga \"%s\"" -#: storage/file/fd.c:3997 +#: storage/file/fd.c:3994 #, c-format msgid "\"debug_io_direct\" is not supported for WAL because XLOG_BLCKSZ is too small" msgstr "\"debug_io_direct\" stöds inte för WAL då XLOG_BLCKSZ är för liten" -#: storage/file/fd.c:4004 +#: storage/file/fd.c:4001 #, c-format msgid "\"debug_io_direct\" is not supported for data because BLCKSZ is too small" msgstr "\"debug_io_direct\" stöds inte för data då BLCKSZ är för liten" @@ -23000,7 +23032,7 @@ msgstr "ogiltig meddelandestorlek %zu i kö i delat minne" #: storage/lmgr/lock.c:4285 storage/lmgr/lock.c:4635 #: storage/lmgr/predicate.c:2468 storage/lmgr/predicate.c:2483 #: storage/lmgr/predicate.c:3880 storage/lmgr/predicate.c:4927 -#: utils/hash/dynahash.c:1095 +#: utils/hash/dynahash.c:1096 #, c-format msgid "out of shared memory" msgstr "slut på delat minne" @@ -23177,107 +23209,107 @@ msgstr "deadlock upptäckt" msgid "See server log for query details." msgstr "Se server-logg för frågedetaljer." -#: storage/lmgr/lmgr.c:848 +#: storage/lmgr/lmgr.c:854 #, c-format msgid "while updating tuple (%u,%u) in relation \"%s\"" msgstr "vid uppdatering av tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:851 +#: storage/lmgr/lmgr.c:857 #, c-format msgid "while deleting tuple (%u,%u) in relation \"%s\"" msgstr "vid borttagning av tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:854 +#: storage/lmgr/lmgr.c:860 #, c-format msgid "while locking tuple (%u,%u) in relation \"%s\"" msgstr "vid låsning av tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:857 +#: storage/lmgr/lmgr.c:863 #, c-format msgid "while locking updated version (%u,%u) of tuple in relation \"%s\"" msgstr "vid låsning av uppdaterad version (%u,%u) av tupel i relation \"%s\"" -#: storage/lmgr/lmgr.c:860 +#: storage/lmgr/lmgr.c:866 #, c-format msgid "while inserting index tuple (%u,%u) in relation \"%s\"" msgstr "vid insättning av indextupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:863 +#: storage/lmgr/lmgr.c:869 #, c-format msgid "while checking uniqueness of tuple (%u,%u) in relation \"%s\"" msgstr "vid kontroll av unikhet av tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:866 +#: storage/lmgr/lmgr.c:872 #, c-format msgid "while rechecking updated tuple (%u,%u) in relation \"%s\"" msgstr "vid återkontroll av uppdaterad tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:869 +#: storage/lmgr/lmgr.c:875 #, c-format msgid "while checking exclusion constraint on tuple (%u,%u) in relation \"%s\"" msgstr "vid kontroll av uteslutningsvillkor av tupel (%u,%u) i relation \"%s\"" -#: storage/lmgr/lmgr.c:1239 +#: storage/lmgr/lmgr.c:1245 #, c-format msgid "relation %u of database %u" msgstr "relation %u i databasen %u" -#: storage/lmgr/lmgr.c:1245 +#: storage/lmgr/lmgr.c:1251 #, c-format msgid "extension of relation %u of database %u" msgstr "utökning av relation %u i databas %u" -#: storage/lmgr/lmgr.c:1251 +#: storage/lmgr/lmgr.c:1257 #, c-format msgid "pg_database.datfrozenxid of database %u" msgstr "pg_database.datfrozenxid för databas %u" -#: storage/lmgr/lmgr.c:1256 +#: storage/lmgr/lmgr.c:1262 #, c-format msgid "page %u of relation %u of database %u" msgstr "sida %u i relation %u i databas %u" -#: storage/lmgr/lmgr.c:1263 +#: storage/lmgr/lmgr.c:1269 #, c-format msgid "tuple (%u,%u) of relation %u of database %u" msgstr "tuple (%u,%u) i relation %u i databas %u" -#: storage/lmgr/lmgr.c:1271 +#: storage/lmgr/lmgr.c:1277 #, c-format msgid "transaction %u" msgstr "transaktion %u" -#: storage/lmgr/lmgr.c:1276 +#: storage/lmgr/lmgr.c:1282 #, c-format msgid "virtual transaction %d/%u" msgstr "vituell transaktion %d/%u" -#: storage/lmgr/lmgr.c:1282 +#: storage/lmgr/lmgr.c:1288 #, c-format msgid "speculative token %u of transaction %u" msgstr "spekulativ token %u för transaktion %u" -#: storage/lmgr/lmgr.c:1288 +#: storage/lmgr/lmgr.c:1294 #, c-format msgid "object %u of class %u of database %u" msgstr "objekt %u av klass %u i databas %u" -#: storage/lmgr/lmgr.c:1296 +#: storage/lmgr/lmgr.c:1302 #, c-format msgid "user lock [%u,%u,%u]" msgstr "användarlås [%u,%u,%u]" -#: storage/lmgr/lmgr.c:1303 +#: storage/lmgr/lmgr.c:1309 #, c-format msgid "advisory lock [%u,%u,%u,%u]" msgstr "rådgivande lås [%u,%u,%u,%u]" -#: storage/lmgr/lmgr.c:1311 +#: storage/lmgr/lmgr.c:1317 #, c-format msgid "remote transaction %u of subscription %u of database %u" msgstr "fjärrtransaktion %u för prenumeration %u i databas %u" -#: storage/lmgr/lmgr.c:1318 +#: storage/lmgr/lmgr.c:1324 #, c-format msgid "unrecognized locktag type %d" msgstr "okänd låsetikettyp %d" @@ -23978,12 +24010,12 @@ msgstr "nedkoppling: sessionstid: %d:%02d:%02d.%03d användare=%s databas=%s vä msgid "bind message has %d result formats but query has %d columns" msgstr "bind-meddelande har %d resultatformat men frågan har %d kolumner" -#: tcop/pquery.c:942 tcop/pquery.c:1696 +#: tcop/pquery.c:942 tcop/pquery.c:1687 #, c-format msgid "cursor can only scan forward" msgstr "markör kan bara hoppa framåt" -#: tcop/pquery.c:943 tcop/pquery.c:1697 +#: tcop/pquery.c:943 tcop/pquery.c:1688 #, c-format msgid "Declare it with SCROLL option to enable backward scan." msgstr "Deklarera den med flaggan SCROLL för att kunna traversera bakåt." @@ -24149,64 +24181,64 @@ msgstr "okänd synonymordboksparameter: \"%s\"" msgid "missing Dictionary parameter" msgstr "saknar ordlistparameter" -#: tsearch/spell.c:381 tsearch/spell.c:398 tsearch/spell.c:407 -#: tsearch/spell.c:1043 +#: tsearch/spell.c:382 tsearch/spell.c:399 tsearch/spell.c:408 +#: tsearch/spell.c:1045 #, c-format msgid "invalid affix flag \"%s\"" msgstr "ogiltig affix-flagga \"%s\"" -#: tsearch/spell.c:385 tsearch/spell.c:1047 +#: tsearch/spell.c:386 tsearch/spell.c:1049 #, c-format msgid "affix flag \"%s\" is out of range" msgstr "affix-flaggan \"%s\" är utanför giltigt intervall" -#: tsearch/spell.c:415 +#: tsearch/spell.c:416 #, c-format msgid "invalid character in affix flag \"%s\"" msgstr "ogiltigt tecken i affix-flagga \"%s\"" -#: tsearch/spell.c:435 +#: tsearch/spell.c:436 #, c-format msgid "invalid affix flag \"%s\" with \"long\" flag value" msgstr "ogiltig affix-flagga \"%s\" med flaggvärdet \"long\"" -#: tsearch/spell.c:525 +#: tsearch/spell.c:526 #, c-format msgid "could not open dictionary file \"%s\": %m" msgstr "kunde inte öppna ordboksfil \"%s\": %m" -#: tsearch/spell.c:1170 tsearch/spell.c:1182 tsearch/spell.c:1742 -#: tsearch/spell.c:1747 tsearch/spell.c:1752 +#: tsearch/spell.c:1173 tsearch/spell.c:1185 tsearch/spell.c:1746 +#: tsearch/spell.c:1751 tsearch/spell.c:1756 #, c-format msgid "invalid affix alias \"%s\"" msgstr "ogiltigt affix-alias \"%s\"" -#: tsearch/spell.c:1223 tsearch/spell.c:1294 tsearch/spell.c:1443 +#: tsearch/spell.c:1226 tsearch/spell.c:1297 tsearch/spell.c:1446 #, c-format msgid "could not open affix file \"%s\": %m" msgstr "kunde inte öppna affix-fil \"%s\": %m" -#: tsearch/spell.c:1277 +#: tsearch/spell.c:1280 #, c-format msgid "Ispell dictionary supports only \"default\", \"long\", and \"num\" flag values" msgstr "Ispell-ordbok stöder bara flaggorna \"default\", \"long\" och \"num\"" -#: tsearch/spell.c:1321 +#: tsearch/spell.c:1324 #, c-format msgid "invalid number of flag vector aliases" msgstr "ogiltigt antal alias i flaggvektor" -#: tsearch/spell.c:1344 +#: tsearch/spell.c:1347 #, c-format msgid "number of aliases exceeds specified number %d" msgstr "antalet alias överskriver angivet antal %d" -#: tsearch/spell.c:1559 +#: tsearch/spell.c:1562 #, c-format msgid "affix file contains both old-style and new-style commands" msgstr "affix-fil innehåller kommandon på gammalt och nytt format" -#: tsearch/to_tsany.c:194 utils/adt/tsvector.c:277 utils/adt/tsvector_op.c:1126 +#: tsearch/to_tsany.c:194 utils/adt/tsvector.c:274 utils/adt/tsvector_op.c:1126 #, c-format msgid "string is too long for tsvector (%d bytes, max %d bytes)" msgstr "strängen är för lång för tsvector (%d byte, max %d byte)" @@ -24273,37 +24305,37 @@ msgstr "%s måste vara >= 0" msgid "could not unlink permanent statistics file \"%s\": %m" msgstr "kunde inte radera permanent statistikfil \"%s\": %m" -#: utils/activity/pgstat.c:1255 +#: utils/activity/pgstat.c:1254 #, c-format msgid "invalid statistics kind: \"%s\"" msgstr "ogiltig statistiktyp \"%s\"" -#: utils/activity/pgstat.c:1335 +#: utils/activity/pgstat.c:1334 #, c-format msgid "could not open temporary statistics file \"%s\": %m" msgstr "kunde inte öppna temporär statistikfil \"%s\": %m" -#: utils/activity/pgstat.c:1455 +#: utils/activity/pgstat.c:1454 #, c-format msgid "could not write temporary statistics file \"%s\": %m" msgstr "kunde inte skriva temporär statistikfil \"%s\": %m" -#: utils/activity/pgstat.c:1464 +#: utils/activity/pgstat.c:1463 #, c-format msgid "could not close temporary statistics file \"%s\": %m" msgstr "kunde inte stänga temporär statistikfil \"%s\": %m" -#: utils/activity/pgstat.c:1472 +#: utils/activity/pgstat.c:1471 #, c-format msgid "could not rename temporary statistics file \"%s\" to \"%s\": %m" msgstr "kunde inte döpa om temporär statistikfil \"%s\" till \"%s\": %m" -#: utils/activity/pgstat.c:1521 +#: utils/activity/pgstat.c:1520 #, c-format msgid "could not open statistics file \"%s\": %m" msgstr "kunde inte öppna statistikfil \"%s\": %m" -#: utils/activity/pgstat.c:1683 +#: utils/activity/pgstat.c:1682 #, c-format msgid "corrupted statistics file \"%s\"" msgstr "korrupt statistikfil \"%s\"" @@ -24328,102 +24360,102 @@ msgstr "wait event \"%s\" finns redan i type \"%s\"" msgid "too many custom wait events" msgstr "för många egendefinierade wait event" -#: utils/adt/acl.c:183 utils/adt/name.c:93 +#: utils/adt/acl.c:200 utils/adt/name.c:93 #, c-format msgid "identifier too long" msgstr "identifieraren för lång" -#: utils/adt/acl.c:184 utils/adt/name.c:94 +#: utils/adt/acl.c:201 utils/adt/name.c:94 #, c-format msgid "Identifier must be less than %d characters." msgstr "Identifierare måste vara mindre än %d tecken." -#: utils/adt/acl.c:272 +#: utils/adt/acl.c:289 #, c-format msgid "unrecognized key word: \"%s\"" msgstr "okänt nyckelord: \"%s\"" -#: utils/adt/acl.c:273 +#: utils/adt/acl.c:290 #, c-format msgid "ACL key word must be \"group\" or \"user\"." msgstr "ACL-nyckelord måste vara \"group\" eller \"user\"." -#: utils/adt/acl.c:281 +#: utils/adt/acl.c:298 #, c-format msgid "missing name" msgstr "namn saknas" -#: utils/adt/acl.c:282 +#: utils/adt/acl.c:299 #, c-format msgid "A name must follow the \"group\" or \"user\" key word." msgstr "Ett namn måste följa efter nyckelorden \"group\" resp. \"user\"." -#: utils/adt/acl.c:288 +#: utils/adt/acl.c:305 #, c-format msgid "missing \"=\" sign" msgstr "saknar \"=\"-tecken" -#: utils/adt/acl.c:350 +#: utils/adt/acl.c:367 #, c-format msgid "invalid mode character: must be one of \"%s\"" msgstr "ogiltigt lägestecken: måste vara en av \"%s\"" -#: utils/adt/acl.c:380 +#: utils/adt/acl.c:397 #, c-format msgid "a name must follow the \"/\" sign" msgstr "ett namn måste följa på tecknet \"/\"" -#: utils/adt/acl.c:392 +#: utils/adt/acl.c:409 #, c-format msgid "defaulting grantor to user ID %u" msgstr "sätter fullmaktsgivaranvändar-ID till standardvärdet %u" -#: utils/adt/acl.c:578 +#: utils/adt/acl.c:595 #, c-format msgid "ACL array contains wrong data type" msgstr "ACL-array innehåller fel datatyp" -#: utils/adt/acl.c:582 +#: utils/adt/acl.c:599 #, c-format msgid "ACL arrays must be one-dimensional" msgstr "ACL-array:er måste vara endimensionella" -#: utils/adt/acl.c:586 +#: utils/adt/acl.c:603 #, c-format msgid "ACL arrays must not contain null values" msgstr "ACL-array:er får inte innehålla null-värden" -#: utils/adt/acl.c:615 +#: utils/adt/acl.c:632 #, c-format msgid "extra garbage at the end of the ACL specification" msgstr "skräp vid slutet av ACL-angivelse" -#: utils/adt/acl.c:1263 +#: utils/adt/acl.c:1280 #, c-format msgid "grant options cannot be granted back to your own grantor" msgstr "fullmaksgivarflaggor kan inte ges tillbaka till den som givit det till dig" -#: utils/adt/acl.c:1579 +#: utils/adt/acl.c:1596 #, c-format msgid "aclinsert is no longer supported" msgstr "aclinsert stöds inte länge" -#: utils/adt/acl.c:1589 +#: utils/adt/acl.c:1606 #, c-format msgid "aclremove is no longer supported" msgstr "aclremove stöds inte längre" -#: utils/adt/acl.c:1709 +#: utils/adt/acl.c:1726 #, c-format msgid "unrecognized privilege type: \"%s\"" msgstr "okänd privilegietyp: \"%s\"" -#: utils/adt/acl.c:3550 utils/adt/regproc.c:100 utils/adt/regproc.c:265 +#: utils/adt/acl.c:3567 utils/adt/regproc.c:100 utils/adt/regproc.c:265 #, c-format msgid "function \"%s\" does not exist" msgstr "funktionen \"%s\" finns inte" -#: utils/adt/acl.c:5196 +#: utils/adt/acl.c:5213 #, c-format msgid "must be able to SET ROLE \"%s\"" msgstr "måste kunna utföra SET ROLE \"%s\"" @@ -24449,7 +24481,7 @@ msgstr "indatatyp är inte en array" #: utils/adt/int.c:1025 utils/adt/int.c:1058 utils/adt/int.c:1072 #: utils/adt/int.c:1086 utils/adt/int.c:1117 utils/adt/int.c:1199 #: utils/adt/int.c:1263 utils/adt/int.c:1331 utils/adt/int.c:1337 -#: utils/adt/int8.c:1256 utils/adt/numeric.c:1917 utils/adt/numeric.c:4454 +#: utils/adt/int8.c:1256 utils/adt/numeric.c:1918 utils/adt/numeric.c:4455 #: utils/adt/rangetypes.c:1488 utils/adt/rangetypes.c:1501 #: utils/adt/varbit.c:1195 utils/adt/varbit.c:1596 utils/adt/varlena.c:1135 #: utils/adt/varlena.c:3137 @@ -24608,7 +24640,7 @@ msgid "Unexpected end of input." msgstr "oväntat slut på indata." #: utils/adt/arrayfuncs.c:1301 utils/adt/arrayfuncs.c:3499 -#: utils/adt/arrayfuncs.c:6106 +#: utils/adt/arrayfuncs.c:6108 #, c-format msgid "invalid number of dimensions: %d" msgstr "felaktigt antal dimensioner: %d" @@ -24647,8 +24679,8 @@ msgstr "slice av fixlängd-array är inte implementerat" #: utils/adt/arrayfuncs.c:2245 utils/adt/arrayfuncs.c:2267 #: utils/adt/arrayfuncs.c:2316 utils/adt/arrayfuncs.c:2570 -#: utils/adt/arrayfuncs.c:2915 utils/adt/arrayfuncs.c:6092 -#: utils/adt/arrayfuncs.c:6118 utils/adt/arrayfuncs.c:6129 +#: utils/adt/arrayfuncs.c:2915 utils/adt/arrayfuncs.c:6094 +#: utils/adt/arrayfuncs.c:6120 utils/adt/arrayfuncs.c:6131 #: utils/adt/json.c:1433 utils/adt/json.c:1505 utils/adt/jsonb.c:1317 #: utils/adt/jsonb.c:1401 utils/adt/jsonfuncs.c:4710 utils/adt/jsonfuncs.c:4863 #: utils/adt/jsonfuncs.c:4974 utils/adt/jsonfuncs.c:5022 @@ -24725,42 +24757,42 @@ msgstr "kan inte ackumulera null-array:er" msgid "cannot accumulate empty arrays" msgstr "kan inte ackumulera tomma array:er" -#: utils/adt/arrayfuncs.c:5990 utils/adt/arrayfuncs.c:6030 +#: utils/adt/arrayfuncs.c:5992 utils/adt/arrayfuncs.c:6032 #, c-format msgid "dimension array or low bound array cannot be null" msgstr "dimensionsarray eller undre gränsarray kan inte vara null" -#: utils/adt/arrayfuncs.c:6093 utils/adt/arrayfuncs.c:6119 +#: utils/adt/arrayfuncs.c:6095 utils/adt/arrayfuncs.c:6121 #, c-format msgid "Dimension array must be one dimensional." msgstr "Dimensionsarray måste vara endimensionell." -#: utils/adt/arrayfuncs.c:6098 utils/adt/arrayfuncs.c:6124 +#: utils/adt/arrayfuncs.c:6100 utils/adt/arrayfuncs.c:6126 #, c-format msgid "dimension values cannot be null" msgstr "dimensionsvärden kan inte vara null" -#: utils/adt/arrayfuncs.c:6130 +#: utils/adt/arrayfuncs.c:6132 #, c-format msgid "Low bound array has different size than dimensions array." msgstr "Undre arraygräns har annan storlek än dimensionsarray." -#: utils/adt/arrayfuncs.c:6411 +#: utils/adt/arrayfuncs.c:6413 #, c-format msgid "removing elements from multidimensional arrays is not supported" msgstr "borttagning av element från en multidimensionell array stöds inte" -#: utils/adt/arrayfuncs.c:6688 +#: utils/adt/arrayfuncs.c:6690 #, c-format msgid "thresholds must be one-dimensional array" msgstr "gränsvärden måste vara en endimensionell array" -#: utils/adt/arrayfuncs.c:6693 +#: utils/adt/arrayfuncs.c:6695 #, c-format msgid "thresholds array must not contain NULLs" msgstr "gränsvärdesarray får inte innehålla NULLL-värden" -#: utils/adt/arrayfuncs.c:6926 +#: utils/adt/arrayfuncs.c:6928 #, c-format msgid "number of elements to trim must be between 0 and %d" msgstr "antal element att trimma måste vara mellan 0 och %d" @@ -24809,8 +24841,8 @@ msgstr "kodningskonvertering från %s till ASCII stöds inte" #: utils/adt/geo_ops.c:3428 utils/adt/geo_ops.c:4650 utils/adt/geo_ops.c:4665 #: utils/adt/geo_ops.c:4672 utils/adt/int.c:174 utils/adt/int.c:186 #: utils/adt/jsonpath.c:185 utils/adt/mac.c:94 utils/adt/mac8.c:226 -#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7221 -#: utils/adt/numeric.c:7424 utils/adt/numeric.c:8371 utils/adt/numutils.c:356 +#: utils/adt/network.c:99 utils/adt/numeric.c:803 utils/adt/numeric.c:7222 +#: utils/adt/numeric.c:7425 utils/adt/numeric.c:8372 utils/adt/numutils.c:356 #: utils/adt/numutils.c:618 utils/adt/numutils.c:880 utils/adt/numutils.c:919 #: utils/adt/numutils.c:941 utils/adt/numutils.c:1005 utils/adt/numutils.c:1027 #: utils/adt/pg_lsn.c:73 utils/adt/tid.c:72 utils/adt/tid.c:80 @@ -24831,10 +24863,10 @@ msgstr "money utanför giltigt intervall" #: utils/adt/int.c:1101 utils/adt/int.c:1139 utils/adt/int.c:1167 #: utils/adt/int8.c:514 utils/adt/int8.c:572 utils/adt/int8.c:942 #: utils/adt/int8.c:1022 utils/adt/int8.c:1084 utils/adt/int8.c:1164 -#: utils/adt/numeric.c:3191 utils/adt/numeric.c:3214 utils/adt/numeric.c:3299 -#: utils/adt/numeric.c:3317 utils/adt/numeric.c:3413 utils/adt/numeric.c:8920 -#: utils/adt/numeric.c:9233 utils/adt/numeric.c:9581 utils/adt/numeric.c:9697 -#: utils/adt/numeric.c:11208 utils/adt/timestamp.c:3713 +#: utils/adt/numeric.c:3192 utils/adt/numeric.c:3215 utils/adt/numeric.c:3300 +#: utils/adt/numeric.c:3318 utils/adt/numeric.c:3414 utils/adt/numeric.c:8921 +#: utils/adt/numeric.c:9234 utils/adt/numeric.c:9582 utils/adt/numeric.c:9698 +#: utils/adt/numeric.c:11209 utils/adt/timestamp.c:3713 #, c-format msgid "division by zero" msgstr "division med noll" @@ -24881,7 +24913,7 @@ msgid "date out of range: \"%s\"" msgstr "datum utanför giltigt intervall \"%s\"" #: utils/adt/date.c:222 utils/adt/date.c:520 utils/adt/date.c:544 -#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2552 +#: utils/adt/rangetypes.c:1584 utils/adt/rangetypes.c:1599 utils/adt/xml.c:2554 #, c-format msgid "date out of range" msgstr "datum utanför giltigt intervall" @@ -24955,8 +24987,8 @@ msgstr "enheten \"%s\" känns inte igen för typen %s" #: utils/adt/timestamp.c:6260 utils/adt/timestamp.c:6347 #: utils/adt/timestamp.c:6388 utils/adt/timestamp.c:6392 #: utils/adt/timestamp.c:6446 utils/adt/timestamp.c:6450 -#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2574 -#: utils/adt/xml.c:2581 utils/adt/xml.c:2601 utils/adt/xml.c:2608 +#: utils/adt/timestamp.c:6456 utils/adt/timestamp.c:6497 utils/adt/xml.c:2576 +#: utils/adt/xml.c:2583 utils/adt/xml.c:2603 utils/adt/xml.c:2610 #, c-format msgid "timestamp out of range" msgstr "timestamp utanför giltigt intervall" @@ -24988,7 +25020,7 @@ msgstr "kan inte subtrahera oändligt intervall från time" #: utils/adt/date.c:2115 utils/adt/date.c:2667 utils/adt/float.c:1036 #: utils/adt/float.c:1112 utils/adt/int.c:635 utils/adt/int.c:682 -#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2595 +#: utils/adt/int.c:717 utils/adt/int8.c:413 utils/adt/numeric.c:2596 #: utils/adt/timestamp.c:3810 utils/adt/timestamp.c:3847 #: utils/adt/timestamp.c:3888 #, c-format @@ -25166,34 +25198,34 @@ msgstr "\"%s\" är utanför giltigt intervall för typen double precision" #: utils/adt/float.c:1247 utils/adt/float.c:1321 utils/adt/int.c:355 #: utils/adt/int.c:893 utils/adt/int.c:915 utils/adt/int.c:929 #: utils/adt/int.c:943 utils/adt/int.c:975 utils/adt/int.c:1213 -#: utils/adt/int8.c:1277 utils/adt/numeric.c:4593 utils/adt/numeric.c:4598 +#: utils/adt/int8.c:1277 utils/adt/numeric.c:4594 utils/adt/numeric.c:4599 #, c-format msgid "smallint out of range" msgstr "smallint utanför sitt intervall" -#: utils/adt/float.c:1447 utils/adt/numeric.c:3709 utils/adt/numeric.c:10112 +#: utils/adt/float.c:1447 utils/adt/numeric.c:3710 utils/adt/numeric.c:10113 #, c-format msgid "cannot take square root of a negative number" msgstr "kan inte ta kvadratroten av ett negativt tal" -#: utils/adt/float.c:1515 utils/adt/numeric.c:3997 utils/adt/numeric.c:4109 +#: utils/adt/float.c:1515 utils/adt/numeric.c:3998 utils/adt/numeric.c:4110 #, c-format msgid "zero raised to a negative power is undefined" msgstr "noll upphöjt med ett negativt tal är odefinierat" -#: utils/adt/float.c:1519 utils/adt/numeric.c:4001 utils/adt/numeric.c:11003 +#: utils/adt/float.c:1519 utils/adt/numeric.c:4002 utils/adt/numeric.c:11004 #, c-format msgid "a negative number raised to a non-integer power yields a complex result" msgstr "ett negativt tal upphöjt i en icke-negativ potens ger ett komplext resultat" -#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3909 -#: utils/adt/numeric.c:10783 +#: utils/adt/float.c:1695 utils/adt/float.c:1728 utils/adt/numeric.c:3910 +#: utils/adt/numeric.c:10784 #, c-format msgid "cannot take logarithm of zero" msgstr "kan inte ta logartimen av noll" -#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3847 -#: utils/adt/numeric.c:3904 utils/adt/numeric.c:10787 +#: utils/adt/float.c:1699 utils/adt/float.c:1732 utils/adt/numeric.c:3848 +#: utils/adt/numeric.c:3905 utils/adt/numeric.c:10788 #, c-format msgid "cannot take logarithm of a negative number" msgstr "kan inte ta logaritmen av ett negativt tal" @@ -25207,22 +25239,22 @@ msgstr "kan inte ta logaritmen av ett negativt tal" msgid "input is out of range" msgstr "indata är utanför giltigt intervall" -#: utils/adt/float.c:4000 utils/adt/numeric.c:1857 +#: utils/adt/float.c:4000 utils/adt/numeric.c:1858 #, c-format msgid "count must be greater than zero" msgstr "antal måste vara större än noll" -#: utils/adt/float.c:4005 utils/adt/numeric.c:1868 +#: utils/adt/float.c:4005 utils/adt/numeric.c:1869 #, c-format msgid "operand, lower bound, and upper bound cannot be NaN" msgstr "operand, undre gräns och övre gräns kan inte vara NaN" -#: utils/adt/float.c:4011 utils/adt/numeric.c:1873 +#: utils/adt/float.c:4011 utils/adt/numeric.c:1874 #, c-format msgid "lower and upper bounds must be finite" msgstr "undre och övre gräns måste vara ändliga" -#: utils/adt/float.c:4077 utils/adt/numeric.c:1887 +#: utils/adt/float.c:4077 utils/adt/numeric.c:1888 #, c-format msgid "lower bound cannot equal upper bound" msgstr "undre gräns kan inte vara samma som övre gräns" @@ -25587,7 +25619,7 @@ msgstr "stegstorleken kan inte vara noll" #: utils/adt/int8.c:994 utils/adt/int8.c:1008 utils/adt/int8.c:1041 #: utils/adt/int8.c:1055 utils/adt/int8.c:1069 utils/adt/int8.c:1100 #: utils/adt/int8.c:1122 utils/adt/int8.c:1136 utils/adt/int8.c:1150 -#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4542 +#: utils/adt/int8.c:1312 utils/adt/int8.c:1347 utils/adt/numeric.c:4543 #: utils/adt/rangetypes.c:1535 utils/adt/rangetypes.c:1548 #: utils/adt/varbit.c:1676 #, c-format @@ -25641,7 +25673,7 @@ msgstr "array:en måste ha två kolumner" msgid "mismatched array dimensions" msgstr "array-dimensionerna stämmer inte" -#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1956 +#: utils/adt/json.c:1702 utils/adt/jsonb_util.c:1962 #, c-format msgid "duplicate JSON object key value" msgstr "duplicerat nyckelvärde i JSON objekt" @@ -25706,23 +25738,23 @@ msgstr "kan inte typomvandla jsonb-objekt till typ %s" msgid "cannot cast jsonb array or object to type %s" msgstr "kan inte typomvandla jsonb-array eller objekt till typ %s" -#: utils/adt/jsonb_util.c:756 +#: utils/adt/jsonb_util.c:753 #, c-format msgid "number of jsonb object pairs exceeds the maximum allowed (%zu)" msgstr "antalet jsonb-objektpar överskrider det maximalt tillåtna (%zu)" -#: utils/adt/jsonb_util.c:797 +#: utils/adt/jsonb_util.c:794 #, c-format msgid "number of jsonb array elements exceeds the maximum allowed (%zu)" msgstr "antalet jsonb-array-element överskrider det maximalt tillåtna (%zu)" -#: utils/adt/jsonb_util.c:1671 utils/adt/jsonb_util.c:1691 +#: utils/adt/jsonb_util.c:1677 utils/adt/jsonb_util.c:1697 #, c-format msgid "total size of jsonb array elements exceeds the maximum of %d bytes" msgstr "total storleken på element i jsonb-array överskrider maximala %d byte" -#: utils/adt/jsonb_util.c:1752 utils/adt/jsonb_util.c:1787 -#: utils/adt/jsonb_util.c:1807 +#: utils/adt/jsonb_util.c:1758 utils/adt/jsonb_util.c:1793 +#: utils/adt/jsonb_util.c:1813 #, c-format msgid "total size of jsonb object elements exceeds the maximum of %d bytes" msgstr "total storleken på element i jsonb-objekt överskrider maximala %d byte" @@ -26215,12 +26247,12 @@ msgstr "ickedeterministiska jämförelser (collation) stöds inte för ILIKE" msgid "LIKE pattern must not end with escape character" msgstr "LIKE-mönster för inte sluta med ett escape-tecken" -#: utils/adt/like_match.c:293 utils/adt/regexp.c:800 +#: utils/adt/like_match.c:293 utils/adt/regexp.c:803 #, c-format msgid "invalid escape string" msgstr "ogiltig escape-sträng" -#: utils/adt/like_match.c:294 utils/adt/regexp.c:801 +#: utils/adt/like_match.c:294 utils/adt/regexp.c:804 #, c-format msgid "Escape string must be empty or one character." msgstr "Escape-sträng måste vara tom eller ett tecken." @@ -26431,10 +26463,10 @@ msgstr "resultatet är utanför giltigt intervall" msgid "cannot subtract inet values of different sizes" msgstr "kan inte subtrahera inet-värden av olika storlek" -#: utils/adt/numeric.c:793 utils/adt/numeric.c:3659 utils/adt/numeric.c:7216 -#: utils/adt/numeric.c:7419 utils/adt/numeric.c:7891 utils/adt/numeric.c:10586 -#: utils/adt/numeric.c:11061 utils/adt/numeric.c:11155 -#: utils/adt/numeric.c:11290 +#: utils/adt/numeric.c:793 utils/adt/numeric.c:3660 utils/adt/numeric.c:7217 +#: utils/adt/numeric.c:7420 utils/adt/numeric.c:7892 utils/adt/numeric.c:10587 +#: utils/adt/numeric.c:11062 utils/adt/numeric.c:11156 +#: utils/adt/numeric.c:11291 #, c-format msgid "value overflows numeric format" msgstr "overflow på värde i formatet numeric" @@ -26499,64 +26531,64 @@ msgstr "stegstorlek får inte vara NaN" msgid "step size cannot be infinity" msgstr "stegstorlek får inte vara oändligt" -#: utils/adt/numeric.c:3649 +#: utils/adt/numeric.c:3650 #, c-format msgid "factorial of a negative number is undefined" msgstr "fakultet av ett negativt tal är odefinierat" -#: utils/adt/numeric.c:4256 +#: utils/adt/numeric.c:4257 #, c-format msgid "lower bound cannot be NaN" msgstr "undre gräns kan inte vara NaN" -#: utils/adt/numeric.c:4260 +#: utils/adt/numeric.c:4261 #, c-format msgid "lower bound cannot be infinity" msgstr "undre gräns kan inte vara oändlig" -#: utils/adt/numeric.c:4267 +#: utils/adt/numeric.c:4268 #, c-format msgid "upper bound cannot be NaN" msgstr "övre gräns kan inte vara NaN" -#: utils/adt/numeric.c:4271 +#: utils/adt/numeric.c:4272 #, c-format msgid "upper bound cannot be infinity" msgstr "övre gräns kan inte vara oändlig" -#: utils/adt/numeric.c:4432 utils/adt/numeric.c:4520 utils/adt/numeric.c:4580 -#: utils/adt/numeric.c:4776 +#: utils/adt/numeric.c:4433 utils/adt/numeric.c:4521 utils/adt/numeric.c:4581 +#: utils/adt/numeric.c:4777 #, c-format msgid "cannot convert NaN to %s" msgstr "kan inte konvertera NaN till %s" -#: utils/adt/numeric.c:4436 utils/adt/numeric.c:4524 utils/adt/numeric.c:4584 -#: utils/adt/numeric.c:4780 +#: utils/adt/numeric.c:4437 utils/adt/numeric.c:4525 utils/adt/numeric.c:4585 +#: utils/adt/numeric.c:4781 #, c-format msgid "cannot convert infinity to %s" msgstr "kan inte konvertera oändlighet till %s" -#: utils/adt/numeric.c:4789 +#: utils/adt/numeric.c:4790 #, c-format msgid "pg_lsn out of range" msgstr "pg_lsn är utanför giltigt intervall" -#: utils/adt/numeric.c:7981 utils/adt/numeric.c:8032 +#: utils/adt/numeric.c:7982 utils/adt/numeric.c:8033 #, c-format msgid "numeric field overflow" msgstr "overflow i numeric-fält" -#: utils/adt/numeric.c:7982 +#: utils/adt/numeric.c:7983 #, c-format msgid "A field with precision %d, scale %d must round to an absolute value less than %s%d." msgstr "Ett fält med precision %d, skala %d måste avrundas till ett absolut värde mindre än %s%d." -#: utils/adt/numeric.c:8033 +#: utils/adt/numeric.c:8034 #, c-format msgid "A field with precision %d, scale %d cannot hold an infinite value." msgstr "Ett fält med precision %d, skala %d kan inte innehålla ett oändligt värde." -#: utils/adt/numeric.c:11359 utils/adt/pseudorandomfuncs.c:135 +#: utils/adt/numeric.c:11360 utils/adt/pseudorandomfuncs.c:135 #: utils/adt/pseudorandomfuncs.c:159 #, c-format msgid "lower bound must be less than or equal to upper bound" @@ -26861,7 +26893,7 @@ msgstr "För många komman." msgid "Junk after right parenthesis or bracket." msgstr "Skräp efter höger parentes eller hakparentes." -#: utils/adt/regexp.c:304 utils/adt/regexp.c:1996 utils/adt/varlena.c:4273 +#: utils/adt/regexp.c:304 utils/adt/regexp.c:2022 utils/adt/varlena.c:4273 #, c-format msgid "regular expression failed: %s" msgstr "reguljärt uttryck misslyckades: %s" @@ -26876,33 +26908,33 @@ msgstr "ogiltigt flagga till reguljärt uttryck: \"%.*s\"" msgid "If you meant to use regexp_replace() with a start parameter, cast the fourth argument to integer explicitly." msgstr "Om du menade att använda regexp_replace() med en startstartparameter så cast:a fjärde argumentet uttryckligen till integer." -#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1082 -#: utils/adt/regexp.c:1146 utils/adt/regexp.c:1155 utils/adt/regexp.c:1164 -#: utils/adt/regexp.c:1173 utils/adt/regexp.c:1853 utils/adt/regexp.c:1862 -#: utils/adt/regexp.c:1871 utils/misc/guc.c:6820 utils/misc/guc.c:6854 +#: utils/adt/regexp.c:716 utils/adt/regexp.c:725 utils/adt/regexp.c:1108 +#: utils/adt/regexp.c:1172 utils/adt/regexp.c:1181 utils/adt/regexp.c:1190 +#: utils/adt/regexp.c:1199 utils/adt/regexp.c:1879 utils/adt/regexp.c:1888 +#: utils/adt/regexp.c:1897 utils/misc/guc.c:6831 utils/misc/guc.c:6865 #, c-format msgid "invalid value for parameter \"%s\": %d" msgstr "ogiltigt värde för parameter \"%s\": %d" -#: utils/adt/regexp.c:936 +#: utils/adt/regexp.c:939 #, c-format msgid "SQL regular expression may not contain more than two escape-double-quote separators" msgstr "Regulart uttryck i SQL får inte innehålla mer än två dubbelcitat-escape-separatorer" #. translator: %s is a SQL function name -#: utils/adt/regexp.c:1093 utils/adt/regexp.c:1184 utils/adt/regexp.c:1271 -#: utils/adt/regexp.c:1310 utils/adt/regexp.c:1698 utils/adt/regexp.c:1753 -#: utils/adt/regexp.c:1882 +#: utils/adt/regexp.c:1119 utils/adt/regexp.c:1210 utils/adt/regexp.c:1297 +#: utils/adt/regexp.c:1336 utils/adt/regexp.c:1724 utils/adt/regexp.c:1779 +#: utils/adt/regexp.c:1908 #, c-format msgid "%s does not support the \"global\" option" msgstr "%s stöder inte \"global\"-flaggan" -#: utils/adt/regexp.c:1312 +#: utils/adt/regexp.c:1338 #, c-format msgid "Use the regexp_matches function instead." msgstr "Använd regexp_matches-funktionen istället." -#: utils/adt/regexp.c:1500 +#: utils/adt/regexp.c:1526 #, c-format msgid "too many regular expression matches" msgstr "för många reguljära uttryck matchar" @@ -26917,8 +26949,8 @@ msgstr "mer än en funktion med namn %s" msgid "more than one operator named %s" msgstr "mer än en operator med namn %s" -#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10509 -#: utils/adt/ruleutils.c:10722 +#: utils/adt/regproc.c:675 utils/adt/regproc.c:2029 utils/adt/ruleutils.c:10526 +#: utils/adt/ruleutils.c:10739 #, c-format msgid "too many arguments" msgstr "för många argument" @@ -27310,12 +27342,12 @@ msgstr "array med vikter får inte innehålla null-värden" msgid "weight out of range" msgstr "vikten är utanför giltigt intervall" -#: utils/adt/tsvector.c:216 +#: utils/adt/tsvector.c:213 #, c-format msgid "word is too long (%ld bytes, max %ld bytes)" msgstr "ordet är för långt (%ld byte, max %ld byte)" -#: utils/adt/tsvector.c:223 +#: utils/adt/tsvector.c:220 #, c-format msgid "string is too long for tsvector (%ld bytes, max %ld bytes)" msgstr "strängen är för lång för tsvector (%ld byte, max %ld byte)" @@ -27617,136 +27649,136 @@ msgstr "ogiltigt XML-kommentar" msgid "not an XML document" msgstr "inget XML-dokument" -#: utils/adt/xml.c:1008 utils/adt/xml.c:1031 +#: utils/adt/xml.c:1020 utils/adt/xml.c:1043 #, c-format msgid "invalid XML processing instruction" msgstr "ogiltig XML-processinstruktion" -#: utils/adt/xml.c:1009 +#: utils/adt/xml.c:1021 #, c-format msgid "XML processing instruction target name cannot be \"%s\"." msgstr "XML-processinstruktions målnamn kan inte vara \"%s\"." -#: utils/adt/xml.c:1032 +#: utils/adt/xml.c:1044 #, c-format msgid "XML processing instruction cannot contain \"?>\"." msgstr "XML-processinstruktion kan inte innehålla \"?>\"." -#: utils/adt/xml.c:1111 +#: utils/adt/xml.c:1123 #, c-format msgid "xmlvalidate is not implemented" msgstr "xmlvalidate är inte implementerat" -#: utils/adt/xml.c:1167 +#: utils/adt/xml.c:1179 #, c-format msgid "could not initialize XML library" msgstr "kunde inte initiera XML-bibliotek" -#: utils/adt/xml.c:1168 +#: utils/adt/xml.c:1180 #, c-format msgid "libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu." msgstr "libxml2 har inkompatibel char-typ: sizeof(char)=%zu, sizeof(xmlChar)=%zu." -#: utils/adt/xml.c:1254 +#: utils/adt/xml.c:1266 #, c-format msgid "could not set up XML error handler" msgstr "kunde inte ställa in XML-felhanterare" -#: utils/adt/xml.c:1255 +#: utils/adt/xml.c:1267 #, c-format msgid "This probably indicates that the version of libxml2 being used is not compatible with the libxml2 header files that PostgreSQL was built with." msgstr "Detta tyder på att libxml2-versionen som används inte är kompatibel med libxml2-header-filerna som PostgreSQL byggts med." -#: utils/adt/xml.c:2281 +#: utils/adt/xml.c:2283 msgid "Invalid character value." msgstr "Ogiltigt teckenvärde." -#: utils/adt/xml.c:2284 +#: utils/adt/xml.c:2286 msgid "Space required." msgstr "Mellanslag krävs." -#: utils/adt/xml.c:2287 +#: utils/adt/xml.c:2289 msgid "standalone accepts only 'yes' or 'no'." msgstr "standalone tillåter bara 'yes' eller 'no'." -#: utils/adt/xml.c:2290 +#: utils/adt/xml.c:2292 msgid "Malformed declaration: missing version." msgstr "Felaktig deklaration: saknar version." -#: utils/adt/xml.c:2293 +#: utils/adt/xml.c:2295 msgid "Missing encoding in text declaration." msgstr "Saknar kodning i textdeklaration." -#: utils/adt/xml.c:2296 +#: utils/adt/xml.c:2298 msgid "Parsing XML declaration: '?>' expected." msgstr "Parsar XML-deklaration: förväntade sig '?>'" -#: utils/adt/xml.c:2299 +#: utils/adt/xml.c:2301 #, c-format msgid "Unrecognized libxml error code: %d." msgstr "Okänd libxml-felkod: %d." -#: utils/adt/xml.c:2553 +#: utils/adt/xml.c:2555 #, c-format msgid "XML does not support infinite date values." msgstr "XML stöder inte oändliga datumvärden." -#: utils/adt/xml.c:2575 utils/adt/xml.c:2602 +#: utils/adt/xml.c:2577 utils/adt/xml.c:2604 #, c-format msgid "XML does not support infinite timestamp values." msgstr "XML stöder inte oändliga timestamp-värden." -#: utils/adt/xml.c:3018 +#: utils/adt/xml.c:3020 #, c-format msgid "invalid query" msgstr "ogiltig fråga" -#: utils/adt/xml.c:3110 +#: utils/adt/xml.c:3112 #, c-format msgid "portal \"%s\" does not return tuples" msgstr "portalen \"%s\" returnerar inga tupler" -#: utils/adt/xml.c:4362 +#: utils/adt/xml.c:4364 #, c-format msgid "invalid array for XML namespace mapping" msgstr "ogiltig array till XML-namnrymdmappning" -#: utils/adt/xml.c:4363 +#: utils/adt/xml.c:4365 #, c-format msgid "The array must be two-dimensional with length of the second axis equal to 2." msgstr "Arrayen måste vara tvådimensionell där längden på andra axeln är 2." -#: utils/adt/xml.c:4387 +#: utils/adt/xml.c:4389 #, c-format msgid "empty XPath expression" msgstr "tomt XPath-uttryck" -#: utils/adt/xml.c:4439 +#: utils/adt/xml.c:4441 #, c-format msgid "neither namespace name nor URI may be null" msgstr "varken namnrymdnamn eller URI får vara null" -#: utils/adt/xml.c:4446 +#: utils/adt/xml.c:4448 #, c-format msgid "could not register XML namespace with name \"%s\" and URI \"%s\"" msgstr "kunde inte registrera XML-namnrymd med namn \"%s\" och URL \"%s\"" -#: utils/adt/xml.c:4795 +#: utils/adt/xml.c:4797 #, c-format msgid "DEFAULT namespace is not supported" msgstr "namnrymden DEFAULT stöds inte" -#: utils/adt/xml.c:4824 +#: utils/adt/xml.c:4826 #, c-format msgid "row path filter must not be empty string" msgstr "sökvägsfilter för rad får inte vara tomma strängen" -#: utils/adt/xml.c:4858 +#: utils/adt/xml.c:4860 #, c-format msgid "column path filter must not be empty string" msgstr "sokvägsfilter för kolumn får inte vara tomma strängen" -#: utils/adt/xml.c:5005 +#: utils/adt/xml.c:5007 #, c-format msgid "more than one value returned by column XPath expression" msgstr "mer än ett värde returnerades från kolumns XPath-uttryck" @@ -28216,7 +28248,7 @@ msgstr "Filen verkar ha lämnats kvar av misstag, men kan inte tas bort. Ta bort msgid "could not write lock file \"%s\": %m" msgstr "kunde inte skriva låsfil \"%s\": %m" -#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5765 +#: utils/init/miscinit.c:1591 utils/init/miscinit.c:1733 utils/misc/guc.c:5770 #, c-format msgid "could not read from file \"%s\": %m" msgstr "kunde inte läsa från fil \"%s\": %m" @@ -28486,7 +28518,7 @@ msgstr "bind_textdomain_codeset misslyckades" msgid "invalid byte sequence for encoding \"%s\": %s" msgstr "ogiltigt byte-sekvens för kodning \"%s\": %s" -#: utils/mb/mbutils.c:1751 +#: utils/mb/mbutils.c:1759 #, c-format msgid "character with byte sequence %s in encoding \"%s\" has no equivalent in encoding \"%s\"" msgstr "tecken med byte-sekvens %s i kodning \"%s\" har inget motsvarande i kodning \"%s\"" @@ -28647,7 +28679,7 @@ msgid "parameter \"%s\" cannot be changed now" msgstr "parameter \"%s\" kan inte ändras nu" #: utils/misc/guc.c:3541 utils/misc/guc.c:3603 utils/misc/guc.c:4671 -#: utils/misc/guc.c:6756 +#: utils/misc/guc.c:6767 #, c-format msgid "permission denied to set parameter \"%s\"" msgstr "rättighet saknas för att sätta parameter \"%s\"" @@ -28672,12 +28704,12 @@ msgstr "parametern \"%s\" kunde inte återställas" msgid "parameter \"%s\" cannot be set locally in functions" msgstr "parametern \"%s\" kan inte ändras lokalt i funktioner" -#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5450 +#: utils/misc/guc.c:4370 utils/misc/guc.c:4418 utils/misc/guc.c:5455 #, c-format msgid "permission denied to examine \"%s\"" msgstr "rättighet saknas för att se \"%s\"" -#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5451 +#: utils/misc/guc.c:4371 utils/misc/guc.c:4419 utils/misc/guc.c:5456 #, c-format msgid "Only roles with privileges of the \"%s\" role may examine this parameter." msgstr "Bara roller med rättigheter från rollen \"%s\" får se denna parameter." @@ -28692,47 +28724,47 @@ msgstr "ALTER SYSTEM tillåts inte i denna miljö" msgid "permission denied to perform ALTER SYSTEM RESET ALL" msgstr "rättighet saknas för att utföra ALTER SYSTEM RESET ALL" -#: utils/misc/guc.c:4740 +#: utils/misc/guc.c:4745 #, c-format msgid "parameter value for ALTER SYSTEM must not contain a newline" msgstr "parametervärde till ALTER SYSTEM kan inte innehålla nyradstecken" -#: utils/misc/guc.c:4785 +#: utils/misc/guc.c:4790 #, c-format msgid "could not parse contents of file \"%s\"" msgstr "kunde inte parsa innehållet i fil \"%s\"" -#: utils/misc/guc.c:4967 +#: utils/misc/guc.c:4972 #, c-format msgid "attempt to redefine parameter \"%s\"" msgstr "försök att omdefiniera parameter \"%s\"" -#: utils/misc/guc.c:5306 +#: utils/misc/guc.c:5311 #, c-format msgid "invalid configuration parameter name \"%s\", removing it" msgstr "ogiltigt konfigurationsparameternamn \"%s\", tas bort" -#: utils/misc/guc.c:5308 +#: utils/misc/guc.c:5313 #, c-format msgid "\"%s\" is now a reserved prefix." msgstr "\"%s\" är nu ett reserverat prefix." -#: utils/misc/guc.c:6179 +#: utils/misc/guc.c:6184 #, c-format msgid "while setting parameter \"%s\" to \"%s\"" msgstr "vid sättande av parameter \"%s\" till \"%s\"" -#: utils/misc/guc.c:6348 +#: utils/misc/guc.c:6353 #, c-format msgid "parameter \"%s\" could not be set" msgstr "parameter \"%s\" kunde inte sättas" -#: utils/misc/guc.c:6438 +#: utils/misc/guc.c:6443 #, c-format msgid "could not parse setting for parameter \"%s\"" msgstr "kunde inte tolka inställningen för parameter \"%s\"" -#: utils/misc/guc.c:6888 +#: utils/misc/guc.c:6899 #, c-format msgid "invalid value for parameter \"%s\": %g" msgstr "ogiltigt värde för parameter \"%s\": %g" @@ -28791,7 +28823,7 @@ msgstr "Resursanvändning / Disk" #: utils/misc/guc_tables.c:684 msgid "Resource Usage / Kernel Resources" -msgstr "Resursanvändning / Kärnresurser" +msgstr "Resursanvändning / Kernel-resurser" #: utils/misc/guc_tables.c:685 msgid "Resource Usage / Cost-Based Vacuum Delay" @@ -31065,3 +31097,7 @@ msgstr "en serialiserbar transaktion som inte är read-only kan inte importera e #, c-format msgid "cannot import a snapshot from a different database" msgstr "kan inte importera en snapshot från en annan databas" + +#, c-format +msgid "oversize GSSAPI packet sent by the client (%zu > %d)" +msgstr "för stort GSSAPI-paket skickat av klienten (%zu > %d)" diff --git a/src/bin/pg_basebackup/po/ru.po b/src/bin/pg_basebackup/po/ru.po index 3f00c4d3d239a..1e1bfa63d877a 100644 --- a/src/bin/pg_basebackup/po/ru.po +++ b/src/bin/pg_basebackup/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_basebackup (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-09-07 11:12+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -1245,9 +1245,9 @@ msgstr "" #: pg_basebackup.c:2712 pg_basebackup.c:2724 pg_basebackup.c:2736 #: pg_basebackup.c:2744 pg_basebackup.c:2757 pg_basebackup.c:2763 #: pg_basebackup.c:2772 pg_basebackup.c:2784 pg_basebackup.c:2795 -#: pg_basebackup.c:2803 pg_createsubscriber.c:2038 pg_createsubscriber.c:2048 -#: pg_createsubscriber.c:2056 pg_createsubscriber.c:2084 -#: pg_createsubscriber.c:2116 pg_receivewal.c:748 pg_receivewal.c:760 +#: pg_basebackup.c:2803 pg_createsubscriber.c:2047 pg_createsubscriber.c:2057 +#: pg_createsubscriber.c:2065 pg_createsubscriber.c:2093 +#: pg_createsubscriber.c:2125 pg_receivewal.c:748 pg_receivewal.c:760 #: pg_receivewal.c:767 pg_receivewal.c:776 pg_receivewal.c:783 #: pg_receivewal.c:793 pg_recvlogical.c:853 pg_recvlogical.c:865 #: pg_recvlogical.c:875 pg_recvlogical.c:882 pg_recvlogical.c:889 @@ -1257,7 +1257,7 @@ msgstr "" msgid "Try \"%s --help\" for more information." msgstr "Для дополнительной информации попробуйте \"%s --help\"." -#: pg_basebackup.c:2584 pg_createsubscriber.c:2046 pg_receivewal.c:758 +#: pg_basebackup.c:2584 pg_createsubscriber.c:2055 pg_receivewal.c:758 #: pg_recvlogical.c:863 #, c-format msgid "too many command-line arguments (first is \"%s\")" @@ -1383,7 +1383,7 @@ msgid "" msgstr "" "на главном сервере остался слот репликации \"%s\", созданный в базе \"%s\"" -#: pg_createsubscriber.c:206 pg_createsubscriber.c:1261 +#: pg_createsubscriber.c:206 pg_createsubscriber.c:1270 #, c-format msgid "Drop this replication slot soon to avoid retention of WAL files." msgstr "" @@ -1732,17 +1732,17 @@ msgstr "удалить подписку \"%s\" не получилось: %s" msgid "could not obtain pre-existing subscriptions: %s" msgstr "получить уже существующие подписки не удалось: %s" -#: pg_createsubscriber.c:1259 +#: pg_createsubscriber.c:1268 #, c-format msgid "could not drop replication slot \"%s\" on primary" msgstr "удалить слот репликации \"%s\" на главном сервере не получилось" -#: pg_createsubscriber.c:1293 +#: pg_createsubscriber.c:1302 #, c-format msgid "could not obtain failover replication slot information: %s" msgstr "получить информацию о переносимом слоте репликации не удалось: %s" -#: pg_createsubscriber.c:1295 pg_createsubscriber.c:1304 +#: pg_createsubscriber.c:1304 pg_createsubscriber.c:1313 #, c-format msgid "" "Drop the failover replication slots on subscriber soon to avoid retention of " @@ -1751,42 +1751,42 @@ msgstr "" "Удалите переносимые слоты репликации на подписчике незамедлительно во " "избежание накопления файлов WAL." -#: pg_createsubscriber.c:1303 +#: pg_createsubscriber.c:1312 #, c-format msgid "could not drop failover replication slot" msgstr "удалить переносимый слот репликации не получилось" -#: pg_createsubscriber.c:1325 +#: pg_createsubscriber.c:1334 #, c-format msgid "creating the replication slot \"%s\" in database \"%s\"" msgstr "создание слота репликации \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1343 +#: pg_createsubscriber.c:1352 #, c-format msgid "could not create replication slot \"%s\" in database \"%s\": %s" msgstr "создать слот репликации \"%s\" в базе \"%s\" не удалось: %s" -#: pg_createsubscriber.c:1373 +#: pg_createsubscriber.c:1382 #, c-format msgid "dropping the replication slot \"%s\" in database \"%s\"" msgstr "удаление слота репликации \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1389 +#: pg_createsubscriber.c:1398 #, c-format msgid "could not drop replication slot \"%s\" in database \"%s\": %s" msgstr "удалить слот репликации \"%s\" в базе \"%s\" не получилось: %s" -#: pg_createsubscriber.c:1410 +#: pg_createsubscriber.c:1419 #, c-format msgid "pg_ctl failed with exit code %d" msgstr "команда pg_ctl завершилась с кодом ошибки %d" -#: pg_createsubscriber.c:1415 +#: pg_createsubscriber.c:1424 #, c-format msgid "pg_ctl was terminated by exception 0x%X" msgstr "команда pg_ctl была прервана исключением 0x%X" -#: pg_createsubscriber.c:1417 +#: pg_createsubscriber.c:1426 #, c-format msgid "" "See C include file \"ntstatus.h\" for a description of the hexadecimal value." @@ -1794,52 +1794,52 @@ msgstr "" "Описание этого шестнадцатеричного значения ищите во включаемом C-файле " "\"ntstatus.h\"" -#: pg_createsubscriber.c:1419 +#: pg_createsubscriber.c:1428 #, c-format msgid "pg_ctl was terminated by signal %d: %s" msgstr "команда pg_ctl была завершена сигналом %d: %s" -#: pg_createsubscriber.c:1425 +#: pg_createsubscriber.c:1434 #, c-format msgid "pg_ctl exited with unrecognized status %d" msgstr "команда pg_ctl завершилась с нераспознанным кодом состояния %d" -#: pg_createsubscriber.c:1428 +#: pg_createsubscriber.c:1437 #, c-format msgid "The failed command was: %s" msgstr "Ошибку вызвала команда: %s" -#: pg_createsubscriber.c:1474 +#: pg_createsubscriber.c:1483 #, c-format msgid "server was started" msgstr "сервер был запущен" -#: pg_createsubscriber.c:1489 +#: pg_createsubscriber.c:1498 #, c-format msgid "server was stopped" msgstr "сервер был остановлен" -#: pg_createsubscriber.c:1508 +#: pg_createsubscriber.c:1517 #, c-format msgid "waiting for the target server to reach the consistent state" msgstr "ожидание достижения целевым сервером согласованного состояния" -#: pg_createsubscriber.c:1531 +#: pg_createsubscriber.c:1540 #, c-format msgid "recovery timed out" msgstr "тайм-аут при восстановлении" -#: pg_createsubscriber.c:1544 +#: pg_createsubscriber.c:1553 #, c-format msgid "server did not end recovery" msgstr "сервер не завершил восстановление" -#: pg_createsubscriber.c:1546 +#: pg_createsubscriber.c:1555 #, c-format msgid "target server reached the consistent state" msgstr "целевой сервер достиг согласованного состояния" -#: pg_createsubscriber.c:1547 +#: pg_createsubscriber.c:1556 #, c-format msgid "" "If pg_createsubscriber fails after this point, you must recreate the " @@ -1848,159 +1848,159 @@ msgstr "" "Если в работе pg_createsubscriber произойдёт сбой после этого момента, " "продолжение возможно только после пересоздания физической реплики." -#: pg_createsubscriber.c:1574 +#: pg_createsubscriber.c:1583 #, c-format msgid "could not obtain publication information: %s" msgstr "получить информацию о публикации не удалось: %s" -#: pg_createsubscriber.c:1588 +#: pg_createsubscriber.c:1597 #, c-format msgid "publication \"%s\" already exists" msgstr "публикация \"%s\" уже существует" -#: pg_createsubscriber.c:1589 +#: pg_createsubscriber.c:1598 #, c-format msgid "Consider renaming this publication before continuing." msgstr "Чтобы продолжить, её можно переименовать." -#: pg_createsubscriber.c:1596 +#: pg_createsubscriber.c:1605 #, c-format msgid "creating publication \"%s\" in database \"%s\"" msgstr "создаётся публикация \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1609 +#: pg_createsubscriber.c:1618 #, c-format msgid "could not create publication \"%s\" in database \"%s\": %s" msgstr "создать публикацию \"%s\" в базе \"%s\" не удалось: %s" -#: pg_createsubscriber.c:1638 +#: pg_createsubscriber.c:1647 #, c-format msgid "dropping publication \"%s\" in database \"%s\"" msgstr "удаляется публикация \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1652 +#: pg_createsubscriber.c:1661 #, c-format msgid "could not drop publication \"%s\" in database \"%s\": %s" msgstr "удалить публикацию \"%s\" в базе \"%s\" не получилось: %s" -#: pg_createsubscriber.c:1698 +#: pg_createsubscriber.c:1707 #, c-format msgid "creating subscription \"%s\" in database \"%s\"" msgstr "создаётся подписка \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1719 +#: pg_createsubscriber.c:1728 #, c-format msgid "could not create subscription \"%s\" in database \"%s\": %s" msgstr "создать подписку \"%s\" в базе \"%s\" не удалось: %s" -#: pg_createsubscriber.c:1764 +#: pg_createsubscriber.c:1773 #, c-format msgid "could not obtain subscription OID: %s" msgstr "получить OID подписки не удалось: %s" -#: pg_createsubscriber.c:1771 +#: pg_createsubscriber.c:1780 #, c-format msgid "could not obtain subscription OID: got %d rows, expected %d row" msgstr "получить OID подписки не удалось; получено строк: %d, ожидалось: %d" -#: pg_createsubscriber.c:1795 +#: pg_createsubscriber.c:1804 #, c-format msgid "" "setting the replication progress (node name \"%s\", LSN %s) in database " "\"%s\"" msgstr "отражение состояния репликации (имя узла \"%s\", LSN %s) в базе \"%s\"" -#: pg_createsubscriber.c:1810 +#: pg_createsubscriber.c:1819 #, c-format msgid "could not set replication progress for subscription \"%s\": %s" msgstr "не удалось передать состояние репликации для подписки \"%s\": %s" -#: pg_createsubscriber.c:1841 +#: pg_createsubscriber.c:1850 #, c-format msgid "enabling subscription \"%s\" in database \"%s\"" msgstr "включение подписки \"%s\" в базе \"%s\"" -#: pg_createsubscriber.c:1853 +#: pg_createsubscriber.c:1862 #, c-format msgid "could not enable subscription \"%s\": %s" msgstr "включить подписку \"%s\" не удалось: %s" -#: pg_createsubscriber.c:1945 +#: pg_createsubscriber.c:1954 #, c-format msgid "cannot be executed by \"root\"" msgstr "программу не должен запускать root" -#: pg_createsubscriber.c:1946 +#: pg_createsubscriber.c:1955 #, c-format msgid "You must run %s as the PostgreSQL superuser." msgstr "Запускать %s нужно от имени суперпользователя PostgreSQL." -#: pg_createsubscriber.c:1967 +#: pg_createsubscriber.c:1976 #, c-format msgid "database \"%s\" specified more than once" msgstr "база \"%s\" указана неоднократно" -#: pg_createsubscriber.c:2008 +#: pg_createsubscriber.c:2017 #, c-format msgid "publication \"%s\" specified more than once" msgstr "публикация \"%s\" указана неоднократно" -#: pg_createsubscriber.c:2020 +#: pg_createsubscriber.c:2029 #, c-format msgid "replication slot \"%s\" specified more than once" msgstr "слот репликации \"%s\" указан неоднократно" -#: pg_createsubscriber.c:2032 +#: pg_createsubscriber.c:2041 #, c-format msgid "subscription \"%s\" specified more than once" msgstr "подписка \"%s\" указана неоднократно" -#: pg_createsubscriber.c:2055 +#: pg_createsubscriber.c:2064 #, c-format msgid "no subscriber data directory specified" msgstr "каталог данных подписчика не указан" -#: pg_createsubscriber.c:2066 +#: pg_createsubscriber.c:2075 #, c-format msgid "could not determine current directory" msgstr "не удалось определить текущий каталог" -#: pg_createsubscriber.c:2083 +#: pg_createsubscriber.c:2092 #, c-format msgid "no publisher connection string specified" msgstr "строка подключения к серверу публикации не указана" -#: pg_createsubscriber.c:2087 +#: pg_createsubscriber.c:2096 #, c-format msgid "validating publisher connection string" msgstr "проверяется строка подключения к серверу публикации" -#: pg_createsubscriber.c:2093 +#: pg_createsubscriber.c:2102 #, c-format msgid "validating subscriber connection string" msgstr "проверяется строка подключения к подписчику" -#: pg_createsubscriber.c:2098 +#: pg_createsubscriber.c:2107 #, c-format msgid "no database was specified" msgstr "база данных не указана" -#: pg_createsubscriber.c:2110 +#: pg_createsubscriber.c:2119 #, c-format msgid "database name \"%s\" was extracted from the publisher connection string" msgstr "имя базы \"%s\" извлечено из строки подключения к серверу публикации" -#: pg_createsubscriber.c:2115 +#: pg_createsubscriber.c:2124 #, c-format msgid "no database name specified" msgstr "имя базы данных не указано" -#: pg_createsubscriber.c:2125 +#: pg_createsubscriber.c:2134 #, c-format msgid "wrong number of publication names specified" msgstr "указано неверное количество имён публикаций" -#: pg_createsubscriber.c:2126 +#: pg_createsubscriber.c:2135 #, c-format msgid "" "The number of specified publication names (%d) must match the number of " @@ -2009,12 +2009,12 @@ msgstr "" "Количество указанных имён публикаций (%d) должно совпадать с количеством " "указанных имён баз (%d)." -#: pg_createsubscriber.c:2132 +#: pg_createsubscriber.c:2141 #, c-format msgid "wrong number of subscription names specified" msgstr "указано неверное количество имён подписок" -#: pg_createsubscriber.c:2133 +#: pg_createsubscriber.c:2142 #, c-format msgid "" "The number of specified subscription names (%d) must match the number of " @@ -2023,12 +2023,12 @@ msgstr "" "Количество указанных имён подписок (%d) должно совпадать с количеством " "указанных имён баз (%d)." -#: pg_createsubscriber.c:2139 +#: pg_createsubscriber.c:2148 #, c-format msgid "wrong number of replication slot names specified" msgstr "указано неверное количество имён слотов репликации" -#: pg_createsubscriber.c:2140 +#: pg_createsubscriber.c:2149 #, c-format msgid "" "The number of specified replication slot names (%d) must match the number of " @@ -2037,38 +2037,38 @@ msgstr "" "Количество указанных имён слотов репликации (%d) должно совпадать с " "количеством указанных имён баз (%d)." -#: pg_createsubscriber.c:2169 +#: pg_createsubscriber.c:2178 #, c-format msgid "subscriber data directory is not a copy of the source database cluster" msgstr "" "каталог данных подписчика не является копией исходного кластера баз данных" -#: pg_createsubscriber.c:2182 +#: pg_createsubscriber.c:2191 #, c-format msgid "standby server is running" msgstr "резервный сервер запущен" -#: pg_createsubscriber.c:2183 +#: pg_createsubscriber.c:2192 #, c-format msgid "Stop the standby server and try again." msgstr "Остановите резервный сервер и повторите попытку." -#: pg_createsubscriber.c:2192 +#: pg_createsubscriber.c:2201 #, c-format msgid "starting the standby server with command-line options" msgstr "резервный сервер запускается с параметрами командной строки" -#: pg_createsubscriber.c:2208 pg_createsubscriber.c:2243 +#: pg_createsubscriber.c:2217 pg_createsubscriber.c:2252 #, c-format msgid "stopping the subscriber" msgstr "подписчик останавливается" -#: pg_createsubscriber.c:2222 +#: pg_createsubscriber.c:2231 #, c-format msgid "starting the subscriber" msgstr "подписчик запускается" -#: pg_createsubscriber.c:2251 +#: pg_createsubscriber.c:2260 #, c-format msgid "Done!" msgstr "Готово!" diff --git a/src/bin/pg_combinebackup/po/ru.po b/src/bin/pg_combinebackup/po/ru.po index 5c2af7387c385..f6b1c900f0876 100644 --- a/src/bin/pg_combinebackup/po/ru.po +++ b/src/bin/pg_combinebackup/po/ru.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_combinebackup (PostgreSQL) 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-08 07:44+0200\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-11-09 08:04+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -165,91 +165,91 @@ msgstr "не удалось прочитать каталог \"%s\": %m" msgid "could not rename file \"%s\" to \"%s\": %m" msgstr "не удалось переименовать файл \"%s\" в \"%s\": %m" -#: ../../common/jsonapi.c:2121 +#: ../../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "" "Инкрементальный лексический анализатор не подходит для нисходящего " "рекурсивного разбора." -#: ../../common/jsonapi.c:2123 +#: ../../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "" "Для инкрементального разбора требуется инкрементальный лексический " "анализатор." -#: ../../common/jsonapi.c:2125 +#: ../../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "" "Слишком большая вложенность JSON, максимальная допустимая глубина: 6400." -#: ../../common/jsonapi.c:2127 +#: ../../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "Неверная спецпоследовательность: \"\\%.*s\"." -#: ../../common/jsonapi.c:2131 +#: ../../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "Символ с кодом 0x%02x необходимо экранировать." -#: ../../common/jsonapi.c:2135 +#: ../../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "Ожидался конец текста, но обнаружено продолжение \"%.*s\"." -#: ../../common/jsonapi.c:2138 +#: ../../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "Ожидался элемент массива или \"]\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2141 +#: ../../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"]\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2144 +#: ../../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "Ожидалось \":\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2147 +#: ../../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "Ожидалось значение JSON, но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2150 +#: ../../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "Неожиданный конец входной строки." -#: ../../common/jsonapi.c:2152 +#: ../../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "Ожидалась строка или \"}\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2155 +#: ../../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"}\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2158 +#: ../../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "Ожидалась строка, но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2161 +#: ../../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "Ошибочный элемент \"%.*s\"." -#: ../../common/jsonapi.c:2164 +#: ../../common/jsonapi.c:2167 msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 нельзя преобразовать в текст." -#: ../../common/jsonapi.c:2166 +#: ../../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "За \"\\u\" должны следовать четыре шестнадцатеричные цифры." -#: ../../common/jsonapi.c:2169 +#: ../../common/jsonapi.c:2172 msgid "" "Unicode escape values cannot be used for code point values above 007F when " "the encoding is not UTF8." @@ -257,18 +257,18 @@ msgstr "" "Спецкоды Unicode для значений выше 007F можно использовать только с " "кодировкой UTF8." -#: ../../common/jsonapi.c:2178 +#: ../../common/jsonapi.c:2181 #, c-format msgid "" "Unicode escape value could not be translated to the server's encoding %s." msgstr "Спецкод Unicode нельзя преобразовать в серверную кодировку %s." -#: ../../common/jsonapi.c:2185 +#: ../../common/jsonapi.c:2188 msgid "Unicode high surrogate must not follow a high surrogate." msgstr "" "Старшее слово суррогата Unicode не может следовать за другим старшим словом." -#: ../../common/jsonapi.c:2187 +#: ../../common/jsonapi.c:2190 msgid "Unicode low surrogate must follow a high surrogate." msgstr "Младшее слово суррогата Unicode должно следовать за старшим словом." diff --git a/src/bin/pg_dump/po/ru.po b/src/bin/pg_dump/po/ru.po index be7aa381ac119..4b9436d95e964 100644 --- a/src/bin/pg_dump/po/ru.po +++ b/src/bin/pg_dump/po/ru.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_dump (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-09 07:11+0300\n" "PO-Revision-Date: 2025-05-03 16:33+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -253,234 +253,234 @@ msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "" "имя базы данных содержит символ новой строки или перевода каретки: \"%s\"\n" -#: common.c:135 +#: common.c:136 #, c-format msgid "reading extensions" msgstr "чтение расширений" -#: common.c:138 +#: common.c:139 #, c-format msgid "identifying extension members" msgstr "выявление членов расширений" -#: common.c:141 +#: common.c:142 #, c-format msgid "reading schemas" msgstr "чтение схем" -#: common.c:150 +#: common.c:151 #, c-format msgid "reading user-defined tables" msgstr "чтение пользовательских таблиц" -#: common.c:155 +#: common.c:156 #, c-format msgid "reading user-defined functions" msgstr "чтение пользовательских функций" -#: common.c:159 +#: common.c:160 #, c-format msgid "reading user-defined types" msgstr "чтение пользовательских типов" -#: common.c:163 +#: common.c:164 #, c-format msgid "reading procedural languages" msgstr "чтение процедурных языков" -#: common.c:166 +#: common.c:167 #, c-format msgid "reading user-defined aggregate functions" msgstr "чтение пользовательских агрегатных функций" -#: common.c:169 +#: common.c:170 #, c-format msgid "reading user-defined operators" msgstr "чтение пользовательских операторов" -#: common.c:172 +#: common.c:173 #, c-format msgid "reading user-defined access methods" msgstr "чтение пользовательских методов доступа" -#: common.c:175 +#: common.c:176 #, c-format msgid "reading user-defined operator classes" msgstr "чтение пользовательских классов операторов" -#: common.c:178 +#: common.c:179 #, c-format msgid "reading user-defined operator families" msgstr "чтение пользовательских семейств операторов" -#: common.c:181 +#: common.c:182 #, c-format msgid "reading user-defined text search parsers" msgstr "чтение пользовательских анализаторов текстового поиска" -#: common.c:184 +#: common.c:185 #, c-format msgid "reading user-defined text search templates" msgstr "чтение пользовательских шаблонов текстового поиска" -#: common.c:187 +#: common.c:188 #, c-format msgid "reading user-defined text search dictionaries" msgstr "чтение пользовательских словарей текстового поиска" -#: common.c:190 +#: common.c:191 #, c-format msgid "reading user-defined text search configurations" msgstr "чтение пользовательских конфигураций текстового поиска" -#: common.c:193 +#: common.c:194 #, c-format msgid "reading user-defined foreign-data wrappers" msgstr "чтение пользовательских оболочек сторонних данных" -#: common.c:196 +#: common.c:197 #, c-format msgid "reading user-defined foreign servers" msgstr "чтение пользовательских сторонних серверов" -#: common.c:199 +#: common.c:200 #, c-format msgid "reading default privileges" msgstr "чтение прав по умолчанию" -#: common.c:202 +#: common.c:203 #, c-format msgid "reading user-defined collations" msgstr "чтение пользовательских правил сортировки" -#: common.c:205 +#: common.c:206 #, c-format msgid "reading user-defined conversions" msgstr "чтение пользовательских преобразований" -#: common.c:208 +#: common.c:209 #, c-format msgid "reading type casts" msgstr "чтение приведений типов" -#: common.c:211 +#: common.c:212 #, c-format msgid "reading transforms" msgstr "чтение преобразований" -#: common.c:214 +#: common.c:215 #, c-format msgid "reading table inheritance information" msgstr "чтение информации о наследовании таблиц" -#: common.c:217 +#: common.c:218 #, c-format msgid "reading event triggers" msgstr "чтение событийных триггеров" -#: common.c:221 +#: common.c:222 #, c-format msgid "finding extension tables" msgstr "поиск таблиц расширений" -#: common.c:225 +#: common.c:226 #, c-format msgid "finding inheritance relationships" msgstr "поиск связей наследования" -#: common.c:228 +#: common.c:229 #, c-format msgid "reading column info for interesting tables" msgstr "чтение информации о столбцах интересующих таблиц" -#: common.c:231 +#: common.c:232 #, c-format msgid "flagging inherited columns in subtables" msgstr "пометка наследованных столбцов в подтаблицах" -#: common.c:234 +#: common.c:235 #, c-format msgid "reading partitioning data" msgstr "чтение информации о секционировании" -#: common.c:237 +#: common.c:238 #, c-format msgid "reading indexes" msgstr "чтение индексов" -#: common.c:240 +#: common.c:241 #, c-format msgid "flagging indexes in partitioned tables" msgstr "пометка индексов в секционированных таблицах" -#: common.c:243 +#: common.c:244 #, c-format msgid "reading extended statistics" msgstr "чтение расширенной статистики" -#: common.c:246 +#: common.c:247 #, c-format msgid "reading constraints" msgstr "чтение ограничений" -#: common.c:249 +#: common.c:250 #, c-format msgid "reading triggers" msgstr "чтение триггеров" -#: common.c:252 +#: common.c:253 #, c-format msgid "reading rewrite rules" msgstr "чтение правил перезаписи" -#: common.c:255 +#: common.c:256 #, c-format msgid "reading policies" msgstr "чтение политик" -#: common.c:258 +#: common.c:259 #, c-format msgid "reading publications" msgstr "чтение публикаций" -#: common.c:261 +#: common.c:262 #, c-format msgid "reading publication membership of tables" msgstr "чтение информации о таблицах, включённых в публикации" -#: common.c:264 +#: common.c:265 #, c-format msgid "reading publication membership of schemas" msgstr "чтение информации о схемах, включённых в публикации" -#: common.c:267 +#: common.c:268 #, c-format msgid "reading subscriptions" msgstr "чтение подписок" -#: common.c:270 +#: common.c:271 #, c-format msgid "reading subscription membership of tables" msgstr "чтение информации о таблицах, включённых в подписки" -#: common.c:333 +#: common.c:334 #, c-format msgid "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found" msgstr "" "нарушение целостности: родительская таблица с OID %u для таблицы \"%s\" (OID " "%u) не найдена" -#: common.c:375 +#: common.c:376 #, c-format msgid "invalid number of parents %d for table \"%s\"" msgstr "неверное число родителей (%d) для таблицы \"%s\"" -#: common.c:1098 +#: common.c:1117 #, c-format msgid "could not parse numeric array \"%s\": too many numbers" msgstr "не удалось разобрать числовой массив \"%s\": слишком много чисел" -#: common.c:1110 +#: common.c:1129 #, c-format msgid "could not parse numeric array \"%s\": invalid character in number" msgstr "не удалось разобрать числовой массив \"%s\": неверный символ в числе" @@ -522,7 +522,7 @@ msgid "could not read from input file: %m" msgstr "не удалось прочитать входной файл: %m" #: compress_gzip.c:297 compress_lz4.c:630 compress_none.c:141 -#: compress_zstd.c:372 pg_backup_custom.c:649 pg_backup_directory.c:565 +#: compress_zstd.c:372 pg_backup_custom.c:649 pg_backup_directory.c:570 #: pg_backup_tar.c:740 pg_backup_tar.c:763 #, c-format msgid "could not read from input file: end of file" @@ -601,32 +601,32 @@ msgid "invalid format in filter read from file \"%s\" on line %d: %s" msgstr "" "неверный формат в фильтре, прочитанном из файла \"%s\", в строке %d: %s" -#: filter.c:241 filter.c:468 +#: filter.c:244 filter.c:471 #, c-format msgid "could not read from filter file \"%s\": %m" msgstr "не удалось прочитать файл фильтра \"%s\": %m" -#: filter.c:244 +#: filter.c:247 msgid "unexpected end of file" msgstr "неожиданный конец файла" -#: filter.c:311 +#: filter.c:314 msgid "missing object name pattern" msgstr "отсутствует шаблон имени объекта" -#: filter.c:422 +#: filter.c:425 msgid "no filter command found (expected \"include\" or \"exclude\")" msgstr "отсутствует команда фильтра (ожидалась \"include\" или \"exclude\")" -#: filter.c:433 +#: filter.c:436 msgid "invalid filter command (expected \"include\" or \"exclude\")" msgstr "неверная команда фильтра (ожидалась \"include\" или \"exclude\")" -#: filter.c:440 +#: filter.c:443 msgid "missing filter object type" msgstr "отсутствует тип объекта фильтра" -#: filter.c:447 +#: filter.c:450 #, c-format msgid "unsupported filter object type: \"%.*s\"" msgstr "неподдерживаемый тип объекта фильтра: \"%.*s\"" @@ -851,7 +851,7 @@ msgstr "восстановление большого объекта с OID %u" msgid "could not create large object %u: %s" msgstr "не удалось создать большой объект %u: %s" -#: pg_backup_archiver.c:1486 pg_dump.c:3889 +#: pg_backup_archiver.c:1486 pg_dump.c:3897 #, c-format msgid "could not open large object %u: %s" msgstr "не удалось открыть большой объект %u: %s" @@ -872,14 +872,14 @@ msgid "could not find entry for ID %d" msgstr "не найдена запись для ID %d" #: pg_backup_archiver.c:1600 pg_backup_directory.c:219 -#: pg_backup_directory.c:613 +#: pg_backup_directory.c:618 #, c-format msgid "could not close TOC file: %m" msgstr "не удалось закрыть файл оглавления: %m" #: pg_backup_archiver.c:1687 pg_backup_custom.c:152 pg_backup_directory.c:333 -#: pg_backup_directory.c:600 pg_backup_directory.c:666 -#: pg_backup_directory.c:684 pg_dumpall.c:506 +#: pg_backup_directory.c:605 pg_backup_directory.c:671 +#: pg_backup_directory.c:689 pg_dumpall.c:506 #, c-format msgid "could not open output file \"%s\": %m" msgstr "не удалось открыть выходной файл \"%s\": %m" @@ -1291,8 +1291,8 @@ msgstr "не удалось переподключиться к базе" msgid "reconnection failed: %s" msgstr "переподключиться не удалось: %s" -#: pg_backup_db.c:190 pg_backup_db.c:264 pg_dump.c:788 pg_dump_sort.c:1213 -#: pg_dump_sort.c:1233 pg_dumpall.c:1753 pg_dumpall.c:1837 +#: pg_backup_db.c:190 pg_backup_db.c:264 pg_dump.c:789 pg_dump_sort.c:1427 +#: pg_dump_sort.c:1447 pg_dumpall.c:1753 pg_dumpall.c:1837 #, c-format msgid "%s" msgstr "%s" @@ -1340,7 +1340,7 @@ msgstr "ошибка в PQputCopyEnd: %s" msgid "COPY failed for table \"%s\": %s" msgstr "сбой команды COPY для таблицы \"%s\": %s" -#: pg_backup_db.c:521 pg_dump.c:2284 +#: pg_backup_db.c:521 pg_dump.c:2292 #, c-format msgid "unexpected extra results during COPY of table \"%s\"" msgstr "неожиданные лишние результаты получены при COPY для таблицы \"%s\"" @@ -1368,8 +1368,8 @@ msgstr "не удалось закрыть каталог \"%s\": %m" msgid "could not create directory \"%s\": %m" msgstr "не удалось создать каталог \"%s\": %m" -#: pg_backup_directory.c:357 pg_backup_directory.c:506 -#: pg_backup_directory.c:544 +#: pg_backup_directory.c:357 pg_backup_directory.c:511 +#: pg_backup_directory.c:549 #, c-format msgid "could not write to output file: %s" msgstr "не удалось записать в выходной файл: %s" @@ -1384,43 +1384,43 @@ msgstr "не удалось закрыть файл данных: %m" msgid "could not close data file \"%s\": %m" msgstr "не удалось закрыть файл данных \"%s\": %m" -#: pg_backup_directory.c:455 +#: pg_backup_directory.c:460 #, c-format msgid "could not open large object TOC file \"%s\" for input: %m" msgstr "" "не удалось открыть для чтения файл оглавления больших объектов \"%s\": %m" -#: pg_backup_directory.c:466 +#: pg_backup_directory.c:471 #, c-format msgid "invalid line in large object TOC file \"%s\": \"%s\"" msgstr "неверная строка в файле оглавления больших объектов \"%s\": \"%s\"" -#: pg_backup_directory.c:475 +#: pg_backup_directory.c:480 #, c-format msgid "error reading large object TOC file \"%s\"" msgstr "ошибка чтения файла оглавления больших объектов \"%s\"" -#: pg_backup_directory.c:479 +#: pg_backup_directory.c:484 #, c-format msgid "could not close large object TOC file \"%s\": %m" msgstr "не удалось закрыть файл оглавления больших объектов \"%s\": %m" -#: pg_backup_directory.c:702 +#: pg_backup_directory.c:707 #, c-format msgid "could not close LO data file: %m" msgstr "не удалось закрыть файл данных LO: %m" -#: pg_backup_directory.c:712 +#: pg_backup_directory.c:717 #, c-format msgid "could not write to LOs TOC file: %s" msgstr "не удалось записать в файл оглавления LO: %s" -#: pg_backup_directory.c:728 +#: pg_backup_directory.c:733 #, c-format msgid "could not close LOs TOC file: %m" msgstr "не удалось закрыть файл оглавления LO: %m" -#: pg_backup_directory.c:747 +#: pg_backup_directory.c:752 #, c-format msgid "file name too long: \"%s\"" msgstr "слишком длинное имя файла: \"%s\"" @@ -1522,7 +1522,7 @@ msgstr "" msgid "unrecognized section name: \"%s\"" msgstr "нераспознанное имя раздела: \"%s\"" -#: pg_backup_utils.c:55 pg_dump.c:694 pg_dump.c:711 pg_dumpall.c:370 +#: pg_backup_utils.c:55 pg_dump.c:695 pg_dump.c:712 pg_dumpall.c:370 #: pg_dumpall.c:380 pg_dumpall.c:388 pg_dumpall.c:396 pg_dumpall.c:403 #: pg_dumpall.c:413 pg_dumpall.c:488 pg_restore.c:307 pg_restore.c:323 #: pg_restore.c:337 @@ -1535,41 +1535,41 @@ msgstr "Для дополнительной информации попробу msgid "out of on_exit_nicely slots" msgstr "превышен предел обработчиков штатного выхода" -#: pg_dump.c:709 pg_dumpall.c:378 pg_restore.c:321 +#: pg_dump.c:710 pg_dumpall.c:378 pg_restore.c:321 #, c-format msgid "too many command-line arguments (first is \"%s\")" msgstr "слишком много аргументов командной строки (первый: \"%s\")" -#: pg_dump.c:728 pg_restore.c:344 +#: pg_dump.c:729 pg_restore.c:344 #, c-format msgid "options -s/--schema-only and -a/--data-only cannot be used together" msgstr "параметры -s/--schema-only и -a/--data-only исключают друг друга" -#: pg_dump.c:731 +#: pg_dump.c:732 #, c-format msgid "" "options -s/--schema-only and --include-foreign-data cannot be used together" msgstr "" "параметры -s/--schema-only и --include-foreign-data исключают друг друга" -#: pg_dump.c:734 +#: pg_dump.c:735 #, c-format msgid "option --include-foreign-data is not supported with parallel backup" msgstr "" "параметр --include-foreign-data не поддерживается при копировании в " "параллельном режиме" -#: pg_dump.c:737 pg_restore.c:347 +#: pg_dump.c:738 pg_restore.c:347 #, c-format msgid "options -c/--clean and -a/--data-only cannot be used together" msgstr "параметры -c/--clean и -a/--data-only исключают друг друга" -#: pg_dump.c:740 pg_dumpall.c:408 pg_restore.c:375 +#: pg_dump.c:741 pg_dumpall.c:408 pg_restore.c:375 #, c-format msgid "option --if-exists requires option -c/--clean" msgstr "параметр --if-exists требует указания -c/--clean" -#: pg_dump.c:747 +#: pg_dump.c:748 #, c-format msgid "" "option --on-conflict-do-nothing requires option --inserts, --rows-per-" @@ -1578,49 +1578,49 @@ msgstr "" "параметр --on-conflict-do-nothing требует указания --inserts, --rows-per-" "insert или --column-inserts" -#: pg_dump.c:776 +#: pg_dump.c:777 #, c-format msgid "unrecognized compression algorithm: \"%s\"" msgstr "нераспознанный алгоритм сжатия: \"%s\"" -#: pg_dump.c:783 +#: pg_dump.c:784 #, c-format msgid "invalid compression specification: %s" msgstr "неправильное указание сжатия: %s" -#: pg_dump.c:796 +#: pg_dump.c:797 #, c-format msgid "compression option \"%s\" is not currently supported by pg_dump" msgstr "pg_dump в настоящее время не поддерживает параметр сжатия \"%s\"" -#: pg_dump.c:808 +#: pg_dump.c:809 #, c-format msgid "parallel backup only supported by the directory format" msgstr "" "параллельное резервное копирование поддерживается только с форматом " "\"каталог\"" -#: pg_dump.c:854 +#: pg_dump.c:855 #, c-format msgid "last built-in OID is %u" msgstr "последний системный OID: %u" -#: pg_dump.c:863 +#: pg_dump.c:864 #, c-format msgid "no matching schemas were found" msgstr "соответствующие схемы не найдены" -#: pg_dump.c:880 +#: pg_dump.c:881 #, c-format msgid "no matching tables were found" msgstr "соответствующие таблицы не найдены" -#: pg_dump.c:908 +#: pg_dump.c:909 #, c-format msgid "no matching extensions were found" msgstr "соответствующие расширения не найдены" -#: pg_dump.c:1092 +#: pg_dump.c:1093 #, c-format msgid "" "%s dumps a database as a text file or to other formats.\n" @@ -1629,17 +1629,17 @@ msgstr "" "%s сохраняет резервную копию БД в текстовом файле или другом виде.\n" "\n" -#: pg_dump.c:1093 pg_dumpall.c:636 pg_restore.c:452 +#: pg_dump.c:1094 pg_dumpall.c:636 pg_restore.c:452 #, c-format msgid "Usage:\n" msgstr "Использование:\n" -#: pg_dump.c:1094 +#: pg_dump.c:1095 #, c-format msgid " %s [OPTION]... [DBNAME]\n" msgstr " %s [ПАРАМЕТР]... [ИМЯ_БД]\n" -#: pg_dump.c:1096 pg_dumpall.c:639 pg_restore.c:455 +#: pg_dump.c:1097 pg_dumpall.c:639 pg_restore.c:455 #, c-format msgid "" "\n" @@ -1648,12 +1648,12 @@ msgstr "" "\n" "Общие параметры:\n" -#: pg_dump.c:1097 +#: pg_dump.c:1098 #, c-format msgid " -f, --file=FILENAME output file or directory name\n" msgstr " -f, --file=ИМЯ имя выходного файла или каталога\n" -#: pg_dump.c:1098 +#: pg_dump.c:1099 #, c-format msgid "" " -F, --format=c|d|t|p output file format (custom, directory, tar,\n" @@ -1663,7 +1663,7 @@ msgstr "" " (пользовательский | каталог | tar |\n" " текстовый (по умолчанию))\n" -#: pg_dump.c:1100 +#: pg_dump.c:1101 #, c-format msgid " -j, --jobs=NUM use this many parallel jobs to dump\n" msgstr "" @@ -1671,18 +1671,18 @@ msgstr "" "число\n" " заданий\n" -#: pg_dump.c:1101 pg_dumpall.c:641 +#: pg_dump.c:1102 pg_dumpall.c:641 #, c-format msgid " -v, --verbose verbose mode\n" msgstr " -v, --verbose режим подробных сообщений\n" -#: pg_dump.c:1102 pg_dumpall.c:642 +#: pg_dump.c:1103 pg_dumpall.c:642 #, c-format msgid " -V, --version output version information, then exit\n" msgstr " -V, --version показать версию и выйти\n" # well-spelled: ИНФО -#: pg_dump.c:1103 +#: pg_dump.c:1104 #, c-format msgid "" " -Z, --compress=METHOD[:DETAIL]\n" @@ -1691,7 +1691,7 @@ msgstr "" " -Z, --compress=МЕТОД[:ДОП_ИНФО]\n" " выполнять сжатие как указано\n" -#: pg_dump.c:1105 pg_dumpall.c:643 +#: pg_dump.c:1106 pg_dumpall.c:643 #, c-format msgid "" " --lock-wait-timeout=TIMEOUT fail after waiting TIMEOUT for a table lock\n" @@ -1699,7 +1699,7 @@ msgstr "" " --lock-wait-timeout=ТАЙМ-АУТ прервать операцию при тайм-ауте блокировки " "таблицы\n" -#: pg_dump.c:1106 pg_dumpall.c:671 +#: pg_dump.c:1107 pg_dumpall.c:671 #, c-format msgid "" " --no-sync do not wait for changes to be written safely " @@ -1708,17 +1708,17 @@ msgstr "" " --no-sync не ждать надёжного сохранения изменений на " "диске\n" -#: pg_dump.c:1107 +#: pg_dump.c:1108 #, c-format msgid " --sync-method=METHOD set method for syncing files to disk\n" msgstr " --sync-method=МЕТОД метод синхронизации файлов с ФС\n" -#: pg_dump.c:1108 pg_dumpall.c:644 +#: pg_dump.c:1109 pg_dumpall.c:644 #, c-format msgid " -?, --help show this help, then exit\n" msgstr " -?, --help показать эту справку и выйти\n" -#: pg_dump.c:1110 pg_dumpall.c:645 +#: pg_dump.c:1111 pg_dumpall.c:645 #, c-format msgid "" "\n" @@ -1727,35 +1727,35 @@ msgstr "" "\n" "Параметры, управляющие выводом:\n" -#: pg_dump.c:1111 pg_dumpall.c:646 +#: pg_dump.c:1112 pg_dumpall.c:646 #, c-format msgid " -a, --data-only dump only the data, not the schema\n" msgstr " -a, --data-only выгрузить только данные, без схемы\n" -#: pg_dump.c:1112 +#: pg_dump.c:1113 #, c-format msgid " -b, --large-objects include large objects in dump\n" msgstr " -b, --large-objects выгрузить большие объекты\n" -#: pg_dump.c:1113 +#: pg_dump.c:1114 #, c-format msgid " --blobs (same as --large-objects, deprecated)\n" msgstr "" " --blobs (устаревшая альтернатива --large-objects)\n" -#: pg_dump.c:1114 +#: pg_dump.c:1115 #, c-format msgid " -B, --no-large-objects exclude large objects in dump\n" msgstr " -B, --no-large-objects исключить из выгрузки большие объекты\n" -#: pg_dump.c:1115 +#: pg_dump.c:1116 #, c-format msgid "" " --no-blobs (same as --no-large-objects, deprecated)\n" msgstr "" " --no-blobs (устаревшая альтернатива --no-large-objects)\n" -#: pg_dump.c:1116 pg_restore.c:466 +#: pg_dump.c:1117 pg_restore.c:466 #, c-format msgid "" " -c, --clean clean (drop) database objects before " @@ -1764,7 +1764,7 @@ msgstr "" " -c, --clean очистить (удалить) объекты БД при " "восстановлении\n" -#: pg_dump.c:1117 +#: pg_dump.c:1118 #, c-format msgid "" " -C, --create include commands to create database in dump\n" @@ -1772,28 +1772,28 @@ msgstr "" " -C, --create добавить в копию команды создания базы " "данных\n" -#: pg_dump.c:1118 +#: pg_dump.c:1119 #, c-format msgid " -e, --extension=PATTERN dump the specified extension(s) only\n" msgstr "" " -e, --extension=ШАБЛОН выгрузить только указанное расширение(я)\n" -#: pg_dump.c:1119 pg_dumpall.c:648 +#: pg_dump.c:1120 pg_dumpall.c:648 #, c-format msgid " -E, --encoding=ENCODING dump the data in encoding ENCODING\n" msgstr " -E, --encoding=КОДИРОВКА выгружать данные в заданной кодировке\n" -#: pg_dump.c:1120 +#: pg_dump.c:1121 #, c-format msgid " -n, --schema=PATTERN dump the specified schema(s) only\n" msgstr " -n, --schema=ШАБЛОН выгрузить только указанную схему(ы)\n" -#: pg_dump.c:1121 +#: pg_dump.c:1122 #, c-format msgid " -N, --exclude-schema=PATTERN do NOT dump the specified schema(s)\n" msgstr " -N, --exclude-schema=ШАБЛОН НЕ выгружать указанную схему(ы)\n" -#: pg_dump.c:1122 +#: pg_dump.c:1123 #, c-format msgid "" " -O, --no-owner skip restoration of object ownership in\n" @@ -1802,12 +1802,12 @@ msgstr "" " -O, --no-owner не восстанавливать владение объектами\n" " при использовании текстового формата\n" -#: pg_dump.c:1124 pg_dumpall.c:652 +#: pg_dump.c:1125 pg_dumpall.c:652 #, c-format msgid " -s, --schema-only dump only the schema, no data\n" msgstr " -s, --schema-only выгрузить только схему, без данных\n" -#: pg_dump.c:1125 +#: pg_dump.c:1126 #, c-format msgid "" " -S, --superuser=NAME superuser user name to use in plain-text " @@ -1816,27 +1816,27 @@ msgstr "" " -S, --superuser=ИМЯ имя пользователя, который будет задействован\n" " при восстановлении из текстового формата\n" -#: pg_dump.c:1126 +#: pg_dump.c:1127 #, c-format msgid " -t, --table=PATTERN dump only the specified table(s)\n" msgstr " -t, --table=ШАБЛОН выгрузить только указанную таблицу(ы)\n" -#: pg_dump.c:1127 +#: pg_dump.c:1128 #, c-format msgid " -T, --exclude-table=PATTERN do NOT dump the specified table(s)\n" msgstr " -T, --exclude-table=ШАБЛОН НЕ выгружать указанную таблицу(ы)\n" -#: pg_dump.c:1128 pg_dumpall.c:655 +#: pg_dump.c:1129 pg_dumpall.c:655 #, c-format msgid " -x, --no-privileges do not dump privileges (grant/revoke)\n" msgstr " -x, --no-privileges не выгружать права (назначение/отзыв)\n" -#: pg_dump.c:1129 pg_dumpall.c:656 +#: pg_dump.c:1130 pg_dumpall.c:656 #, c-format msgid " --binary-upgrade for use by upgrade utilities only\n" msgstr " --binary-upgrade только для утилит обновления БД\n" -#: pg_dump.c:1130 pg_dumpall.c:657 +#: pg_dump.c:1131 pg_dumpall.c:657 #, c-format msgid "" " --column-inserts dump data as INSERT commands with column " @@ -1845,7 +1845,7 @@ msgstr "" " --column-inserts выгружать данные в виде INSERT с именами " "столбцов\n" -#: pg_dump.c:1131 pg_dumpall.c:658 +#: pg_dump.c:1132 pg_dumpall.c:658 #, c-format msgid "" " --disable-dollar-quoting disable dollar quoting, use SQL standard " @@ -1854,7 +1854,7 @@ msgstr "" " --disable-dollar-quoting отключить спецстроки с $, выводить строки\n" " по стандарту SQL\n" -#: pg_dump.c:1132 pg_dumpall.c:659 pg_restore.c:483 +#: pg_dump.c:1133 pg_dumpall.c:659 pg_restore.c:483 #, c-format msgid "" " --disable-triggers disable triggers during data-only restore\n" @@ -1862,7 +1862,7 @@ msgstr "" " --disable-triggers отключить триггеры при восстановлении\n" " только данных, без схемы\n" -#: pg_dump.c:1133 +#: pg_dump.c:1134 #, c-format msgid "" " --enable-row-security enable row security (dump only content user " @@ -1873,12 +1873,12 @@ msgstr "" "только\n" " те данные, которые доступны пользователю)\n" -#: pg_dump.c:1135 +#: pg_dump.c:1136 #, c-format msgid " --exclude-extension=PATTERN do NOT dump the specified extension(s)\n" msgstr " --exclude-extension=ШАБЛОН НЕ выгружать указанное расширение(я)\n" -#: pg_dump.c:1136 +#: pg_dump.c:1137 #, c-format msgid "" " --exclude-table-and-children=PATTERN\n" @@ -1890,7 +1890,7 @@ msgstr "" " НЕ выгружать указанную таблицу(ы), а также\n" " её секции и дочерние таблицы\n" -#: pg_dump.c:1139 +#: pg_dump.c:1140 #, c-format msgid "" " --exclude-table-data=PATTERN do NOT dump data for the specified table(s)\n" @@ -1898,7 +1898,7 @@ msgstr "" " --exclude-table-data=ШАБЛОН НЕ выгружать данные указанной таблицы " "(таблиц)\n" -#: pg_dump.c:1140 +#: pg_dump.c:1141 #, c-format msgid "" " --exclude-table-data-and-children=PATTERN\n" @@ -1910,7 +1910,7 @@ msgstr "" "(таблиц),\n" " а также её секций и дочерних таблиц\n" -#: pg_dump.c:1143 pg_dumpall.c:661 +#: pg_dump.c:1144 pg_dumpall.c:661 #, c-format msgid "" " --extra-float-digits=NUM override default setting for " @@ -1918,7 +1918,7 @@ msgid "" msgstr "" " --extra-float-digits=ЧИСЛО переопределить значение extra_float_digits\n" -#: pg_dump.c:1144 +#: pg_dump.c:1145 #, c-format msgid "" " --filter=FILENAME include or exclude objects and data from " @@ -1929,13 +1929,13 @@ msgstr "" " дампа в соответствии с выражениями в этом " "файле\n" -#: pg_dump.c:1146 pg_dumpall.c:663 pg_restore.c:487 +#: pg_dump.c:1147 pg_dumpall.c:663 pg_restore.c:487 #, c-format msgid " --if-exists use IF EXISTS when dropping objects\n" msgstr "" " --if-exists применять IF EXISTS при удалении объектов\n" -#: pg_dump.c:1147 +#: pg_dump.c:1148 #, c-format msgid "" " --include-foreign-data=PATTERN\n" @@ -1946,7 +1946,7 @@ msgstr "" " включать в копию данные сторонних таблиц с\n" " серверов с именами, подпадающими под ШАБЛОН\n" -#: pg_dump.c:1150 pg_dumpall.c:664 +#: pg_dump.c:1151 pg_dumpall.c:664 #, c-format msgid "" " --inserts dump data as INSERT commands, rather than " @@ -1955,57 +1955,57 @@ msgstr "" " --inserts выгрузить данные в виде команд INSERT, не " "COPY\n" -#: pg_dump.c:1151 pg_dumpall.c:665 +#: pg_dump.c:1152 pg_dumpall.c:665 #, c-format msgid " --load-via-partition-root load partitions via the root table\n" msgstr "" " --load-via-partition-root загружать секции через главную таблицу\n" -#: pg_dump.c:1152 pg_dumpall.c:666 +#: pg_dump.c:1153 pg_dumpall.c:666 #, c-format msgid " --no-comments do not dump comments\n" msgstr " --no-comments не выгружать комментарии\n" -#: pg_dump.c:1153 pg_dumpall.c:667 +#: pg_dump.c:1154 pg_dumpall.c:667 #, c-format msgid " --no-publications do not dump publications\n" msgstr " --no-publications не выгружать публикации\n" -#: pg_dump.c:1154 pg_dumpall.c:669 +#: pg_dump.c:1155 pg_dumpall.c:669 #, c-format msgid " --no-security-labels do not dump security label assignments\n" msgstr "" " --no-security-labels не выгружать назначения меток безопасности\n" -#: pg_dump.c:1155 pg_dumpall.c:670 +#: pg_dump.c:1156 pg_dumpall.c:670 #, c-format msgid " --no-subscriptions do not dump subscriptions\n" msgstr " --no-subscriptions не выгружать подписки\n" -#: pg_dump.c:1156 pg_dumpall.c:672 +#: pg_dump.c:1157 pg_dumpall.c:672 #, c-format msgid " --no-table-access-method do not dump table access methods\n" msgstr " --no-table-access-method не выгружать табличные методы доступа\n" -#: pg_dump.c:1157 pg_dumpall.c:673 +#: pg_dump.c:1158 pg_dumpall.c:673 #, c-format msgid " --no-tablespaces do not dump tablespace assignments\n" msgstr "" " --no-tablespaces не выгружать назначения табличных " "пространств\n" -#: pg_dump.c:1158 pg_dumpall.c:674 +#: pg_dump.c:1159 pg_dumpall.c:674 #, c-format msgid " --no-toast-compression do not dump TOAST compression methods\n" msgstr " --no-toast-compression не выгружать методы сжатия TOAST\n" -#: pg_dump.c:1159 pg_dumpall.c:675 +#: pg_dump.c:1160 pg_dumpall.c:675 #, c-format msgid " --no-unlogged-table-data do not dump unlogged table data\n" msgstr "" " --no-unlogged-table-data не выгружать данные нежурналируемых таблиц\n" -#: pg_dump.c:1160 pg_dumpall.c:676 +#: pg_dump.c:1161 pg_dumpall.c:676 #, c-format msgid "" " --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT " @@ -2014,7 +2014,7 @@ msgstr "" " --on-conflict-do-nothing добавлять ON CONFLICT DO NOTHING в команды " "INSERT\n" -#: pg_dump.c:1161 pg_dumpall.c:677 +#: pg_dump.c:1162 pg_dumpall.c:677 #, c-format msgid "" " --quote-all-identifiers quote all identifiers, even if not key words\n" @@ -2022,7 +2022,7 @@ msgstr "" " --quote-all-identifiers заключать в кавычки все идентификаторы,\n" " а не только ключевые слова\n" -#: pg_dump.c:1162 pg_dumpall.c:678 +#: pg_dump.c:1163 pg_dumpall.c:678 #, c-format msgid "" " --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n" @@ -2030,7 +2030,7 @@ msgstr "" " --rows-per-insert=ЧИСЛО число строк в одном INSERT; подразумевает --" "inserts\n" -#: pg_dump.c:1163 +#: pg_dump.c:1164 #, c-format msgid "" " --section=SECTION dump named section (pre-data, data, or post-" @@ -2039,7 +2039,7 @@ msgstr "" " --section=РАЗДЕЛ выгрузить заданный раздел\n" " (pre-data, data или post-data)\n" -#: pg_dump.c:1164 +#: pg_dump.c:1165 #, c-format msgid "" " --serializable-deferrable wait until the dump can run without " @@ -2048,13 +2048,13 @@ msgstr "" " --serializable-deferrable дождаться момента для выгрузки данных без " "аномалий\n" -#: pg_dump.c:1165 +#: pg_dump.c:1166 #, c-format msgid " --snapshot=SNAPSHOT use given snapshot for the dump\n" msgstr "" " --snapshot=СНИМОК использовать при выгрузке заданный снимок\n" -#: pg_dump.c:1166 pg_restore.c:497 +#: pg_dump.c:1167 pg_restore.c:497 #, c-format msgid "" " --strict-names require table and/or schema include patterns " @@ -2067,7 +2067,7 @@ msgstr "" "минимум\n" " один объект\n" -#: pg_dump.c:1168 +#: pg_dump.c:1169 #, c-format msgid "" " --table-and-children=PATTERN dump only the specified table(s), including\n" @@ -2077,7 +2077,7 @@ msgstr "" "также\n" " её секции и дочерние таблицы\n" -#: pg_dump.c:1170 pg_dumpall.c:679 pg_restore.c:500 +#: pg_dump.c:1171 pg_dumpall.c:679 pg_restore.c:500 #, c-format msgid "" " --use-set-session-authorization\n" @@ -2089,7 +2089,7 @@ msgstr "" " устанавливать владельца, используя команды\n" " SET SESSION AUTHORIZATION вместо ALTER OWNER\n" -#: pg_dump.c:1174 pg_dumpall.c:683 pg_restore.c:504 +#: pg_dump.c:1175 pg_dumpall.c:683 pg_restore.c:504 #, c-format msgid "" "\n" @@ -2098,34 +2098,34 @@ msgstr "" "\n" "Параметры подключения:\n" -#: pg_dump.c:1175 +#: pg_dump.c:1176 #, c-format msgid " -d, --dbname=DBNAME database to dump\n" msgstr " -d, --dbname=БД имя базы данных для выгрузки\n" -#: pg_dump.c:1176 pg_dumpall.c:685 pg_restore.c:505 +#: pg_dump.c:1177 pg_dumpall.c:685 pg_restore.c:505 #, c-format msgid " -h, --host=HOSTNAME database server host or socket directory\n" msgstr "" " -h, --host=ИМЯ компьютер с сервером баз данных или каталог " "сокетов\n" -#: pg_dump.c:1177 pg_dumpall.c:687 pg_restore.c:506 +#: pg_dump.c:1178 pg_dumpall.c:687 pg_restore.c:506 #, c-format msgid " -p, --port=PORT database server port number\n" msgstr " -p, --port=ПОРТ номер порта сервера БД\n" -#: pg_dump.c:1178 pg_dumpall.c:688 pg_restore.c:507 +#: pg_dump.c:1179 pg_dumpall.c:688 pg_restore.c:507 #, c-format msgid " -U, --username=NAME connect as specified database user\n" msgstr " -U, --username=ИМЯ имя пользователя баз данных\n" -#: pg_dump.c:1179 pg_dumpall.c:689 pg_restore.c:508 +#: pg_dump.c:1180 pg_dumpall.c:689 pg_restore.c:508 #, c-format msgid " -w, --no-password never prompt for password\n" msgstr " -w, --no-password не запрашивать пароль\n" -#: pg_dump.c:1180 pg_dumpall.c:690 pg_restore.c:509 +#: pg_dump.c:1181 pg_dumpall.c:690 pg_restore.c:509 #, c-format msgid "" " -W, --password force password prompt (should happen " @@ -2133,12 +2133,12 @@ msgid "" msgstr "" " -W, --password запрашивать пароль всегда (обычно не требуется)\n" -#: pg_dump.c:1181 pg_dumpall.c:691 +#: pg_dump.c:1182 pg_dumpall.c:691 #, c-format msgid " --role=ROLENAME do SET ROLE before dump\n" msgstr " --role=ИМЯ_РОЛИ выполнить SET ROLE перед выгрузкой\n" -#: pg_dump.c:1183 +#: pg_dump.c:1184 #, c-format msgid "" "\n" @@ -2151,22 +2151,22 @@ msgstr "" "PGDATABASE.\n" "\n" -#: pg_dump.c:1185 pg_dumpall.c:695 pg_restore.c:516 +#: pg_dump.c:1186 pg_dumpall.c:695 pg_restore.c:516 #, c-format msgid "Report bugs to <%s>.\n" msgstr "Об ошибках сообщайте по адресу <%s>.\n" -#: pg_dump.c:1186 pg_dumpall.c:696 pg_restore.c:517 +#: pg_dump.c:1187 pg_dumpall.c:696 pg_restore.c:517 #, c-format msgid "%s home page: <%s>\n" msgstr "Домашняя страница %s: <%s>\n" -#: pg_dump.c:1205 pg_dumpall.c:518 +#: pg_dump.c:1206 pg_dumpall.c:518 #, c-format msgid "invalid client encoding \"%s\" specified" msgstr "указана неверная клиентская кодировка \"%s\"" -#: pg_dump.c:1353 +#: pg_dump.c:1354 #, c-format msgid "" "parallel dumps from standby servers are not supported by this server version" @@ -2174,170 +2174,170 @@ msgstr "" "выгрузка дампа в параллельном режиме с ведомых серверов не поддерживается " "данной версией сервера" -#: pg_dump.c:1418 +#: pg_dump.c:1419 #, c-format msgid "invalid output format \"%s\" specified" msgstr "указан неверный формат вывода: \"%s\"" -#: pg_dump.c:1459 pg_dump.c:1515 pg_dump.c:1568 pg_dumpall.c:1516 +#: pg_dump.c:1460 pg_dump.c:1516 pg_dump.c:1569 pg_dumpall.c:1516 #, c-format msgid "improper qualified name (too many dotted names): %s" msgstr "неверное полное имя (слишком много компонентов): %s" -#: pg_dump.c:1467 +#: pg_dump.c:1468 #, c-format msgid "no matching schemas were found for pattern \"%s\"" msgstr "схемы, соответствующие шаблону \"%s\", не найдены" -#: pg_dump.c:1520 +#: pg_dump.c:1521 #, c-format msgid "no matching extensions were found for pattern \"%s\"" msgstr "расширения, соответствующие шаблону \"%s\", не найдены" -#: pg_dump.c:1573 +#: pg_dump.c:1574 #, c-format msgid "no matching foreign servers were found for pattern \"%s\"" msgstr "сторонние серверы, соответствующие шаблону \"%s\", не найдены" -#: pg_dump.c:1644 +#: pg_dump.c:1645 #, c-format msgid "improper relation name (too many dotted names): %s" msgstr "неверное имя отношения (слишком много компонентов): %s" -#: pg_dump.c:1666 +#: pg_dump.c:1667 #, c-format msgid "no matching tables were found for pattern \"%s\"" msgstr "таблицы, соответствующие шаблону \"%s\", не найдены" -#: pg_dump.c:1693 +#: pg_dump.c:1694 #, c-format msgid "You are currently not connected to a database." msgstr "В данный момент вы не подключены к базе данных." -#: pg_dump.c:1696 +#: pg_dump.c:1697 #, c-format msgid "cross-database references are not implemented: %s" msgstr "ссылки между базами не реализованы: %s" -#: pg_dump.c:2155 +#: pg_dump.c:2163 #, c-format msgid "dumping contents of table \"%s.%s\"" msgstr "выгрузка содержимого таблицы \"%s.%s\"" -#: pg_dump.c:2265 +#: pg_dump.c:2273 #, c-format msgid "Dumping the contents of table \"%s\" failed: PQgetCopyData() failed." msgstr "Ошибка выгрузки таблицы \"%s\": сбой в PQgetCopyData()." -#: pg_dump.c:2266 pg_dump.c:2276 +#: pg_dump.c:2274 pg_dump.c:2284 #, c-format msgid "Error message from server: %s" msgstr "Сообщение об ошибке с сервера: %s" # skip-rule: language-mix -#: pg_dump.c:2267 pg_dump.c:2277 +#: pg_dump.c:2275 pg_dump.c:2285 #, c-format msgid "Command was: %s" msgstr "Выполнялась команда: %s" -#: pg_dump.c:2275 +#: pg_dump.c:2283 #, c-format msgid "Dumping the contents of table \"%s\" failed: PQgetResult() failed." msgstr "Ошибка выгрузки таблицы \"%s\": сбой в PQgetResult()." -#: pg_dump.c:2366 +#: pg_dump.c:2374 #, c-format msgid "wrong number of fields retrieved from table \"%s\"" msgstr "из таблицы \"%s\" получено неверное количество полей" -#: pg_dump.c:3068 +#: pg_dump.c:3076 #, c-format msgid "saving database definition" msgstr "сохранение определения базы данных" -#: pg_dump.c:3177 +#: pg_dump.c:3185 #, c-format msgid "unrecognized locale provider: %s" msgstr "нераспознанный провайдер локали: %s" -#: pg_dump.c:3538 +#: pg_dump.c:3546 #, c-format msgid "saving encoding = %s" msgstr "сохранение кодировки (%s)" -#: pg_dump.c:3563 +#: pg_dump.c:3571 #, c-format msgid "saving \"standard_conforming_strings = %s\"" msgstr "сохранение \"standard_conforming_strings = %s\"" -#: pg_dump.c:3602 +#: pg_dump.c:3610 #, c-format msgid "could not parse result of current_schemas()" msgstr "не удалось разобрать результат current_schemas()" -#: pg_dump.c:3621 +#: pg_dump.c:3629 #, c-format msgid "saving \"search_path = %s\"" msgstr "сохранение \"search_path = %s\"" -#: pg_dump.c:3657 +#: pg_dump.c:3665 #, c-format msgid "reading large objects" msgstr "чтение больших объектов" -#: pg_dump.c:3878 +#: pg_dump.c:3886 #, c-format msgid "saving large objects \"%s\"" msgstr "сохранение больших объектов \"%s\"" -#: pg_dump.c:3899 +#: pg_dump.c:3907 #, c-format msgid "error reading large object %u: %s" msgstr "ошибка чтения большого объекта %u: %s" -#: pg_dump.c:4002 +#: pg_dump.c:4010 #, c-format msgid "reading row-level security policies" msgstr "чтение политик защиты на уровне строк" -#: pg_dump.c:4143 +#: pg_dump.c:4151 #, c-format msgid "unexpected policy command type: %c" msgstr "нераспознанный тип команды в политике: %c" -#: pg_dump.c:4593 pg_dump.c:5151 pg_dump.c:12365 pg_dump.c:18264 -#: pg_dump.c:18266 pg_dump.c:18888 +#: pg_dump.c:4601 pg_dump.c:5159 pg_dump.c:12475 pg_dump.c:18399 +#: pg_dump.c:18401 pg_dump.c:19023 #, c-format msgid "could not parse %s array" msgstr "не удалось разобрать массив %s" -#: pg_dump.c:4807 +#: pg_dump.c:4815 #, c-format msgid "subscriptions not dumped because current user is not a superuser" msgstr "" "подписки не выгружены, так как текущий пользователь не суперпользователь" -#: pg_dump.c:5013 +#: pg_dump.c:5021 #, c-format msgid "subscription with OID %u does not exist" msgstr "подписка с OID %u не существует" -#: pg_dump.c:5020 +#: pg_dump.c:5028 #, c-format msgid "failed sanity check, table with OID %u not found" msgstr "нарушение целостности: таблица с OID %u не найдена" -#: pg_dump.c:5583 +#: pg_dump.c:5591 #, c-format msgid "could not find parent extension for %s %s" msgstr "не удалось найти родительское расширение для %s %s" -#: pg_dump.c:5728 +#: pg_dump.c:5736 #, c-format msgid "schema with OID %u does not exist" msgstr "схема с OID %u не существует" -#: pg_dump.c:7210 pg_dump.c:17635 +#: pg_dump.c:7246 pg_dump.c:17770 #, c-format msgid "" "failed sanity check, parent table with OID %u of sequence with OID %u not " @@ -2346,7 +2346,7 @@ msgstr "" "нарушение целостности: по OID %u не удалось найти родительскую таблицу " "последовательности с OID %u" -#: pg_dump.c:7355 +#: pg_dump.c:7391 #, c-format msgid "" "failed sanity check, table OID %u appearing in pg_partitioned_table not found" @@ -2354,18 +2354,18 @@ msgstr "" "нарушение целостности: таблица с OID %u, фигурирующим в " "pg_partitioned_table, не найдена" -#: pg_dump.c:7586 pg_dump.c:7860 pg_dump.c:8307 pg_dump.c:8921 pg_dump.c:9043 -#: pg_dump.c:9191 +#: pg_dump.c:7622 pg_dump.c:7896 pg_dump.c:8365 pg_dump.c:8979 pg_dump.c:9101 +#: pg_dump.c:9249 #, c-format msgid "unrecognized table OID %u" msgstr "нераспознанный OID таблицы %u" -#: pg_dump.c:7590 +#: pg_dump.c:7626 #, c-format msgid "unexpected index data for table \"%s\"" msgstr "неожиданно получены данные индекса для таблицы \"%s\"" -#: pg_dump.c:8092 +#: pg_dump.c:8150 #, c-format msgid "" "failed sanity check, parent table with OID %u of pg_rewrite entry with OID " @@ -2374,32 +2374,32 @@ msgstr "" "нарушение целостности: по OID %u не удалось найти родительскую таблицу для " "записи pg_rewrite с OID %u" -#: pg_dump.c:8925 +#: pg_dump.c:8983 #, c-format msgid "unexpected column data for table \"%s\"" msgstr "неожиданно получены данные столбцов для таблицы \"%s\"" -#: pg_dump.c:8954 +#: pg_dump.c:9012 #, c-format msgid "invalid column numbering in table \"%s\"" msgstr "неверная нумерация столбцов в таблице \"%s\"" -#: pg_dump.c:9005 +#: pg_dump.c:9063 #, c-format msgid "finding table default expressions" msgstr "поиск выражений по умолчанию для таблиц" -#: pg_dump.c:9047 +#: pg_dump.c:9105 #, c-format msgid "invalid adnum value %d for table \"%s\"" msgstr "неверное значение adnum (%d) в таблице \"%s\"" -#: pg_dump.c:9141 +#: pg_dump.c:9199 #, c-format msgid "finding table check constraints" msgstr "поиск ограничений-проверок для таблиц" -#: pg_dump.c:9195 +#: pg_dump.c:9253 #, c-format msgid "expected %d check constraint on table \"%s\" but found %d" msgid_plural "expected %d check constraints on table \"%s\" but found %d" @@ -2410,59 +2410,59 @@ msgstr[1] "" msgstr[2] "" "ожидалось %d ограничений-проверок для таблицы \"%s\", но найдено: %d" -#: pg_dump.c:9199 +#: pg_dump.c:9257 #, c-format msgid "The system catalogs might be corrupted." msgstr "Возможно, повреждены системные каталоги." -#: pg_dump.c:9889 +#: pg_dump.c:9947 #, c-format msgid "role with OID %u does not exist" msgstr "роль с OID %u не существует" -#: pg_dump.c:10001 pg_dump.c:10030 +#: pg_dump.c:10059 pg_dump.c:10088 #, c-format msgid "unsupported pg_init_privs entry: %u %u %d" msgstr "неподдерживаемая запись в pg_init_privs: %u %u %d" -#: pg_dump.c:10577 +#: pg_dump.c:10635 #, c-format msgid "missing metadata for large objects \"%s\"" msgstr "отсутствуют метаданные о больших объектах \"%s\"" -#: pg_dump.c:10860 +#: pg_dump.c:10918 #, c-format msgid "typtype of data type \"%s\" appears to be invalid" msgstr "у типа данных \"%s\" по-видимому неправильный тип типа" # TO REVEIW -#: pg_dump.c:12434 +#: pg_dump.c:12544 #, c-format msgid "unrecognized provolatile value for function \"%s\"" msgstr "недопустимое значение provolatile для функции \"%s\"" # TO REVEIW -#: pg_dump.c:12484 pg_dump.c:14380 +#: pg_dump.c:12594 pg_dump.c:14490 #, c-format msgid "unrecognized proparallel value for function \"%s\"" msgstr "недопустимое значение proparallel для функции \"%s\"" -#: pg_dump.c:12614 pg_dump.c:12720 pg_dump.c:12727 +#: pg_dump.c:12724 pg_dump.c:12830 pg_dump.c:12837 #, c-format msgid "could not find function definition for function with OID %u" msgstr "не удалось найти определение функции для функции с OID %u" -#: pg_dump.c:12653 +#: pg_dump.c:12763 #, c-format msgid "bogus value in pg_cast.castfunc or pg_cast.castmethod field" msgstr "неприемлемое значение в поле pg_cast.castfunc или pg_cast.castmethod" -#: pg_dump.c:12656 +#: pg_dump.c:12766 #, c-format msgid "bogus value in pg_cast.castmethod field" msgstr "неприемлемое значение в поле pg_cast.castmethod" -#: pg_dump.c:12746 +#: pg_dump.c:12856 #, c-format msgid "" "bogus transform definition, at least one of trffromsql and trftosql should " @@ -2471,63 +2471,63 @@ msgstr "" "неприемлемое определение преобразования (trffromsql или trftosql должно быть " "ненулевым)" -#: pg_dump.c:12763 +#: pg_dump.c:12873 #, c-format msgid "bogus value in pg_transform.trffromsql field" msgstr "неприемлемое значение в поле pg_transform.trffromsql" -#: pg_dump.c:12784 +#: pg_dump.c:12894 #, c-format msgid "bogus value in pg_transform.trftosql field" msgstr "неприемлемое значение в поле pg_transform.trftosql" -#: pg_dump.c:12929 +#: pg_dump.c:13039 #, c-format msgid "postfix operators are not supported anymore (operator \"%s\")" msgstr "постфиксные операторы больше не поддерживаются (оператор \"%s\")" -#: pg_dump.c:13099 +#: pg_dump.c:13209 #, c-format msgid "could not find operator with OID %s" msgstr "оператор с OID %s не найден" -#: pg_dump.c:13167 +#: pg_dump.c:13277 #, c-format msgid "invalid type \"%c\" of access method \"%s\"" msgstr "неверный тип \"%c\" метода доступа \"%s\"" -#: pg_dump.c:13841 pg_dump.c:13909 +#: pg_dump.c:13951 pg_dump.c:14019 #, c-format msgid "unrecognized collation provider: %s" msgstr "нераспознанный провайдер правил сортировки: %s" -#: pg_dump.c:13850 pg_dump.c:13857 pg_dump.c:13868 pg_dump.c:13878 -#: pg_dump.c:13893 +#: pg_dump.c:13960 pg_dump.c:13967 pg_dump.c:13978 pg_dump.c:13988 +#: pg_dump.c:14003 #, c-format msgid "invalid collation \"%s\"" msgstr "неверное правило сортировки \"%s\"" -#: pg_dump.c:14299 +#: pg_dump.c:14409 #, c-format msgid "unrecognized aggfinalmodify value for aggregate \"%s\"" msgstr "нераспознанное значение aggfinalmodify для агрегата \"%s\"" -#: pg_dump.c:14355 +#: pg_dump.c:14465 #, c-format msgid "unrecognized aggmfinalmodify value for aggregate \"%s\"" msgstr "нераспознанное значение aggmfinalmodify для агрегата \"%s\"" -#: pg_dump.c:15072 +#: pg_dump.c:15182 #, c-format msgid "unrecognized object type in default privileges: %d" msgstr "нераспознанный тип объекта в определении прав по умолчанию: %d" -#: pg_dump.c:15088 +#: pg_dump.c:15198 #, c-format msgid "could not parse default ACL list (%s)" msgstr "не удалось разобрать список прав по умолчанию (%s)" -#: pg_dump.c:15172 +#: pg_dump.c:15282 #, c-format msgid "" "could not parse initial ACL list (%s) or default (%s) for object \"%s\" (%s)" @@ -2535,20 +2535,20 @@ msgstr "" "не удалось разобрать изначальный список ACL (%s) или ACL по умолчанию (%s) " "для объекта \"%s\" (%s)" -#: pg_dump.c:15197 +#: pg_dump.c:15307 #, c-format msgid "could not parse ACL list (%s) or default (%s) for object \"%s\" (%s)" msgstr "" "не удалось разобрать список ACL (%s) или ACL по умолчанию (%s) для объекта " "\"%s\" (%s)" -#: pg_dump.c:15740 +#: pg_dump.c:15850 #, c-format msgid "query to obtain definition of view \"%s\" returned no data" msgstr "" "запрос на получение определения представления \"%s\" не возвратил данные" -#: pg_dump.c:15743 +#: pg_dump.c:15853 #, c-format msgid "" "query to obtain definition of view \"%s\" returned more than one definition" @@ -2556,49 +2556,49 @@ msgstr "" "запрос на получение определения представления \"%s\" возвратил несколько " "определений" -#: pg_dump.c:15750 +#: pg_dump.c:15860 #, c-format msgid "definition of view \"%s\" appears to be empty (length zero)" msgstr "определение представления \"%s\" пустое (длина равна нулю)" -#: pg_dump.c:15835 +#: pg_dump.c:15945 #, c-format msgid "WITH OIDS is not supported anymore (table \"%s\")" msgstr "свойство WITH OIDS больше не поддерживается (таблица \"%s\")" -#: pg_dump.c:16822 +#: pg_dump.c:16932 #, c-format msgid "invalid column number %d for table \"%s\"" msgstr "неверный номер столбца %d для таблицы \"%s\"" -#: pg_dump.c:16900 +#: pg_dump.c:17010 #, c-format msgid "could not parse index statistic columns" msgstr "не удалось разобрать столбцы статистики в индексе" -#: pg_dump.c:16902 +#: pg_dump.c:17012 #, c-format msgid "could not parse index statistic values" msgstr "не удалось разобрать значения статистики в индексе" -#: pg_dump.c:16904 +#: pg_dump.c:17014 #, c-format msgid "mismatched number of columns and values for index statistics" msgstr "" "столбцы, задающие статистику индекса, не соответствуют значениям по " "количеству" -#: pg_dump.c:17133 +#: pg_dump.c:17243 #, c-format msgid "missing index for constraint \"%s\"" msgstr "отсутствует индекс для ограничения \"%s\"" -#: pg_dump.c:17368 +#: pg_dump.c:17503 #, c-format msgid "unrecognized constraint type: %c" msgstr "нераспознанный тип ограничения: %c" -#: pg_dump.c:17469 pg_dump.c:17699 +#: pg_dump.c:17604 pg_dump.c:17834 #, c-format msgid "query to get data of sequence \"%s\" returned %d row (expected 1)" msgid_plural "" @@ -2613,12 +2613,12 @@ msgstr[2] "" "запрос на получение данных последовательности \"%s\" вернул %d строк " "(ожидалась 1)" -#: pg_dump.c:17501 +#: pg_dump.c:17636 #, c-format msgid "unrecognized sequence type: %s" msgstr "нераспознанный тип последовательности: %s" -#: pg_dump.c:18016 +#: pg_dump.c:18151 #, c-format msgid "" "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows " @@ -2627,53 +2627,53 @@ msgstr "" "запрос на получение правила \"%s\" для таблицы \"%s\" возвратил неверное " "число строк" -#: pg_dump.c:18169 +#: pg_dump.c:18304 #, c-format msgid "could not find referenced extension %u" msgstr "не удалось найти упомянутое расширение %u" -#: pg_dump.c:18268 +#: pg_dump.c:18403 #, c-format msgid "mismatched number of configurations and conditions for extension" msgstr "конфигурации расширения не соответствуют условиям по количеству" -#: pg_dump.c:18400 +#: pg_dump.c:18535 #, c-format msgid "reading dependency data" msgstr "чтение информации о зависимостях" -#: pg_dump.c:18486 +#: pg_dump.c:18621 #, c-format msgid "no referencing object %u %u" msgstr "нет подчинённого объекта %u %u" -#: pg_dump.c:18497 +#: pg_dump.c:18632 #, c-format msgid "no referenced object %u %u" msgstr "нет вышестоящего объекта %u %u" -#: pg_dump.c:18922 pg_dump.c:18960 pg_dumpall.c:2011 pg_restore.c:551 +#: pg_dump.c:19057 pg_dump.c:19095 pg_dumpall.c:2011 pg_restore.c:551 #: pg_restore.c:597 #, c-format msgid "%s filter for \"%s\" is not allowed" msgstr "фильтр %s для \"%s\" не допускается" -#: pg_dump_sort.c:424 +#: pg_dump_sort.c:635 #, c-format msgid "invalid dumpId %d" msgstr "неверный dumpId %d" -#: pg_dump_sort.c:430 +#: pg_dump_sort.c:641 #, c-format msgid "invalid dependency %d" msgstr "неверная зависимость %d" -#: pg_dump_sort.c:594 +#: pg_dump_sort.c:805 #, c-format msgid "could not identify dependency loop" msgstr "не удалось определить цикл зависимостей" -#: pg_dump_sort.c:1209 +#: pg_dump_sort.c:1423 #, c-format msgid "there are circular foreign-key constraints on this table:" msgid_plural "there are circular foreign-key constraints among these tables:" @@ -2681,7 +2681,7 @@ msgstr[0] "в следующей таблице зациклены ограни msgstr[1] "в следующих таблицах зациклены ограничения внешних ключей:" msgstr[2] "в следующих таблицах зациклены ограничения внешних ключей:" -#: pg_dump_sort.c:1214 +#: pg_dump_sort.c:1428 #, c-format msgid "" "You might not be able to restore the dump without using --disable-triggers " @@ -2690,7 +2690,7 @@ msgstr "" "Возможно, для восстановления базы потребуется использовать --disable-" "triggers или временно удалить ограничения." -#: pg_dump_sort.c:1215 +#: pg_dump_sort.c:1429 #, c-format msgid "" "Consider using a full dump instead of a --data-only dump to avoid this " @@ -2699,7 +2699,7 @@ msgstr "" "Во избежание этой проблемы, вероятно, стоит выгружать всю базу данных, а не " "только данные (--data-only)." -#: pg_dump_sort.c:1227 +#: pg_dump_sort.c:1441 #, c-format msgid "could not resolve dependency loop among these items:" msgstr "не удалось разрешить цикл зависимостей для следующих объектов:" diff --git a/src/bin/pg_dump/po/sv.po b/src/bin/pg_dump/po/sv.po index 8fbe8b3636b56..b9dc9a7a9e432 100644 --- a/src/bin/pg_dump/po/sv.po +++ b/src/bin/pg_dump/po/sv.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-08-27 15:52+0000\n" -"PO-Revision-Date: 2024-08-27 18:32+0200\n" +"POT-Creation-Date: 2025-05-09 17:09+0000\n" +"PO-Revision-Date: 2025-05-09 20:54+0200\n" "Last-Translator: Dennis Björklund \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -231,12 +231,12 @@ msgstr "%s måste vara i intervallet %d..%d" msgid "unrecognized sync method: %s" msgstr "okänd synkmetod: %s" -#: ../../fe_utils/string_utils.c:434 +#: ../../fe_utils/string_utils.c:587 #, c-format msgid "shell command argument contains a newline or carriage return: \"%s\"\n" msgstr "shell-kommandots argument innehåller nyrad eller vagnretur: \"%s\"\n" -#: ../../fe_utils/string_utils.c:607 +#: ../../fe_utils/string_utils.c:760 #, c-format msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "databasnamnet innehåller nyrad eller vagnretur: \"%s\"\n" @@ -502,13 +502,13 @@ msgid "could not read from input file: %s" msgstr "kunde inte läsa från infilen: %s" #: compress_gzip.c:295 compress_none.c:97 compress_none.c:139 -#: compress_zstd.c:373 pg_backup_custom.c:651 +#: compress_zstd.c:374 pg_backup_custom.c:651 #, c-format msgid "could not read from input file: %m" msgstr "kunde inte läsa från infilen: %m" #: compress_gzip.c:297 compress_lz4.c:630 compress_none.c:141 -#: compress_zstd.c:371 pg_backup_custom.c:649 pg_backup_directory.c:565 +#: compress_zstd.c:372 pg_backup_custom.c:649 pg_backup_directory.c:565 #: pg_backup_tar.c:740 pg_backup_tar.c:763 #, c-format msgid "could not read from input file: end of file" @@ -549,18 +549,18 @@ msgstr "kunde inte avsluta dekomprimering: %s" msgid "could not set compression parameter \"%s\": %s" msgstr "kunde inte sätta komprimeringsparameter \"%s\": %s" -#: compress_zstd.c:78 compress_zstd.c:231 compress_zstd.c:490 -#: compress_zstd.c:498 +#: compress_zstd.c:78 compress_zstd.c:232 compress_zstd.c:491 +#: compress_zstd.c:499 #, c-format msgid "could not initialize compression library" msgstr "kunde inte initierar komprimeringsbibliotek" -#: compress_zstd.c:194 compress_zstd.c:308 +#: compress_zstd.c:195 compress_zstd.c:309 #, c-format msgid "could not decompress data: %s" msgstr "kunde inte dekomprimera data: %s" -#: compress_zstd.c:501 +#: compress_zstd.c:502 #, c-format msgid "unhandled mode \"%s\"" msgstr "kan inte hantera läget \"%s\"" @@ -822,7 +822,7 @@ msgstr "återställer stort objekt med OID %u" msgid "could not create large object %u: %s" msgstr "kunde inte skapa stort objekt %u: %s" -#: pg_backup_archiver.c:1486 pg_dump.c:3888 +#: pg_backup_archiver.c:1486 pg_dump.c:3889 #, c-format msgid "could not open large object %u: %s" msgstr "kunde inte öppna stort objekt %u: %s" @@ -973,12 +973,12 @@ msgstr "kunde inte öppna stdout för append: %m" msgid "unrecognized file format \"%d\"" msgstr "känner inte igen filformat \"%d\"" -#: pg_backup_archiver.c:2527 pg_backup_archiver.c:4647 +#: pg_backup_archiver.c:2527 pg_backup_archiver.c:4648 #, c-format msgid "finished item %d %s %s" msgstr "klar med objekt %d %s %s" -#: pg_backup_archiver.c:2531 pg_backup_archiver.c:4660 +#: pg_backup_archiver.c:2531 pg_backup_archiver.c:4661 #, c-format msgid "worker process failed: exit code %d" msgstr "arbetsprocess misslyckades: felkod %d" @@ -998,137 +998,137 @@ msgstr "återeställa tabeller med WITH OIDS stöds inte längre" msgid "unrecognized encoding \"%s\"" msgstr "okänd teckenkodning \"%s\"" -#: pg_backup_archiver.c:2823 +#: pg_backup_archiver.c:2824 #, c-format msgid "invalid ENCODING item: %s" msgstr "ogiltigt ENCODING-val: %s" -#: pg_backup_archiver.c:2841 +#: pg_backup_archiver.c:2842 #, c-format msgid "invalid STDSTRINGS item: %s" msgstr "ogiltigt STDSTRINGS-val: %s" -#: pg_backup_archiver.c:2866 +#: pg_backup_archiver.c:2867 #, c-format msgid "schema \"%s\" not found" msgstr "schema \"%s\" hittades inte" -#: pg_backup_archiver.c:2873 +#: pg_backup_archiver.c:2874 #, c-format msgid "table \"%s\" not found" msgstr "tabell \"%s\" hittades inte" -#: pg_backup_archiver.c:2880 +#: pg_backup_archiver.c:2881 #, c-format msgid "index \"%s\" not found" msgstr "index \"%s\" hittades inte" -#: pg_backup_archiver.c:2887 +#: pg_backup_archiver.c:2888 #, c-format msgid "function \"%s\" not found" msgstr "funktion \"%s\" hittades inte" -#: pg_backup_archiver.c:2894 +#: pg_backup_archiver.c:2895 #, c-format msgid "trigger \"%s\" not found" msgstr "trigger \"%s\" hittades inte" -#: pg_backup_archiver.c:3325 +#: pg_backup_archiver.c:3326 #, c-format msgid "could not set session user to \"%s\": %s" msgstr "kunde inte sätta sessionsanvändare till \"%s\": %s" -#: pg_backup_archiver.c:3457 +#: pg_backup_archiver.c:3458 #, c-format msgid "could not set \"search_path\" to \"%s\": %s" msgstr "kunde inte sätta \"search_path\" till \"%s\": %s" -#: pg_backup_archiver.c:3518 +#: pg_backup_archiver.c:3519 #, c-format msgid "could not set \"default_tablespace\" to %s: %s" msgstr "kunde inte sätta \"default_tablespace\" till %s: %s" -#: pg_backup_archiver.c:3567 +#: pg_backup_archiver.c:3568 #, c-format msgid "could not set \"default_table_access_method\": %s" msgstr "kunde inte sätta \"default_table_access_method\": %s" -#: pg_backup_archiver.c:3616 +#: pg_backup_archiver.c:3617 #, c-format msgid "could not alter table access method: %s" msgstr "kunde inte ändra tabellaccessmetod: %s" -#: pg_backup_archiver.c:3717 +#: pg_backup_archiver.c:3718 #, c-format msgid "don't know how to set owner for object type \"%s\"" msgstr "vet inte hur man sätter ägare för objekttyp \"%s\"" -#: pg_backup_archiver.c:4004 +#: pg_backup_archiver.c:4005 #, c-format msgid "did not find magic string in file header" msgstr "kunde inte hitta den magiska strängen i filhuvudet" -#: pg_backup_archiver.c:4018 +#: pg_backup_archiver.c:4019 #, c-format msgid "unsupported version (%d.%d) in file header" msgstr "ej supportad version (%d.%d) i filhuvudet" -#: pg_backup_archiver.c:4023 +#: pg_backup_archiver.c:4024 #, c-format msgid "sanity check on integer size (%lu) failed" msgstr "riktighetskontroll på heltalsstorlek (%lu) misslyckades" -#: pg_backup_archiver.c:4027 +#: pg_backup_archiver.c:4028 #, c-format msgid "archive was made on a machine with larger integers, some operations might fail" msgstr "arkivet skapades på en maskin med större heltal, en del operationer kan misslyckas" -#: pg_backup_archiver.c:4037 +#: pg_backup_archiver.c:4038 #, c-format msgid "expected format (%d) differs from format found in file (%d)" msgstr "förväntat format (%d) skiljer sig från formatet som fanns i filen (%d)" -#: pg_backup_archiver.c:4059 +#: pg_backup_archiver.c:4060 #, c-format msgid "archive is compressed, but this installation does not support compression (%s) -- no data will be available" msgstr "arkivet är komprimerat, men denna installation stödjer inte komprimering (%s) -- ingen data kommer kunna läsas" -#: pg_backup_archiver.c:4095 +#: pg_backup_archiver.c:4096 #, c-format msgid "invalid creation date in header" msgstr "ogiltig skapandedatum i huvud" -#: pg_backup_archiver.c:4229 +#: pg_backup_archiver.c:4230 #, c-format msgid "processing item %d %s %s" msgstr "processar objekt %d %s %s" -#: pg_backup_archiver.c:4314 +#: pg_backup_archiver.c:4315 #, c-format msgid "entering main parallel loop" msgstr "går in i parallella huvudloopen" -#: pg_backup_archiver.c:4325 +#: pg_backup_archiver.c:4326 #, c-format msgid "skipping item %d %s %s" msgstr "hoppar över objekt %d %s %s" -#: pg_backup_archiver.c:4334 +#: pg_backup_archiver.c:4335 #, c-format msgid "launching item %d %s %s" msgstr "startar objekt %d %s %s" -#: pg_backup_archiver.c:4388 +#: pg_backup_archiver.c:4389 #, c-format msgid "finished main parallel loop" msgstr "klar med parallella huvudloopen" -#: pg_backup_archiver.c:4424 +#: pg_backup_archiver.c:4425 #, c-format msgid "processing missed item %d %s %s" msgstr "processar saknat objekt %d %s %s" -#: pg_backup_archiver.c:4966 +#: pg_backup_archiver.c:4967 #, c-format msgid "table \"%s\" could not be created, will not restore its data" msgstr "tabell \"%s\" kunde inte skapas, dess data kommer ej återställas" @@ -1215,12 +1215,12 @@ msgstr "komprimerare aktiv" msgid "could not get server_version from libpq" msgstr "kunde inte hämta serverversionen från libpq" -#: pg_backup_db.c:53 pg_dumpall.c:1830 +#: pg_backup_db.c:53 pg_dumpall.c:1879 #, c-format msgid "aborting because of server version mismatch" msgstr "avbryter då serverversionerna i matchar" -#: pg_backup_db.c:54 pg_dumpall.c:1831 +#: pg_backup_db.c:54 pg_dumpall.c:1880 #, c-format msgid "server version: %s; %s version: %s" msgstr "server version: %s; %s version: %s" @@ -1230,7 +1230,7 @@ msgstr "server version: %s; %s version: %s" msgid "already connected to a database" msgstr "är redan uppkopplad mot en databas" -#: pg_backup_db.c:128 pg_backup_db.c:178 pg_dumpall.c:1677 pg_dumpall.c:1779 +#: pg_backup_db.c:128 pg_backup_db.c:178 pg_dumpall.c:1726 pg_dumpall.c:1828 msgid "Password: " msgstr "Lösenord: " @@ -1245,17 +1245,17 @@ msgid "reconnection failed: %s" msgstr "återanslutning misslyckades: %s" #: pg_backup_db.c:190 pg_backup_db.c:264 pg_dump.c:788 pg_dump_sort.c:1213 -#: pg_dump_sort.c:1233 pg_dumpall.c:1704 pg_dumpall.c:1788 +#: pg_dump_sort.c:1233 pg_dumpall.c:1753 pg_dumpall.c:1837 #, c-format msgid "%s" msgstr "%s" -#: pg_backup_db.c:271 pg_dumpall.c:1893 pg_dumpall.c:1916 +#: pg_backup_db.c:271 pg_dumpall.c:1942 pg_dumpall.c:1965 #, c-format msgid "query failed: %s" msgstr "fråga misslyckades: %s" -#: pg_backup_db.c:273 pg_dumpall.c:1894 pg_dumpall.c:1917 +#: pg_backup_db.c:273 pg_dumpall.c:1943 pg_dumpall.c:1966 #, c-format msgid "Query was: %s" msgstr "Frågan var: %s" @@ -1291,7 +1291,7 @@ msgstr "fel returnerat av PQputCopyEnd: %s" msgid "COPY failed for table \"%s\": %s" msgstr "COPY misslyckades för tabell \"%s\": %s" -#: pg_backup_db.c:521 pg_dump.c:2283 +#: pg_backup_db.c:521 pg_dump.c:2284 #, c-format msgid "unexpected extra results during COPY of table \"%s\"" msgstr "oväntade extraresultat under kopiering (COPY) av tabell \"%s\"" @@ -1560,7 +1560,7 @@ msgstr "" "%s dumpar en databas som en textfil eller i andra format.\n" "\n" -#: pg_dump.c:1093 pg_dumpall.c:635 pg_restore.c:452 +#: pg_dump.c:1093 pg_dumpall.c:636 pg_restore.c:452 #, c-format msgid "Usage:\n" msgstr "Användning:\n" @@ -1570,7 +1570,7 @@ msgstr "Användning:\n" msgid " %s [OPTION]... [DBNAME]\n" msgstr " %s [FLAGGA]... [DBNAMN]\n" -#: pg_dump.c:1096 pg_dumpall.c:638 pg_restore.c:455 +#: pg_dump.c:1096 pg_dumpall.c:639 pg_restore.c:455 #, c-format msgid "" "\n" @@ -1598,12 +1598,12 @@ msgstr "" msgid " -j, --jobs=NUM use this many parallel jobs to dump\n" msgstr " -j, --jobs=NUM använd så här många parellella job för att dumpa\n" -#: pg_dump.c:1101 pg_dumpall.c:640 +#: pg_dump.c:1101 pg_dumpall.c:641 #, c-format msgid " -v, --verbose verbose mode\n" msgstr " -v, --verbose visa mer information\n" -#: pg_dump.c:1102 pg_dumpall.c:641 +#: pg_dump.c:1102 pg_dumpall.c:642 #, c-format msgid " -V, --version output version information, then exit\n" msgstr " -V, --version visa versionsinformation, avsluta sedan\n" @@ -1617,12 +1617,12 @@ msgstr "" " -Z, --compress=METOD[:DETALJ]\n" " komprimera som angivet\n" -#: pg_dump.c:1105 pg_dumpall.c:642 +#: pg_dump.c:1105 pg_dumpall.c:643 #, c-format msgid " --lock-wait-timeout=TIMEOUT fail after waiting TIMEOUT for a table lock\n" msgstr " --lock-wait-timeout=TIMEOUT misslyckas efter att ha väntat i TIMEOUT på tabellås\n" -#: pg_dump.c:1106 pg_dumpall.c:670 +#: pg_dump.c:1106 pg_dumpall.c:671 #, c-format msgid " --no-sync do not wait for changes to be written safely to disk\n" msgstr " --no-sync vänta inte på att ändingar säkert skrivits till disk\n" @@ -1632,12 +1632,12 @@ msgstr " --no-sync vänta inte på att ändingar säkert skr msgid " --sync-method=METHOD set method for syncing files to disk\n" msgstr " --sync-method=METOD sätt synkmetod för att synka filer till disk\n" -#: pg_dump.c:1108 pg_dumpall.c:643 +#: pg_dump.c:1108 pg_dumpall.c:644 #, c-format msgid " -?, --help show this help, then exit\n" msgstr " -?, --help visa denna hjälp, avsluta sedan\n" -#: pg_dump.c:1110 pg_dumpall.c:644 +#: pg_dump.c:1110 pg_dumpall.c:645 #, c-format msgid "" "\n" @@ -1646,7 +1646,7 @@ msgstr "" "\n" "Flaggor som styr utmatning:\n" -#: pg_dump.c:1111 pg_dumpall.c:645 +#: pg_dump.c:1111 pg_dumpall.c:646 #, c-format msgid " -a, --data-only dump only the data, not the schema\n" msgstr " -a, --data-only dumpa bara data, inte schema\n" @@ -1686,7 +1686,7 @@ msgstr " -C, --create inkludera kommandon för att skapa databa msgid " -e, --extension=PATTERN dump the specified extension(s) only\n" msgstr " -e, --extension=MALL dumpa bara de angivna utökningarna\n" -#: pg_dump.c:1119 pg_dumpall.c:647 +#: pg_dump.c:1119 pg_dumpall.c:648 #, c-format msgid " -E, --encoding=ENCODING dump the data in encoding ENCODING\n" msgstr " -E, --encoding=KODNING dumpa data i teckenkodning KODNING\n" @@ -1710,7 +1710,7 @@ msgstr "" " -O, --no-owner hoppa över återställande av objektägare i\n" " textformatdumpar\n" -#: pg_dump.c:1124 pg_dumpall.c:651 +#: pg_dump.c:1124 pg_dumpall.c:652 #, c-format msgid " -s, --schema-only dump only the schema, no data\n" msgstr " -s, --schema-only dumpa bara scheman, inte data\n" @@ -1730,27 +1730,27 @@ msgstr " -t, --table=MALL dumpa bara de angivna tabellerna\n" msgid " -T, --exclude-table=PATTERN do NOT dump the specified table(s)\n" msgstr " -T, --exclude-table=MALL dumpa INTE de angivna tabellerna\n" -#: pg_dump.c:1128 pg_dumpall.c:654 +#: pg_dump.c:1128 pg_dumpall.c:655 #, c-format msgid " -x, --no-privileges do not dump privileges (grant/revoke)\n" msgstr " -x, --no-privileges dumpa inte rättigheter (grant/revoke)\n" -#: pg_dump.c:1129 pg_dumpall.c:655 +#: pg_dump.c:1129 pg_dumpall.c:656 #, c-format msgid " --binary-upgrade for use by upgrade utilities only\n" msgstr " --binary-upgrade används bara av uppgraderingsverktyg\n" -#: pg_dump.c:1130 pg_dumpall.c:656 +#: pg_dump.c:1130 pg_dumpall.c:657 #, c-format msgid " --column-inserts dump data as INSERT commands with column names\n" msgstr " --column-inserts dumpa data som INSERT med kolumnnamn\n" -#: pg_dump.c:1131 pg_dumpall.c:657 +#: pg_dump.c:1131 pg_dumpall.c:658 #, c-format msgid " --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n" msgstr " --disable-dollar-quoting slå av dollar-citering, använd standard SQL-citering\n" -#: pg_dump.c:1132 pg_dumpall.c:658 pg_restore.c:483 +#: pg_dump.c:1132 pg_dumpall.c:659 pg_restore.c:483 #, c-format msgid " --disable-triggers disable triggers during data-only restore\n" msgstr " --disable-triggers slå av triggrar vid återställning av enbart data\n" @@ -1796,7 +1796,7 @@ msgstr "" " dumpa INTE data för angivna tabeller,\n" " inklusive barn och partitionstabeller\n" -#: pg_dump.c:1143 pg_dumpall.c:660 +#: pg_dump.c:1143 pg_dumpall.c:661 #, c-format msgid " --extra-float-digits=NUM override default setting for extra_float_digits\n" msgstr " --extra-float-digits=NUM övertrumfa standardinställningen för extra_float_digits\n" @@ -1811,7 +1811,7 @@ msgstr "" " från dump baserat på uttryck i FILNAMN\n" "\n" -#: pg_dump.c:1146 pg_dumpall.c:662 pg_restore.c:487 +#: pg_dump.c:1146 pg_dumpall.c:663 pg_restore.c:487 #, c-format msgid " --if-exists use IF EXISTS when dropping objects\n" msgstr " --if-exists använd IF EXISTS när objekt droppas\n" @@ -1827,67 +1827,67 @@ msgstr "" " inkludera data i främmande tabeller från\n" " främmande servrar som matchar MALL\n" -#: pg_dump.c:1150 pg_dumpall.c:663 +#: pg_dump.c:1150 pg_dumpall.c:664 #, c-format msgid " --inserts dump data as INSERT commands, rather than COPY\n" msgstr " --inserts dumpa data som INSERT, istället för COPY\n" -#: pg_dump.c:1151 pg_dumpall.c:664 +#: pg_dump.c:1151 pg_dumpall.c:665 #, c-format msgid " --load-via-partition-root load partitions via the root table\n" msgstr " --load-via-partition-root ladda partitioner via root-tabellen\n" -#: pg_dump.c:1152 pg_dumpall.c:665 +#: pg_dump.c:1152 pg_dumpall.c:666 #, c-format msgid " --no-comments do not dump comments\n" msgstr " --no-comments dumpa inte kommentarer\n" -#: pg_dump.c:1153 pg_dumpall.c:666 +#: pg_dump.c:1153 pg_dumpall.c:667 #, c-format msgid " --no-publications do not dump publications\n" msgstr " --no-publications dumpa inte publiceringar\n" -#: pg_dump.c:1154 pg_dumpall.c:668 +#: pg_dump.c:1154 pg_dumpall.c:669 #, c-format msgid " --no-security-labels do not dump security label assignments\n" msgstr " --no-security-labels dumpa inte tilldelning av säkerhetsetiketter\n" -#: pg_dump.c:1155 pg_dumpall.c:669 +#: pg_dump.c:1155 pg_dumpall.c:670 #, c-format msgid " --no-subscriptions do not dump subscriptions\n" msgstr " --no-subscriptions dumpa inte prenumereringar\n" -#: pg_dump.c:1156 pg_dumpall.c:671 +#: pg_dump.c:1156 pg_dumpall.c:672 #, c-format msgid " --no-table-access-method do not dump table access methods\n" msgstr " --no-table-access-method dumpa inte tabellaccessmetoder\n" -#: pg_dump.c:1157 pg_dumpall.c:672 +#: pg_dump.c:1157 pg_dumpall.c:673 #, c-format msgid " --no-tablespaces do not dump tablespace assignments\n" msgstr " --no-tablespaces dumpa inte användning av tabellutymmen\n" -#: pg_dump.c:1158 pg_dumpall.c:673 +#: pg_dump.c:1158 pg_dumpall.c:674 #, c-format msgid " --no-toast-compression do not dump TOAST compression methods\n" msgstr " --no-toast-compression dumpa inte komprimeringsmetoder för TOAST\n" -#: pg_dump.c:1159 pg_dumpall.c:674 +#: pg_dump.c:1159 pg_dumpall.c:675 #, c-format msgid " --no-unlogged-table-data do not dump unlogged table data\n" msgstr " --no-unlogged-table-data dumpa inte ologgad tabelldata\n" -#: pg_dump.c:1160 pg_dumpall.c:675 +#: pg_dump.c:1160 pg_dumpall.c:676 #, c-format msgid " --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n" msgstr " --on-conflict-do-nothing addera ON CONFLICT DO NOTHING till INSERT-kommandon\n" -#: pg_dump.c:1161 pg_dumpall.c:676 +#: pg_dump.c:1161 pg_dumpall.c:677 #, c-format msgid " --quote-all-identifiers quote all identifiers, even if not key words\n" msgstr " --quote-all-identifiers citera alla identifierar, även om de inte är nyckelord\n" -#: pg_dump.c:1162 pg_dumpall.c:677 +#: pg_dump.c:1162 pg_dumpall.c:678 #, c-format msgid " --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n" msgstr " --rows-per-insert=NRADER antal rader per INSERT; implicerar --inserts\n" @@ -1925,7 +1925,7 @@ msgstr "" " --table-and-children=MALL dumpa bara angivna tabell(er), inklusive\n" " barn och partitionstabeller\n" -#: pg_dump.c:1170 pg_dumpall.c:678 pg_restore.c:500 +#: pg_dump.c:1170 pg_dumpall.c:679 pg_restore.c:500 #, c-format msgid "" " --use-set-session-authorization\n" @@ -1936,7 +1936,7 @@ msgstr "" " använd kommandot SET SESSION AUTHORIZATION istället för\n" " kommandot ALTER OWNER för att sätta ägare\n" -#: pg_dump.c:1174 pg_dumpall.c:682 pg_restore.c:504 +#: pg_dump.c:1174 pg_dumpall.c:683 pg_restore.c:504 #, c-format msgid "" "\n" @@ -1950,32 +1950,32 @@ msgstr "" msgid " -d, --dbname=DBNAME database to dump\n" msgstr " -d, --dbname=DBNAMN databasens som skall dumpas\n" -#: pg_dump.c:1176 pg_dumpall.c:684 pg_restore.c:505 +#: pg_dump.c:1176 pg_dumpall.c:685 pg_restore.c:505 #, c-format msgid " -h, --host=HOSTNAME database server host or socket directory\n" msgstr " -h, --host=VÄRDNAMN databasens värdnamn eller socketkatalog\n" -#: pg_dump.c:1177 pg_dumpall.c:686 pg_restore.c:506 +#: pg_dump.c:1177 pg_dumpall.c:687 pg_restore.c:506 #, c-format msgid " -p, --port=PORT database server port number\n" msgstr " -p, --port=PORT databasens värdport\n" -#: pg_dump.c:1178 pg_dumpall.c:687 pg_restore.c:507 +#: pg_dump.c:1178 pg_dumpall.c:688 pg_restore.c:507 #, c-format msgid " -U, --username=NAME connect as specified database user\n" msgstr " -U, --username=NAMN anslut med datta användarnamn mot databasen\n" -#: pg_dump.c:1179 pg_dumpall.c:688 pg_restore.c:508 +#: pg_dump.c:1179 pg_dumpall.c:689 pg_restore.c:508 #, c-format msgid " -w, --no-password never prompt for password\n" msgstr " -w, --no-password fråga aldrig efter lösenord\n" -#: pg_dump.c:1180 pg_dumpall.c:689 pg_restore.c:509 +#: pg_dump.c:1180 pg_dumpall.c:690 pg_restore.c:509 #, c-format msgid " -W, --password force password prompt (should happen automatically)\n" msgstr " -W, --password fråga om lösenord (borde ske automatiskt)\n" -#: pg_dump.c:1181 pg_dumpall.c:690 +#: pg_dump.c:1181 pg_dumpall.c:691 #, c-format msgid " --role=ROLENAME do SET ROLE before dump\n" msgstr " --role=ROLLNAMN gör SET ROLE innan dumpen\n" @@ -1993,12 +1993,12 @@ msgstr "" "PGDATABASE att användas.\n" "\n" -#: pg_dump.c:1185 pg_dumpall.c:694 pg_restore.c:516 +#: pg_dump.c:1185 pg_dumpall.c:695 pg_restore.c:516 #, c-format msgid "Report bugs to <%s>.\n" msgstr "Rapportera fel till <%s>.\n" -#: pg_dump.c:1186 pg_dumpall.c:695 pg_restore.c:517 +#: pg_dump.c:1186 pg_dumpall.c:696 pg_restore.c:517 #, c-format msgid "%s home page: <%s>\n" msgstr "hemsida för %s: <%s>\n" @@ -2008,444 +2008,444 @@ msgstr "hemsida för %s: <%s>\n" msgid "invalid client encoding \"%s\" specified" msgstr "ogiltig klientteckenkodning \"%s\" angiven" -#: pg_dump.c:1352 +#: pg_dump.c:1353 #, c-format msgid "parallel dumps from standby servers are not supported by this server version" msgstr "parallella dumpar från standby-server stöds inte av denna serverversion" -#: pg_dump.c:1417 +#: pg_dump.c:1418 #, c-format msgid "invalid output format \"%s\" specified" msgstr "ogiltigt utdataformat \"%s\" angivet" -#: pg_dump.c:1458 pg_dump.c:1514 pg_dump.c:1567 pg_dumpall.c:1467 +#: pg_dump.c:1459 pg_dump.c:1515 pg_dump.c:1568 pg_dumpall.c:1516 #, c-format msgid "improper qualified name (too many dotted names): %s" msgstr "ej korrekt kvalificerat namn (för många namn med punkt): %s" -#: pg_dump.c:1466 +#: pg_dump.c:1467 #, c-format msgid "no matching schemas were found for pattern \"%s\"" msgstr "hittade inga matchande scheman för mallen \"%s\"" -#: pg_dump.c:1519 +#: pg_dump.c:1520 #, c-format msgid "no matching extensions were found for pattern \"%s\"" msgstr "hittade inga matchande utökningar för mallen \"%s\"" -#: pg_dump.c:1572 +#: pg_dump.c:1573 #, c-format msgid "no matching foreign servers were found for pattern \"%s\"" msgstr "hittade inga matchande främmande servrar för mallen \"%s\"" -#: pg_dump.c:1643 +#: pg_dump.c:1644 #, c-format msgid "improper relation name (too many dotted names): %s" msgstr "ej korrekt relationsnamn (för många namn med punkt): %s" -#: pg_dump.c:1665 +#: pg_dump.c:1666 #, c-format msgid "no matching tables were found for pattern \"%s\"" msgstr "hittade inga matchande tabeller för mallen \"%s\"" -#: pg_dump.c:1692 +#: pg_dump.c:1693 #, c-format msgid "You are currently not connected to a database." msgstr "Du är för närvarande inte uppkopplad mot en databas." -#: pg_dump.c:1695 +#: pg_dump.c:1696 #, c-format msgid "cross-database references are not implemented: %s" msgstr "referenser till andra databaser är inte implementerat: %s" -#: pg_dump.c:2154 +#: pg_dump.c:2155 #, c-format msgid "dumping contents of table \"%s.%s\"" msgstr "dumpar innehållet i tabell \"%s.%s\"" -#: pg_dump.c:2264 +#: pg_dump.c:2265 #, c-format msgid "Dumping the contents of table \"%s\" failed: PQgetCopyData() failed." msgstr "Dumpning av innehållet i tabellen \"%s\" misslyckades: PQendcopy() misslyckades." -#: pg_dump.c:2265 pg_dump.c:2275 +#: pg_dump.c:2266 pg_dump.c:2276 #, c-format msgid "Error message from server: %s" msgstr "Felmeddelandet från servern: %s" -#: pg_dump.c:2266 pg_dump.c:2276 +#: pg_dump.c:2267 pg_dump.c:2277 #, c-format msgid "Command was: %s" msgstr "Kommandot var: %s" -#: pg_dump.c:2274 +#: pg_dump.c:2275 #, c-format msgid "Dumping the contents of table \"%s\" failed: PQgetResult() failed." msgstr "Dumpning av innehållet i tabellen \"%s\" misslyckades: PQgetResult() misslyckades." -#: pg_dump.c:2365 +#: pg_dump.c:2366 #, c-format msgid "wrong number of fields retrieved from table \"%s\"" msgstr "fel antal fält hämtades för tabell \"%s\"" -#: pg_dump.c:3067 +#: pg_dump.c:3068 #, c-format msgid "saving database definition" msgstr "sparar databasdefinition" -#: pg_dump.c:3176 +#: pg_dump.c:3177 #, c-format msgid "unrecognized locale provider: %s" msgstr "okänd lokalleverantör: %s" -#: pg_dump.c:3537 +#: pg_dump.c:3538 #, c-format msgid "saving encoding = %s" msgstr "sparar kodning = %s" -#: pg_dump.c:3562 +#: pg_dump.c:3563 #, c-format msgid "saving \"standard_conforming_strings = %s\"" msgstr "sparar \"standard_conforming_strings = %s\"" -#: pg_dump.c:3601 +#: pg_dump.c:3602 #, c-format msgid "could not parse result of current_schemas()" msgstr "kunde inte parsa resultat från current_schemas()" -#: pg_dump.c:3620 +#: pg_dump.c:3621 #, c-format msgid "saving \"search_path = %s\"" msgstr "sparar \"search_path = %s\"" -#: pg_dump.c:3656 +#: pg_dump.c:3657 #, c-format msgid "reading large objects" msgstr "läser stora objekt" -#: pg_dump.c:3877 +#: pg_dump.c:3878 #, c-format msgid "saving large objects \"%s\"" msgstr "sparar stora objekt \"%s\"" -#: pg_dump.c:3898 +#: pg_dump.c:3899 #, c-format msgid "error reading large object %u: %s" msgstr "fel vid läsning av stort objekt %u: %s" -#: pg_dump.c:4001 +#: pg_dump.c:4002 #, c-format msgid "reading row-level security policies" msgstr "läser säkerhetspolicy på radnivå" -#: pg_dump.c:4142 +#: pg_dump.c:4143 #, c-format msgid "unexpected policy command type: %c" msgstr "oväntad kommandotyp för policy: %c" -#: pg_dump.c:4592 pg_dump.c:5150 pg_dump.c:12362 pg_dump.c:18246 -#: pg_dump.c:18248 pg_dump.c:18870 +#: pg_dump.c:4593 pg_dump.c:5151 pg_dump.c:12365 pg_dump.c:18264 +#: pg_dump.c:18266 pg_dump.c:18888 #, c-format msgid "could not parse %s array" msgstr "kunde inte parsa arrayen %s" -#: pg_dump.c:4806 +#: pg_dump.c:4807 #, c-format msgid "subscriptions not dumped because current user is not a superuser" msgstr "prenumerationer har inte dumpats få aktuell användare inte är en superuser" -#: pg_dump.c:5012 +#: pg_dump.c:5013 #, c-format msgid "subscription with OID %u does not exist" msgstr "prenumeration med OID %u existerar inte" -#: pg_dump.c:5019 +#: pg_dump.c:5020 #, c-format msgid "failed sanity check, table with OID %u not found" msgstr "misslyckades med riktighetskontroll, hittade inte tabell med OID %u" -#: pg_dump.c:5582 +#: pg_dump.c:5583 #, c-format msgid "could not find parent extension for %s %s" msgstr "kunde inte hitta föräldrautökning för %s %s" -#: pg_dump.c:5727 +#: pg_dump.c:5728 #, c-format msgid "schema with OID %u does not exist" msgstr "schema med OID %u existerar inte" -#: pg_dump.c:7209 pg_dump.c:17617 +#: pg_dump.c:7210 pg_dump.c:17635 #, c-format msgid "failed sanity check, parent table with OID %u of sequence with OID %u not found" msgstr "misslyckades med riktighetskontroll, föräldratabell med OID %u för sekvens med OID %u hittas inte" -#: pg_dump.c:7352 +#: pg_dump.c:7355 #, c-format msgid "failed sanity check, table OID %u appearing in pg_partitioned_table not found" msgstr "misslyckades med riktighetskontroll, hittade inte tabell med OID %u i pg_partitioned_table" -#: pg_dump.c:7583 pg_dump.c:7857 pg_dump.c:8304 pg_dump.c:8918 pg_dump.c:9040 -#: pg_dump.c:9188 +#: pg_dump.c:7586 pg_dump.c:7860 pg_dump.c:8307 pg_dump.c:8921 pg_dump.c:9043 +#: pg_dump.c:9191 #, c-format msgid "unrecognized table OID %u" msgstr "okänt tabell-OID %u" -#: pg_dump.c:7587 +#: pg_dump.c:7590 #, c-format msgid "unexpected index data for table \"%s\"" msgstr "oväntat indexdata för tabell \"%s\"" -#: pg_dump.c:8089 +#: pg_dump.c:8092 #, c-format msgid "failed sanity check, parent table with OID %u of pg_rewrite entry with OID %u not found" msgstr "misslyckades med riktighetskontroll, föräldratabell med OID %u för pg_rewrite-rad med OID %u hittades inte" -#: pg_dump.c:8922 +#: pg_dump.c:8925 #, c-format msgid "unexpected column data for table \"%s\"" msgstr "oväntad kolumndata för tabell \"%s\"" -#: pg_dump.c:8951 +#: pg_dump.c:8954 #, c-format msgid "invalid column numbering in table \"%s\"" msgstr "ogiltigt kolumnnumrering i tabell \"%s\"" -#: pg_dump.c:9002 +#: pg_dump.c:9005 #, c-format msgid "finding table default expressions" msgstr "hittar tabellers default-uttryck" -#: pg_dump.c:9044 +#: pg_dump.c:9047 #, c-format msgid "invalid adnum value %d for table \"%s\"" msgstr "felaktigt adnum-värde %d för tabell \"%s\"" -#: pg_dump.c:9138 +#: pg_dump.c:9141 #, c-format msgid "finding table check constraints" msgstr "hittar tabellers check-villkor" -#: pg_dump.c:9192 +#: pg_dump.c:9195 #, c-format msgid "expected %d check constraint on table \"%s\" but found %d" msgid_plural "expected %d check constraints on table \"%s\" but found %d" msgstr[0] "förväntade %d check-villkor för tabell \"%s\" men hittade %d" msgstr[1] "förväntade %d check-villkor för tabell \"%s\" men hittade %d" -#: pg_dump.c:9196 +#: pg_dump.c:9199 #, c-format msgid "The system catalogs might be corrupted." msgstr "Systemkatalogerna kan vara trasiga." -#: pg_dump.c:9886 +#: pg_dump.c:9889 #, c-format msgid "role with OID %u does not exist" msgstr "roll med OID %u existerar inte" -#: pg_dump.c:9998 pg_dump.c:10027 +#: pg_dump.c:10001 pg_dump.c:10030 #, c-format msgid "unsupported pg_init_privs entry: %u %u %d" msgstr "ogiltig pg_init_privs-post: %u %u %d" -#: pg_dump.c:10574 +#: pg_dump.c:10577 #, c-format msgid "missing metadata for large objects \"%s\"" msgstr "saknar metadata för stort objekt \"%s\"" -#: pg_dump.c:10857 +#: pg_dump.c:10860 #, c-format msgid "typtype of data type \"%s\" appears to be invalid" msgstr "typtype för datatyp \"%s\" verkar vara ogiltig" -#: pg_dump.c:12431 +#: pg_dump.c:12434 #, c-format msgid "unrecognized provolatile value for function \"%s\"" msgstr "okänt provolatile-värde för funktion \"%s\"" -#: pg_dump.c:12481 pg_dump.c:14377 +#: pg_dump.c:12484 pg_dump.c:14380 #, c-format msgid "unrecognized proparallel value for function \"%s\"" msgstr "okänt proparallel-värde för funktion \"%s\"" -#: pg_dump.c:12611 pg_dump.c:12717 pg_dump.c:12724 +#: pg_dump.c:12614 pg_dump.c:12720 pg_dump.c:12727 #, c-format msgid "could not find function definition for function with OID %u" msgstr "kunde inte hitta funktionsdefinitionen för funktion med OID %u" -#: pg_dump.c:12650 +#: pg_dump.c:12653 #, c-format msgid "bogus value in pg_cast.castfunc or pg_cast.castmethod field" msgstr "felaktigt värde i fältet pg_cast.castfunc eller pg_cast.castmethod" -#: pg_dump.c:12653 +#: pg_dump.c:12656 #, c-format msgid "bogus value in pg_cast.castmethod field" msgstr "felaktigt värde i fältet pg_cast.castmethod" -#: pg_dump.c:12743 +#: pg_dump.c:12746 #, c-format msgid "bogus transform definition, at least one of trffromsql and trftosql should be nonzero" msgstr "felaktig transform-definition, minst en av trffromsql och trftosql måste vara ickenoll" -#: pg_dump.c:12760 +#: pg_dump.c:12763 #, c-format msgid "bogus value in pg_transform.trffromsql field" msgstr "felaktigt värde i fältet pg_transform.trffromsql" -#: pg_dump.c:12781 +#: pg_dump.c:12784 #, c-format msgid "bogus value in pg_transform.trftosql field" msgstr "felaktigt värde i fältet pg_transform.trftosql" -#: pg_dump.c:12926 +#: pg_dump.c:12929 #, c-format msgid "postfix operators are not supported anymore (operator \"%s\")" msgstr "postfix-operatorer stöds inte längre (operator \"%s\")" -#: pg_dump.c:13096 +#: pg_dump.c:13099 #, c-format msgid "could not find operator with OID %s" msgstr "kunde inte hitta en operator med OID %s." -#: pg_dump.c:13164 +#: pg_dump.c:13167 #, c-format msgid "invalid type \"%c\" of access method \"%s\"" msgstr "ogiltig typ \"%c\" för accessmetod \"%s\"" -#: pg_dump.c:13838 pg_dump.c:13906 +#: pg_dump.c:13841 pg_dump.c:13909 #, c-format msgid "unrecognized collation provider: %s" msgstr "okänd jämförelseleverantör: %s" -#: pg_dump.c:13847 pg_dump.c:13854 pg_dump.c:13865 pg_dump.c:13875 -#: pg_dump.c:13890 +#: pg_dump.c:13850 pg_dump.c:13857 pg_dump.c:13868 pg_dump.c:13878 +#: pg_dump.c:13893 #, c-format msgid "invalid collation \"%s\"" msgstr "ogiltig jämförelse \"%s\"" -#: pg_dump.c:14296 +#: pg_dump.c:14299 #, c-format msgid "unrecognized aggfinalmodify value for aggregate \"%s\"" msgstr "okänt aggfinalmodify-värde för aggregat \"%s\"" -#: pg_dump.c:14352 +#: pg_dump.c:14355 #, c-format msgid "unrecognized aggmfinalmodify value for aggregate \"%s\"" msgstr "okänt aggmfinalmodify-värde för aggregat \"%s\"" -#: pg_dump.c:15069 +#: pg_dump.c:15072 #, c-format msgid "unrecognized object type in default privileges: %d" msgstr "okänd objekttyp i standardrättigheter: %d" -#: pg_dump.c:15085 +#: pg_dump.c:15088 #, c-format msgid "could not parse default ACL list (%s)" msgstr "kunde inte parsa standard-ACL-lista (%s)" -#: pg_dump.c:15169 +#: pg_dump.c:15172 #, c-format msgid "could not parse initial ACL list (%s) or default (%s) for object \"%s\" (%s)" msgstr "kunde inte parsa initial ACL-lista (%s) eller default (%s) för objekt \"%s\" (%s)" -#: pg_dump.c:15194 +#: pg_dump.c:15197 #, c-format msgid "could not parse ACL list (%s) or default (%s) for object \"%s\" (%s)" msgstr "kunde inte parsa ACL-lista (%s) eller default (%s) för objekt \"%s\" (%s)" -#: pg_dump.c:15737 +#: pg_dump.c:15740 #, c-format msgid "query to obtain definition of view \"%s\" returned no data" msgstr "fråga för att hämta definition av vy \"%s\" returnerade ingen data" -#: pg_dump.c:15740 +#: pg_dump.c:15743 #, c-format msgid "query to obtain definition of view \"%s\" returned more than one definition" msgstr "fråga för att hämta definition av vy \"%s\" returnerade mer än en definition" -#: pg_dump.c:15747 +#: pg_dump.c:15750 #, c-format msgid "definition of view \"%s\" appears to be empty (length zero)" msgstr "definition av vy \"%s\" verkar vara tom (längd noll)" -#: pg_dump.c:15832 +#: pg_dump.c:15835 #, c-format msgid "WITH OIDS is not supported anymore (table \"%s\")" msgstr "WITH OIDS stöds inte längre (tabell \"%s\")" -#: pg_dump.c:16819 +#: pg_dump.c:16822 #, c-format msgid "invalid column number %d for table \"%s\"" msgstr "ogiltigt kolumnnummer %d för tabell \"%s\"" -#: pg_dump.c:16897 +#: pg_dump.c:16900 #, c-format msgid "could not parse index statistic columns" msgstr "kunde inte parsa kolumn i indexstatistik" -#: pg_dump.c:16899 +#: pg_dump.c:16902 #, c-format msgid "could not parse index statistic values" msgstr "kunde inte parsa värden i indexstatistik" -#: pg_dump.c:16901 +#: pg_dump.c:16904 #, c-format msgid "mismatched number of columns and values for index statistics" msgstr "antal kolumner och värden stämmer inte i indexstatistik" -#: pg_dump.c:17116 +#: pg_dump.c:17133 #, c-format msgid "missing index for constraint \"%s\"" msgstr "saknar index för integritetsvillkor \"%s\"" -#: pg_dump.c:17351 +#: pg_dump.c:17368 #, c-format msgid "unrecognized constraint type: %c" msgstr "oväntad integritetsvillkorstyp: %c" -#: pg_dump.c:17452 pg_dump.c:17681 +#: pg_dump.c:17469 pg_dump.c:17699 #, c-format msgid "query to get data of sequence \"%s\" returned %d row (expected 1)" msgid_plural "query to get data of sequence \"%s\" returned %d rows (expected 1)" msgstr[0] "fråga för att hämta data för sekvens \"%s\" returnerade %d rad (förväntade 1)" msgstr[1] "fråga för att hämta data för sekvens \"%s\" returnerade %d rader (förväntade 1)" -#: pg_dump.c:17484 +#: pg_dump.c:17501 #, c-format msgid "unrecognized sequence type: %s" msgstr "okänd sekvenstyp: %s" -#: pg_dump.c:17998 +#: pg_dump.c:18016 #, c-format msgid "query to get rule \"%s\" for table \"%s\" failed: wrong number of rows returned" msgstr "fråga för att hämta regel \"%s\" för tabell \"%s\" misslyckades: fel antal rader returnerades" -#: pg_dump.c:18151 +#: pg_dump.c:18169 #, c-format msgid "could not find referenced extension %u" msgstr "kunde inte hitta refererad utökning %u" -#: pg_dump.c:18250 +#: pg_dump.c:18268 #, c-format msgid "mismatched number of configurations and conditions for extension" msgstr "antal konfigurationer och villkor stämmer inte för utökning" -#: pg_dump.c:18382 +#: pg_dump.c:18400 #, c-format msgid "reading dependency data" msgstr "läser beroendedata" -#: pg_dump.c:18468 +#: pg_dump.c:18486 #, c-format msgid "no referencing object %u %u" msgstr "inget refererande objekt %u %u" -#: pg_dump.c:18479 +#: pg_dump.c:18497 #, c-format msgid "no referenced object %u %u" msgstr "inget refererat objekt %u %u" -#: pg_dump.c:18904 pg_dump.c:18942 pg_dumpall.c:1962 pg_restore.c:551 +#: pg_dump.c:18922 pg_dump.c:18960 pg_dumpall.c:2011 pg_restore.c:551 #: pg_restore.c:597 #, c-format msgid "%s filter for \"%s\" is not allowed" @@ -2518,7 +2518,7 @@ msgstr "flaggorna \"bara globala\" (-g) och \"bara tabellutrymmen\" (-t) kan int msgid "options -r/--roles-only and -t/--tablespaces-only cannot be used together" msgstr "flaggorna \"bara roller\" (-r) och \"bara tabellutrymmen\" (-t) kan inte användas tillsammans" -#: pg_dumpall.c:474 pg_dumpall.c:1771 +#: pg_dumpall.c:474 pg_dumpall.c:1820 #, c-format msgid "could not connect to database \"%s\"" msgstr "kunde inte ansluta till databasen \"%s\"" @@ -2532,7 +2532,7 @@ msgstr "" "kunde inte ansluta till databasen \"postgres\" eller \"template1\"\n" "Ange en annan databas." -#: pg_dumpall.c:634 +#: pg_dumpall.c:635 #, c-format msgid "" "%s extracts a PostgreSQL database cluster into an SQL script file.\n" @@ -2541,72 +2541,72 @@ msgstr "" "%s extraherar ett PostgreSQL databaskluster till en SQL-scriptfil.\n" "\n" -#: pg_dumpall.c:636 +#: pg_dumpall.c:637 #, c-format msgid " %s [OPTION]...\n" msgstr " %s [FLAGGA]...\n" -#: pg_dumpall.c:639 +#: pg_dumpall.c:640 #, c-format msgid " -f, --file=FILENAME output file name\n" msgstr " -f, --file=FILENAME utdatafilnamn\n" -#: pg_dumpall.c:646 +#: pg_dumpall.c:647 #, c-format msgid " -c, --clean clean (drop) databases before recreating\n" msgstr " -c, --clean nollställ (drop) databaser innan återskapning\n" -#: pg_dumpall.c:648 +#: pg_dumpall.c:649 #, c-format msgid " -g, --globals-only dump only global objects, no databases\n" msgstr " -g, --globals-only dumpa bara globala objekt, inte databaser\n" -#: pg_dumpall.c:649 pg_restore.c:475 +#: pg_dumpall.c:650 pg_restore.c:475 #, c-format msgid " -O, --no-owner skip restoration of object ownership\n" msgstr " -O, --no-owner återställ inte objektägare\n" -#: pg_dumpall.c:650 +#: pg_dumpall.c:651 #, c-format msgid " -r, --roles-only dump only roles, no databases or tablespaces\n" msgstr " -r, --roles-only dumpa endast roller, inte databaser eller tabellutrymmen\n" -#: pg_dumpall.c:652 +#: pg_dumpall.c:653 #, c-format msgid " -S, --superuser=NAME superuser user name to use in the dump\n" msgstr " -S, --superuser=NAMN namn på superuser för användning i dumpen\n" -#: pg_dumpall.c:653 +#: pg_dumpall.c:654 #, c-format msgid " -t, --tablespaces-only dump only tablespaces, no databases or roles\n" msgstr " -t, --tablespaces-only dumpa endasdt tabellutrymmen, inte databaser eller roller\n" -#: pg_dumpall.c:659 +#: pg_dumpall.c:660 #, c-format msgid " --exclude-database=PATTERN exclude databases whose name matches PATTERN\n" msgstr " --exclude-database=MALL uteslut databaser vars namn matchar MALL\n" -#: pg_dumpall.c:661 +#: pg_dumpall.c:662 #, c-format msgid " --filter=FILENAME exclude databases based on expressions in FILENAME\n" msgstr " --filter=FILENAMN exkludera databaser givet uttryck i FILENAMN\n" -#: pg_dumpall.c:667 +#: pg_dumpall.c:668 #, c-format msgid " --no-role-passwords do not dump passwords for roles\n" msgstr " --no-role-passwords dumpa inte lösenord för roller\n" -#: pg_dumpall.c:683 +#: pg_dumpall.c:684 #, c-format msgid " -d, --dbname=CONNSTR connect using connection string\n" msgstr " -d, --dbname=ANSLSTR anslut med anslutningssträng\n" -#: pg_dumpall.c:685 +#: pg_dumpall.c:686 #, c-format msgid " -l, --database=DBNAME alternative default database\n" msgstr " -l, --database=DBNAMN alternativ standarddatabas\n" -#: pg_dumpall.c:692 +#: pg_dumpall.c:693 #, c-format msgid "" "\n" @@ -2618,67 +2618,73 @@ msgstr "" "Om -f/--file inte används så kommer SQL-skriptet skriva till standard ut.\n" "\n" -#: pg_dumpall.c:837 +#: pg_dumpall.c:838 #, c-format msgid "role name starting with \"pg_\" skipped (%s)" msgstr "rollnamn som startar med \"pg_\" hoppas över (%s)" -#: pg_dumpall.c:1059 +#. translator: %s represents a numeric role OID +#: pg_dumpall.c:1055 pg_dumpall.c:1113 pg_dumpall.c:1122 +#, c-format +msgid "found orphaned pg_auth_members entry for role %s" +msgstr "hittade föräldralös pg_auth_members-post för roll %s" + +#: pg_dumpall.c:1088 #, c-format msgid "could not find a legal dump ordering for memberships in role \"%s\"" msgstr "kunde inte hitta en korrekt dumpordning för medlemskap i rollen \"%s\"" -#: pg_dumpall.c:1194 +#: pg_dumpall.c:1243 #, c-format msgid "could not parse ACL list (%s) for parameter \"%s\"" msgstr "kunde inte parsa ACL-listan (%s) för parameter \"%s\"" -#: pg_dumpall.c:1321 +#: pg_dumpall.c:1370 #, c-format msgid "could not parse ACL list (%s) for tablespace \"%s\"" msgstr "kunde inte tolka ACL-listan (%s) för tabellutrymme \"%s\"" -#: pg_dumpall.c:1528 +#: pg_dumpall.c:1577 #, c-format msgid "excluding database \"%s\"" msgstr "utesluter databas \"%s\"" -#: pg_dumpall.c:1532 +#: pg_dumpall.c:1581 #, c-format msgid "dumping database \"%s\"" msgstr "dumpar databas \"%s\"" -#: pg_dumpall.c:1563 +#: pg_dumpall.c:1612 #, c-format msgid "pg_dump failed on database \"%s\", exiting" msgstr "pg_dump misslyckades med databas \"%s\", avslutar" -#: pg_dumpall.c:1569 +#: pg_dumpall.c:1618 #, c-format msgid "could not re-open the output file \"%s\": %m" msgstr "kunde inte öppna om utdatafilen \"%s\": %m" -#: pg_dumpall.c:1613 +#: pg_dumpall.c:1662 #, c-format msgid "running \"%s\"" msgstr "kör \"%s\"" -#: pg_dumpall.c:1814 +#: pg_dumpall.c:1863 #, c-format msgid "could not get server version" msgstr "kunde inte hämta serverversionen" -#: pg_dumpall.c:1817 +#: pg_dumpall.c:1866 #, c-format msgid "could not parse server version \"%s\"" msgstr "kunde inte tolka versionsträngen \"%s\"" -#: pg_dumpall.c:1887 pg_dumpall.c:1910 +#: pg_dumpall.c:1936 pg_dumpall.c:1959 #, c-format msgid "executing %s" msgstr "kör: %s" -#: pg_dumpall.c:1982 +#: pg_dumpall.c:2031 msgid "unsupported filter object" msgstr "filterobjektet stöds inte" diff --git a/src/bin/pg_rewind/po/ru.po b/src/bin/pg_rewind/po/ru.po index fef2d25775b31..cb4ea9a533076 100644 --- a/src/bin/pg_rewind/po/ru.po +++ b/src/bin/pg_rewind/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_rewind (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-09-07 13:07+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -982,52 +982,52 @@ msgstr "" msgid "contrecord is requested by %X/%X" msgstr "в позиции %X/%X запрошено продолжение записи" -#: xlogreader.c:669 xlogreader.c:1134 +#: xlogreader.c:669 xlogreader.c:1144 #, c-format msgid "invalid record length at %X/%X: expected at least %u, got %u" msgstr "" "неверная длина записи в позиции %X/%X: ожидалось минимум %u, получено %u" -#: xlogreader.c:758 +#: xlogreader.c:759 #, c-format msgid "there is no contrecord flag at %X/%X" msgstr "нет флага contrecord в позиции %X/%X" -#: xlogreader.c:771 +#: xlogreader.c:772 #, c-format msgid "invalid contrecord length %u (expected %lld) at %X/%X" msgstr "неверная длина contrecord: %u (ожидалась %lld) в позиции %X/%X" -#: xlogreader.c:1142 +#: xlogreader.c:1152 #, c-format msgid "invalid resource manager ID %u at %X/%X" msgstr "неверный ID менеджера ресурсов %u в позиции %X/%X" -#: xlogreader.c:1155 xlogreader.c:1171 +#: xlogreader.c:1165 xlogreader.c:1181 #, c-format msgid "record with incorrect prev-link %X/%X at %X/%X" msgstr "запись с неверной ссылкой назад %X/%X в позиции %X/%X" -#: xlogreader.c:1209 +#: xlogreader.c:1219 #, c-format msgid "incorrect resource manager data checksum in record at %X/%X" msgstr "" "некорректная контрольная сумма данных менеджера ресурсов в записи в позиции " "%X/%X" -#: xlogreader.c:1243 +#: xlogreader.c:1253 #, c-format msgid "invalid magic number %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверное магическое число %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1258 xlogreader.c:1300 +#: xlogreader.c:1268 xlogreader.c:1310 #, c-format msgid "invalid info bits %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверные информационные биты %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1274 +#: xlogreader.c:1284 #, c-format msgid "" "WAL file is from different database system: WAL file database system " @@ -1036,7 +1036,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: в нём указан идентификатор системы БД " "%llu, а идентификатор системы pg_control: %llu" -#: xlogreader.c:1282 +#: xlogreader.c:1292 #, c-format msgid "" "WAL file is from different database system: incorrect segment size in page " @@ -1045,7 +1045,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный размер сегмента в заголовке " "страницы" -#: xlogreader.c:1288 +#: xlogreader.c:1298 #, c-format msgid "" "WAL file is from different database system: incorrect XLOG_BLCKSZ in page " @@ -1054,12 +1054,12 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный XLOG_BLCKSZ в заголовке " "страницы" -#: xlogreader.c:1320 +#: xlogreader.c:1330 #, c-format msgid "unexpected pageaddr %X/%X in WAL segment %s, LSN %X/%X, offset %u" msgstr "неожиданный pageaddr %X/%X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1346 +#: xlogreader.c:1356 #, c-format msgid "" "out-of-sequence timeline ID %u (after %u) in WAL segment %s, LSN %X/%X, " @@ -1068,23 +1068,23 @@ msgstr "" "нарушение последовательности ID линии времени %u (после %u) в сегменте WAL " "%s, LSN %X/%X, смещение %u" -#: xlogreader.c:1749 +#: xlogreader.c:1759 #, c-format msgid "out-of-order block_id %u at %X/%X" msgstr "идентификатор блока %u идёт не по порядку в позиции %X/%X" -#: xlogreader.c:1773 +#: xlogreader.c:1783 #, c-format msgid "BKPBLOCK_HAS_DATA set, but no data included at %X/%X" msgstr "BKPBLOCK_HAS_DATA установлен, но данных в позиции %X/%X нет" -#: xlogreader.c:1780 +#: xlogreader.c:1790 #, c-format msgid "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X" msgstr "" "BKPBLOCK_HAS_DATA не установлен, но длина данных равна %u в позиции %X/%X" -#: xlogreader.c:1816 +#: xlogreader.c:1826 #, c-format msgid "" "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at " @@ -1093,21 +1093,21 @@ msgstr "" "BKPIMAGE_HAS_HOLE установлен, но для пропуска заданы смещение %u и длина %u " "при длине образа блока %u в позиции %X/%X" -#: xlogreader.c:1832 +#: xlogreader.c:1842 #, c-format msgid "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X" msgstr "" "BKPIMAGE_HAS_HOLE не установлен, но для пропуска заданы смещение %u и длина " "%u в позиции %X/%X" -#: xlogreader.c:1846 +#: xlogreader.c:1856 #, c-format msgid "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X" msgstr "" "BKPIMAGE_COMPRESSED установлен, но длина образа блока равна %u в позиции %X/" "%X" -#: xlogreader.c:1861 +#: xlogreader.c:1871 #, c-format msgid "" "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image " @@ -1116,41 +1116,41 @@ msgstr "" "ни BKPIMAGE_HAS_HOLE, ни BKPIMAGE_COMPRESSED не установлены, но длина образа " "блока равна %u в позиции %X/%X" -#: xlogreader.c:1877 +#: xlogreader.c:1887 #, c-format msgid "BKPBLOCK_SAME_REL set but no previous rel at %X/%X" msgstr "" "BKPBLOCK_SAME_REL установлен, но предыдущее значение не задано в позиции %X/" "%X" -#: xlogreader.c:1889 +#: xlogreader.c:1899 #, c-format msgid "invalid block_id %u at %X/%X" msgstr "неверный идентификатор блока %u в позиции %X/%X" -#: xlogreader.c:1956 +#: xlogreader.c:1966 #, c-format msgid "record with invalid length at %X/%X" msgstr "запись с неверной длиной в позиции %X/%X" -#: xlogreader.c:1982 +#: xlogreader.c:1992 #, c-format msgid "could not locate backup block with ID %d in WAL record" msgstr "не удалось найти копию блока с ID %d в записи журнала WAL" -#: xlogreader.c:2066 +#: xlogreader.c:2076 #, c-format msgid "could not restore image at %X/%X with invalid block %d specified" msgstr "" "не удалось восстановить образ в позиции %X/%X с указанным неверным блоком %d" -#: xlogreader.c:2073 +#: xlogreader.c:2083 #, c-format msgid "could not restore image at %X/%X with invalid state, block %d" msgstr "" "не удалось восстановить образ в позиции %X/%X с неверным состоянием, блок %d" -#: xlogreader.c:2100 xlogreader.c:2117 +#: xlogreader.c:2110 xlogreader.c:2127 #, c-format msgid "" "could not restore image at %X/%X compressed with %s not supported by build, " @@ -1159,7 +1159,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый методом %s, который не " "поддерживается этой сборкой, блок %d" -#: xlogreader.c:2126 +#: xlogreader.c:2136 #, c-format msgid "" "could not restore image at %X/%X compressed with unknown method, block %d" @@ -1167,7 +1167,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый неизвестным методом, " "блок %d" -#: xlogreader.c:2134 +#: xlogreader.c:2144 #, c-format msgid "could not decompress image at %X/%X, block %d" msgstr "не удалось развернуть образ в позиции %X/%X, блок %d" diff --git a/src/bin/pg_upgrade/po/de.po b/src/bin/pg_upgrade/po/de.po index 545eab9df793e..dc70c589a0bce 100644 --- a/src/bin/pg_upgrade/po/de.po +++ b/src/bin/pg_upgrade/po/de.po @@ -1,13 +1,13 @@ # German message translation file for pg_upgrade -# Copyright (C) 2024 PostgreSQL Global Development Group +# Copyright (C) 2025 PostgreSQL Global Development Group # This file is distributed under the same license as the PostgreSQL package. # msgid "" msgstr "" "Project-Id-Version: pg_upgrade (PostgreSQL) 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-10-30 10:50+0000\n" -"PO-Revision-Date: 2024-10-30 16:05+0100\n" +"POT-Creation-Date: 2025-08-07 12:38+0000\n" +"PO-Revision-Date: 2025-08-07 23:48+0200\n" "Last-Translator: Peter Eisentraut \n" "Language-Team: German \n" "Language: de\n" @@ -45,21 +45,21 @@ msgstr "Benutzer existiert nicht" msgid "user name lookup failure: error code %lu" msgstr "Fehler beim Nachschlagen des Benutzernamens: Fehlercode %lu" -#: ../../fe_utils/string_utils.c:434 +#: ../../fe_utils/string_utils.c:587 #, c-format msgid "shell command argument contains a newline or carriage return: \"%s\"\n" msgstr "Argument des Shell-Befehls enthält Newline oder Carriage Return: »%s«\n" -#: ../../fe_utils/string_utils.c:607 +#: ../../fe_utils/string_utils.c:760 #, c-format msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "Datenbankname enthält Newline oder Carriage Return: »%s«\n" -#: check.c:111 +#: check.c:112 msgid "Checking for system-defined composite types in user tables" msgstr "Prüfe auf systemdefinierte zusammengesetzte Typen in Benutzertabellen" -#: check.c:118 +#: check.c:119 msgid "" "Your installation contains system-defined composite types in user tables.\n" "These type OIDs are not stable across PostgreSQL versions,\n" @@ -72,11 +72,11 @@ msgstr "" "nicht aktualisiert werden. Sie können die Problemspalten löschen\n" "und das Upgrade neu starten.\n" -#: check.c:132 +#: check.c:133 msgid "Checking for incompatible \"line\" data type" msgstr "Prüfe auf inkompatiblen Datentyp »line«" -#: check.c:137 +#: check.c:138 msgid "" "Your installation contains the \"line\" data type in user tables.\n" "This data type changed its internal and input/output format\n" @@ -90,11 +90,11 @@ msgstr "" "Cluster gegenwärtig nicht aktualisiert werden. Sie können die\n" "Problemspalten löschen und das Upgrade neu starten.\n" -#: check.c:154 +#: check.c:155 msgid "Checking for reg* data types in user tables" msgstr "Prüfe auf reg*-Datentypen in Benutzertabellen" -#: check.c:181 +#: check.c:182 msgid "" "Your installation contains one of the reg* data types in user tables.\n" "These data types reference system OIDs that are not preserved by\n" @@ -107,11 +107,11 @@ msgstr "" "gegenwärtig nicht aktualiert werden. Sie können die Problemspalten\n" "löschen und das Upgrade neu starten.\n" -#: check.c:193 +#: check.c:194 msgid "Checking for incompatible \"aclitem\" data type" msgstr "Prüfe auf inkompatiblen Datentyp »aclitem«" -#: check.c:198 +#: check.c:199 msgid "" "Your installation contains the \"aclitem\" data type in user tables.\n" "The internal format of \"aclitem\" changed in PostgreSQL version 16\n" @@ -124,11 +124,11 @@ msgstr "" "werden. Sie können die Problemspalten löschen und das Upgrade neu\n" "starten.\n" -#: check.c:217 +#: check.c:218 msgid "Checking for invalid \"unknown\" user columns" msgstr "Prüfe auf ungültige Benutzerspalten mit Typ »unknown«" -#: check.c:222 +#: check.c:223 msgid "" "Your installation contains the \"unknown\" data type in user tables.\n" "This data type is no longer allowed in tables, so this cluster\n" @@ -141,11 +141,11 @@ msgstr "" "werden. Sie können die Problemspalten löschen und das Upgrade neu\n" "starten.\n" -#: check.c:239 +#: check.c:240 msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "Prüfe auf ungültige Benutzerspalten mit Typ »sql_identifier«" -#: check.c:244 +#: check.c:245 msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" "The on-disk format for this data type has changed, so this\n" @@ -158,11 +158,11 @@ msgstr "" "werden. Sie können die Problemspalten löschen und das Upgrade neu\n" "starten.\n" -#: check.c:255 +#: check.c:256 msgid "Checking for incompatible \"jsonb\" data type in user tables" msgstr "Prüfe auf inkompatiblen Datentyp »jsonb« in Benutzertabellen" -#: check.c:260 +#: check.c:261 msgid "" "Your installation contains the \"jsonb\" data type in user tables.\n" "The internal format of \"jsonb\" changed during 9.4 beta so this\n" @@ -175,11 +175,11 @@ msgstr "" "aktualisiert werden. Sie können die Problemspalten löschen und das\n" "Upgrade neu starten.\n" -#: check.c:272 +#: check.c:273 msgid "Checking for removed \"abstime\" data type in user tables" msgstr "Prüfe auf entfernten Datentyp »abstime« in Benutzertabellen" -#: check.c:277 +#: check.c:278 msgid "" "Your installation contains the \"abstime\" data type in user tables.\n" "The \"abstime\" type has been removed in PostgreSQL version 12,\n" @@ -193,11 +193,11 @@ msgstr "" "löschen oder in einen anderen Datentyp ändern und das Upgrade neu\n" "starten.\n" -#: check.c:285 +#: check.c:286 msgid "Checking for removed \"reltime\" data type in user tables" msgstr "Prüfe auf entfernten Datentyp »reltime« in Benutzertabellen" -#: check.c:290 +#: check.c:291 msgid "" "Your installation contains the \"reltime\" data type in user tables.\n" "The \"reltime\" type has been removed in PostgreSQL version 12,\n" @@ -211,11 +211,11 @@ msgstr "" "löschen oder in einen anderen Datentyp ändern und das Upgrade neu\n" "starten.\n" -#: check.c:298 +#: check.c:299 msgid "Checking for removed \"tinterval\" data type in user tables" msgstr "Prüfe auf entfernten Datentyp »tinterval« in Benutzertabellen" -#: check.c:303 +#: check.c:304 msgid "" "Your installation contains the \"tinterval\" data type in user tables.\n" "The \"tinterval\" type has been removed in PostgreSQL version 12,\n" @@ -229,34 +229,34 @@ msgstr "" "löschen oder in einen anderen Datentyp ändern und das Upgrade neu\n" "starten.\n" -#: check.c:345 +#: check.c:346 #, c-format msgid "Checking data type usage" msgstr "Prüfe Verwendung von Datentypen" -#: check.c:480 +#: check.c:481 #, c-format msgid "failed check: %s" msgstr "fehlgeschlagene Prüfung: %s" -#: check.c:483 +#: check.c:484 msgid "A list of the problem columns is in the file:" msgstr "Eine Liste der Problemspalten ist in der Datei:" -#: check.c:495 check.c:963 check.c:1136 check.c:1251 check.c:1345 check.c:1473 -#: check.c:1549 check.c:1613 check.c:1686 check.c:1865 check.c:1884 -#: check.c:1953 check.c:2005 file.c:378 file.c:415 function.c:189 option.c:493 -#: version.c:79 version.c:177 +#: check.c:496 check.c:971 check.c:1144 check.c:1259 check.c:1353 check.c:1481 +#: check.c:1557 check.c:1634 check.c:1700 check.c:1773 check.c:1952 +#: check.c:1971 check.c:2040 check.c:2092 file.c:378 file.c:415 function.c:189 +#: option.c:493 version.c:79 version.c:177 #, c-format msgid "could not open file \"%s\": %m" msgstr "konnte Datei »%s« nicht öffnen: %m" -#: check.c:522 +#: check.c:523 #, c-format msgid "Data type checks failed: %s" msgstr "Datentypprüfungen fehlgeschlagen: %s" -#: check.c:563 +#: check.c:564 #, c-format msgid "" "Performing Consistency Checks on Old Live Server\n" @@ -265,7 +265,7 @@ msgstr "" "Führe Konsistenzprüfungen am alten laufenden Server durch\n" "---------------------------------------------------------" -#: check.c:569 +#: check.c:570 #, c-format msgid "" "Performing Consistency Checks\n" @@ -274,7 +274,7 @@ msgstr "" "Führe Konsistenzprüfungen durch\n" "-------------------------------" -#: check.c:718 +#: check.c:726 #, c-format msgid "" "\n" @@ -283,7 +283,7 @@ msgstr "" "\n" "*Cluster sind kompatibel*" -#: check.c:726 +#: check.c:734 #, c-format msgid "" "\n" @@ -295,7 +295,7 @@ msgstr "" "neuen Cluster neu mit initdb initialisieren, bevor fortgesetzt\n" "werden kann." -#: check.c:767 +#: check.c:775 #, c-format msgid "" "Optimizer statistics are not transferred by pg_upgrade.\n" @@ -306,7 +306,7 @@ msgstr "" "den neuen Server starten, sollte Sie diesen Befehl ausführen:\n" " %s/vacuumdb %s--all --analyze-in-stages" -#: check.c:773 +#: check.c:781 #, c-format msgid "" "Running this script will delete the old cluster's data files:\n" @@ -315,7 +315,7 @@ msgstr "" "Mit diesem Skript können die Dateien des alten Clusters gelöscht werden:\n" " %s" -#: check.c:778 +#: check.c:786 #, c-format msgid "" "Could not create a script to delete the old cluster's data files\n" @@ -328,57 +328,57 @@ msgstr "" "Datenverzeichnis des neuen Clusters im alten Cluster-Verzeichnis\n" "liegen. Der Inhalt des alten Clusters muss von Hand gelöscht werden." -#: check.c:790 +#: check.c:798 #, c-format msgid "Checking cluster versions" msgstr "Prüfe Cluster-Versionen" -#: check.c:802 +#: check.c:810 #, c-format msgid "This utility can only upgrade from PostgreSQL version %s and later." msgstr "Dieses Programm kann nur Upgrades von PostgreSQL Version %s oder später durchführen." -#: check.c:807 +#: check.c:815 #, c-format msgid "This utility can only upgrade to PostgreSQL version %s." msgstr "Dieses Programm kann nur Upgrades auf PostgreSQL Version %s durchführen." -#: check.c:816 +#: check.c:824 #, c-format msgid "This utility cannot be used to downgrade to older major PostgreSQL versions." msgstr "Dieses Programm kann keine Downgrades auf ältere Hauptversionen von PostgreSQL durchführen." -#: check.c:821 +#: check.c:829 #, c-format msgid "Old cluster data and binary directories are from different major versions." msgstr "Die Daten- und Programmverzeichnisse des alten Clusters stammen von verschiedenen Hauptversionen." -#: check.c:824 +#: check.c:832 #, c-format msgid "New cluster data and binary directories are from different major versions." msgstr "Die Daten- und Programmverzeichnisse des neuen Clusters stammen von verschiedenen Hauptversionen." -#: check.c:839 +#: check.c:847 #, c-format msgid "When checking a live server, the old and new port numbers must be different." msgstr "Wenn ein laufender Server geprüft wird, müssen die alte und die neue Portnummer verschieden sein." -#: check.c:859 +#: check.c:867 #, c-format msgid "New cluster database \"%s\" is not empty: found relation \"%s.%s\"" msgstr "Datenbank »%s« im neuen Cluster ist nicht leer: Relation »%s.%s« gefunden" -#: check.c:882 +#: check.c:890 #, c-format msgid "Checking for new cluster tablespace directories" msgstr "Prüfe Tablespace-Verzeichnisse des neuen Clusters" -#: check.c:893 +#: check.c:901 #, c-format msgid "new cluster tablespace directory already exists: \"%s\"" msgstr "Tablespace-Verzeichnis für neuen Cluster existiert bereits: »%s«" -#: check.c:926 +#: check.c:934 #, c-format msgid "" "\n" @@ -387,7 +387,7 @@ msgstr "" "\n" "WARNUNG: das neue Datenverzeichnis sollte nicht im alten Datenverzeichnis, d.h. %s, liegen" -#: check.c:950 +#: check.c:958 #, c-format msgid "" "\n" @@ -396,53 +396,54 @@ msgstr "" "\n" "WARNUNG: benutzerdefinierte Tablespace-Pfade sollten nicht im Datenverzeichnis, d.h. %s, liegen" -#: check.c:960 +#: check.c:968 #, c-format msgid "Creating script to delete old cluster" msgstr "Erzeuge Skript zum Löschen des alten Clusters" -#: check.c:1014 +#: check.c:1022 #, c-format msgid "could not add execute permission to file \"%s\": %m" msgstr "konnte Datei »%s« nicht ausführbar machen: %m" -#: check.c:1034 +#: check.c:1042 #, c-format msgid "Checking database user is the install user" msgstr "Prüfe ob der Datenbankbenutzer der Installationsbenutzer ist" -#: check.c:1050 +#: check.c:1058 #, c-format msgid "database user \"%s\" is not the install user" msgstr "Datenbankbenutzer »%s« ist nicht der Installationsbenutzer" -#: check.c:1061 +#: check.c:1069 #, c-format msgid "could not determine the number of users" msgstr "konnte die Anzahl der Benutzer nicht ermitteln" -#: check.c:1069 +#: check.c:1077 #, c-format msgid "Only the install user can be defined in the new cluster." msgstr "Nur der Installationsbenutzer darf im neuen Cluster definiert sein." -#: check.c:1098 +#: check.c:1106 #, c-format msgid "Checking database connection settings" msgstr "Prüfe Verbindungseinstellungen der Datenbank" -#: check.c:1124 +#: check.c:1132 #, c-format msgid "template0 must not allow connections, i.e. its pg_database.datallowconn must be false" msgstr "template0 darf keine Verbindungen erlauben, d.h. ihr pg_database.datallowconn muss falsch sein" -#: check.c:1150 check.c:1270 check.c:1367 check.c:1492 check.c:1568 -#: check.c:1626 check.c:1706 check.c:1897 check.c:2022 function.c:210 +#: check.c:1158 check.c:1278 check.c:1375 check.c:1500 check.c:1576 +#: check.c:1654 check.c:1713 check.c:1793 check.c:1984 check.c:2109 +#: function.c:210 #, c-format msgid "fatal" msgstr "fatal" -#: check.c:1151 +#: check.c:1159 #, c-format msgid "" "All non-template0 databases must allow connections, i.e. their\n" @@ -462,27 +463,27 @@ msgstr "" "in der Datei:\n" " %s" -#: check.c:1176 +#: check.c:1184 #, c-format msgid "Checking for prepared transactions" msgstr "Prüfe auf vorbereitete Transaktionen" -#: check.c:1185 +#: check.c:1193 #, c-format msgid "The source cluster contains prepared transactions" msgstr "Der alte Cluster enthält vorbereitete Transaktionen" -#: check.c:1187 +#: check.c:1195 #, c-format msgid "The target cluster contains prepared transactions" msgstr "Der neue Cluster enthält vorbereitete Transaktionen" -#: check.c:1212 +#: check.c:1220 #, c-format msgid "Checking for contrib/isn with bigint-passing mismatch" msgstr "Prüfe auf contrib/isn mit unpassender bigint-Übergabe" -#: check.c:1271 +#: check.c:1279 #, c-format msgid "" "Your installation contains \"contrib/isn\" functions which rely on the\n" @@ -502,12 +503,12 @@ msgstr "" "der problematischen Funktionen ist in der Datei:\n" " %s" -#: check.c:1293 +#: check.c:1301 #, c-format msgid "Checking for user-defined postfix operators" msgstr "Prüfe auf benutzerdefinierte Postfix-Operatoren" -#: check.c:1368 +#: check.c:1376 #, c-format msgid "" "Your installation contains user-defined postfix operators, which are not\n" @@ -522,12 +523,12 @@ msgstr "" "Liste der benutzerdefinierten Postfixoperatoren ist in der Datei:\n" " %s" -#: check.c:1392 +#: check.c:1400 #, c-format msgid "Checking for incompatible polymorphic functions" msgstr "Prüfe auf inkompatible polymorphische Funktionen" -#: check.c:1493 +#: check.c:1501 #, c-format msgid "" "Your installation contains user-defined objects that refer to internal\n" @@ -548,12 +549,12 @@ msgstr "" "Eine Liste der problematischen Objekte ist in der Datei:\n" " %s" -#: check.c:1517 +#: check.c:1525 #, c-format msgid "Checking for tables WITH OIDS" msgstr "Prüfe auf Tabellen mit WITH OIDS" -#: check.c:1569 +#: check.c:1577 #, c-format msgid "" "Your installation contains tables declared WITH OIDS, which is not\n" @@ -568,12 +569,37 @@ msgstr "" "Eine Liste der Tabellen mit dem Problem ist in der Datei:\n" " %s" -#: check.c:1596 +#: check.c:1602 +#, c-format +msgid "Checking for not-null constraint inconsistencies" +msgstr "Prüfe auf Inkonsistenzen bei Not-Null-Constraints" + +#: check.c:1655 +#, c-format +msgid "" +"Your installation contains inconsistent NOT NULL constraints.\n" +"If the parent column(s) are NOT NULL, then the child column must\n" +"also be marked NOT NULL, or the upgrade will fail.\n" +"You can fix this by running\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"on each column listed in the file:\n" +" %s" +msgstr "" +"Ihre Installation enthält inkonsistente NOT-NULL-Constraints.\n" +"Wenn die Spalte in der Elterntabelle NOT NULL ist, dann muss die\n" +"Spalte in der abgeleiteten Tabelle auch NOT NULL sein, ansonsten wird\n" +"das Upgrade fehlschlagen.\n" +"Sie können dies reparieren, indem Sie\n" +" ALTER TABLE tabellenname ALTER spalte SET NOT NULL;\n" +"für jede Spalte in dieser Datei ausführen:\n" +" %s" + +#: check.c:1683 #, c-format msgid "Checking for roles starting with \"pg_\"" msgstr "Prüfe auf Rollen, die mit »pg_« anfangen" -#: check.c:1627 +#: check.c:1714 #, c-format msgid "" "Your installation contains roles starting with \"pg_\".\n" @@ -588,12 +614,12 @@ msgstr "" "Eine Liste der Rollen, die mit »pg_« anfangen, ist in der Datei:\n" " %s" -#: check.c:1647 +#: check.c:1734 #, c-format msgid "Checking for user-defined encoding conversions" msgstr "Prüfe auf benutzerdefinierte Kodierungsumwandlungen" -#: check.c:1707 +#: check.c:1794 #, c-format msgid "" "Your installation contains user-defined encoding conversions.\n" @@ -612,52 +638,52 @@ msgstr "" "in der Datei:\n" " %s" -#: check.c:1746 +#: check.c:1833 #, c-format msgid "Checking for new cluster logical replication slots" msgstr "Prüfe logische Replikations-Slots des neuen Clusters" -#: check.c:1754 +#: check.c:1841 #, c-format msgid "could not count the number of logical replication slots" msgstr "konnte Anzahl der logischen Replikations-Slots nicht zählen" -#: check.c:1759 +#: check.c:1846 #, c-format msgid "expected 0 logical replication slots but found %d" msgstr "0 logische Replikations-Slots erwartet aber %d gefunden" -#: check.c:1769 check.c:1820 +#: check.c:1856 check.c:1907 #, c-format msgid "could not determine parameter settings on new cluster" msgstr "konnte Parametereinstellung im neuen Cluster nicht ermitteln" -#: check.c:1774 +#: check.c:1861 #, c-format msgid "\"wal_level\" must be \"logical\" but is set to \"%s\"" msgstr "»wal_level« muss »logical« sein, aber es ist auf »%s« gesetzt" -#: check.c:1780 +#: check.c:1867 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of logical replication slots (%d) on the old cluster" msgstr "»max_replication_slots« (%d) muss größer als oder gleich der Anzahl der logischen Replikations-Slots (%d) im alten Cluster sein" -#: check.c:1812 +#: check.c:1899 #, c-format msgid "Checking for new cluster configuration for subscriptions" msgstr "Prüfe Konfiguration für Subskriptionen im neuen Cluster" -#: check.c:1824 +#: check.c:1911 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of subscriptions (%d) on the old cluster" msgstr "»max_replication_slots« (%d) muss größer als oder gleich der Anzahl der Subskriptionen (%d) im alten Cluster sein" -#: check.c:1846 +#: check.c:1933 #, c-format msgid "Checking for valid logical replication slots" msgstr "Prüfe auf gültige logische Replikations-Slots" -#: check.c:1898 +#: check.c:1985 #, c-format msgid "" "Your installation contains logical replication slots that cannot be upgraded.\n" @@ -673,12 +699,12 @@ msgstr "" "Eine Liste der problematischen Slots ist in der Datei:\n" " %s" -#: check.c:1922 +#: check.c:2009 #, c-format msgid "Checking for subscription state" msgstr "Prüfe Subskriptionszustand" -#: check.c:2023 +#: check.c:2110 #, c-format msgid "" "Your installation contains subscriptions without origin or having relations not in i (initialize) or r (ready) state.\n" @@ -738,8 +764,8 @@ msgstr "Im alten Cluster fehlen Cluster-Zustandsinformationen:" msgid "The target cluster lacks cluster state information:" msgstr "Im neuen Cluster fehlen Cluster-Zustandsinformationen:" -#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:556 pg_upgrade.c:596 -#: pg_upgrade.c:945 relfilenumber.c:233 server.c:34 util.c:337 +#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:558 pg_upgrade.c:598 +#: pg_upgrade.c:947 relfilenumber.c:233 server.c:34 util.c:337 #, c-format msgid "%s" msgstr "%s" @@ -1714,77 +1740,77 @@ msgstr "" msgid "Setting locale and encoding for new cluster" msgstr "Setze Locale und Kodierung für neuen Cluster" -#: pg_upgrade.c:489 +#: pg_upgrade.c:491 #, c-format msgid "Analyzing all rows in the new cluster" msgstr "Analysiere alle Zeilen im neuen Cluster" -#: pg_upgrade.c:502 +#: pg_upgrade.c:504 #, c-format msgid "Freezing all rows in the new cluster" msgstr "Friere alle Zeilen im neuen Cluster ein" -#: pg_upgrade.c:522 +#: pg_upgrade.c:524 #, c-format msgid "Restoring global objects in the new cluster" msgstr "Stelle globale Objekte im neuen Cluster wieder her" -#: pg_upgrade.c:538 +#: pg_upgrade.c:540 #, c-format msgid "Restoring database schemas in the new cluster" msgstr "Stelle Datenbankschemas im neuen Cluster wieder her" -#: pg_upgrade.c:662 +#: pg_upgrade.c:664 #, c-format msgid "Deleting files from new %s" msgstr "Lösche Dateien aus neuem %s" -#: pg_upgrade.c:666 +#: pg_upgrade.c:668 #, c-format msgid "could not delete directory \"%s\"" msgstr "konnte Verzeichnis »%s« nicht löschen" -#: pg_upgrade.c:685 +#: pg_upgrade.c:687 #, c-format msgid "Copying old %s to new server" msgstr "Kopiere altes %s zum neuen Server" -#: pg_upgrade.c:711 +#: pg_upgrade.c:713 #, c-format msgid "Setting oldest XID for new cluster" msgstr "Setze älteste XID im neuen Cluster" -#: pg_upgrade.c:719 +#: pg_upgrade.c:721 #, c-format msgid "Setting next transaction ID and epoch for new cluster" msgstr "Setze nächste Transaktions-ID und -epoche im neuen Cluster" -#: pg_upgrade.c:749 +#: pg_upgrade.c:751 #, c-format msgid "Setting next multixact ID and offset for new cluster" msgstr "Setze nächste Multixact-ID und nächstes Offset im neuen Cluster" -#: pg_upgrade.c:773 +#: pg_upgrade.c:775 #, c-format msgid "Setting oldest multixact ID in new cluster" msgstr "Setze älteste Multixact-ID im neuen Cluster" -#: pg_upgrade.c:793 +#: pg_upgrade.c:795 #, c-format msgid "Resetting WAL archives" msgstr "Setze WAL-Archive zurück" -#: pg_upgrade.c:836 +#: pg_upgrade.c:838 #, c-format msgid "Setting frozenxid and minmxid counters in new cluster" msgstr "Setze frozenxid und minmxid im neuen Cluster" -#: pg_upgrade.c:838 +#: pg_upgrade.c:840 #, c-format msgid "Setting minmxid counter in new cluster" msgstr "Setze minmxid im neuen Cluster" -#: pg_upgrade.c:929 +#: pg_upgrade.c:931 #, c-format msgid "Restoring logical replication slots in the new cluster" msgstr "Stelle logische Replikations-Slots im neuen Cluster wieder her" @@ -1875,7 +1901,7 @@ msgstr "konnte Versionsdatei »%s« nicht öffnen: %m" msgid "could not parse version file \"%s\"" msgstr "konnte Versionsdatei »%s« nicht interpretieren" -#: server.c:310 +#: server.c:299 #, c-format msgid "" "\n" @@ -1884,7 +1910,7 @@ msgstr "" "\n" "%s" -#: server.c:314 +#: server.c:303 #, c-format msgid "" "could not connect to source postmaster started with the command:\n" @@ -1893,7 +1919,7 @@ msgstr "" "konnte nicht mit dem Postmaster für den alten Cluster verbinden, gestartet mit dem Befehl:\n" "%s" -#: server.c:318 +#: server.c:307 #, c-format msgid "" "could not connect to target postmaster started with the command:\n" @@ -1902,22 +1928,22 @@ msgstr "" "konnte nicht mit dem Postmaster für den neuen Cluster verbinden, gestartet mit dem Befehl:\n" "%s" -#: server.c:332 +#: server.c:321 #, c-format msgid "pg_ctl failed to start the source server, or connection failed" msgstr "pg_ctl konnte den Quellserver nicht starten, oder Verbindung fehlgeschlagen" -#: server.c:334 +#: server.c:323 #, c-format msgid "pg_ctl failed to start the target server, or connection failed" msgstr "pg_ctl konnte den Zielserver nicht starten, oder Verbindung fehlgeschlagen" -#: server.c:379 +#: server.c:368 #, c-format msgid "out of memory" msgstr "Speicher aufgebraucht" -#: server.c:392 +#: server.c:381 #, c-format msgid "libpq environment variable %s has a non-local server value: %s" msgstr "libpq-Umgebungsvariable %s hat einen nicht lokalen Serverwert: %s" diff --git a/src/bin/pg_upgrade/po/fr.po b/src/bin/pg_upgrade/po/fr.po index 6117d1fb48d91..510198382ac1f 100644 --- a/src/bin/pg_upgrade/po/fr.po +++ b/src/bin/pg_upgrade/po/fr.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-10-29 18:20+0000\n" -"PO-Revision-Date: 2024-10-30 07:41+0100\n" +"POT-Creation-Date: 2025-07-18 09:38+0000\n" +"PO-Revision-Date: 2025-07-19 07:11+0200\n" "Last-Translator: Guillaume Lelarge \n" "Language-Team: French \n" "Language: fr\n" @@ -19,7 +19,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.6\n" #: ../../common/fe_memutils.c:35 ../../common/fe_memutils.c:75 #: ../../common/fe_memutils.c:98 ../../common/fe_memutils.c:161 @@ -51,21 +51,21 @@ msgstr "l'utilisateur n'existe pas" msgid "user name lookup failure: error code %lu" msgstr "échec de la recherche du nom d'utilisateur : code d'erreur %lu" -#: ../../fe_utils/string_utils.c:434 +#: ../../fe_utils/string_utils.c:587 #, c-format msgid "shell command argument contains a newline or carriage return: \"%s\"\n" msgstr "l'argument de la commande shell contient un retour à la ligne ou un retour chariot : « %s »\n" -#: ../../fe_utils/string_utils.c:607 +#: ../../fe_utils/string_utils.c:760 #, c-format msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "le nom de la base contient un retour à la ligne ou un retour chariot : « %s »\n" -#: check.c:111 +#: check.c:112 msgid "Checking for system-defined composite types in user tables" msgstr "Vérification des types composites définis par le système dans les tables utilisateurs" -#: check.c:118 +#: check.c:119 msgid "" "Your installation contains system-defined composite types in user tables.\n" "These type OIDs are not stable across PostgreSQL versions,\n" @@ -77,11 +77,11 @@ msgstr "" "de PostgreSQL, donc cette instance ne peut pas être mise à jour actuellement. Vous pouvez\n" "supprimer les colonnes problématiques, puis relancer la mise à jour.\n" -#: check.c:132 +#: check.c:133 msgid "Checking for incompatible \"line\" data type" msgstr "Vérification des types de données line incompatibles" -#: check.c:137 +#: check.c:138 msgid "" "Your installation contains the \"line\" data type in user tables.\n" "This data type changed its internal and input/output format\n" @@ -94,11 +94,11 @@ msgstr "" "et nouvelle versions, donc cette instance ne peut pas être mise à jour\n" "actuellement. Vous pouvez supprimer les colonnes problématiques et relancer la mise à jour.\n" -#: check.c:154 +#: check.c:155 msgid "Checking for reg* data types in user tables" msgstr "Vérification des types de données reg* dans les tables utilisateurs" -#: check.c:181 +#: check.c:182 msgid "" "Your installation contains one of the reg* data types in user tables.\n" "These data types reference system OIDs that are not preserved by\n" @@ -111,11 +111,11 @@ msgstr "" "jour actuellement. Vous pouvez supprimer les colonnes problématiques et relancer\n" "la mise à jour.\n" -#: check.c:193 +#: check.c:194 msgid "Checking for incompatible \"aclitem\" data type" msgstr "Vérification des types de données « aclitem » incompatibles" -#: check.c:198 +#: check.c:199 msgid "" "Your installation contains the \"aclitem\" data type in user tables.\n" "The internal format of \"aclitem\" changed in PostgreSQL version 16\n" @@ -127,11 +127,11 @@ msgstr "" "cette instance ne peut pas être mise à jour actuellement. Vous pouvez supprimer les\n" "colonnes problématiques et relancer la mise à jour.\n" -#: check.c:217 +#: check.c:218 msgid "Checking for invalid \"unknown\" user columns" msgstr "Vérification des colonnes utilisateurs « unknown » invalides" -#: check.c:222 +#: check.c:223 msgid "" "Your installation contains the \"unknown\" data type in user tables.\n" "This data type is no longer allowed in tables, so this cluster\n" @@ -143,11 +143,11 @@ msgstr "" "cette instance ne peut pas être mise à jour pour l'instant. Vous pouvez\n" "supprimer les colonnes problématiques, puis relancer la mise à jour.\n" -#: check.c:239 +#: check.c:240 msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "Vérification des colonnes utilisateurs « sql_identifier » invalides" -#: check.c:244 +#: check.c:245 msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" "The on-disk format for this data type has changed, so this\n" @@ -159,11 +159,11 @@ msgstr "" "donc cette instance ne peut pas être mise à jour actuellement. Vous pouvez supprimer\n" "les colonnes problématiques, puis relancer la mise à jour.\n" -#: check.c:255 +#: check.c:256 msgid "Checking for incompatible \"jsonb\" data type in user tables" msgstr "Vérification du type de données « json » incompatible dans les tables utilisateurs" -#: check.c:260 +#: check.c:261 msgid "" "Your installation contains the \"jsonb\" data type in user tables.\n" "The internal format of \"jsonb\" changed during 9.4 beta so this\n" @@ -175,11 +175,11 @@ msgstr "" "cette instance ne peut pas être mise à jour actuellement. Vous pouvez supprimer les\n" "colonnes problématiques et relancer la mise à jour.\n" -#: check.c:272 +#: check.c:273 msgid "Checking for removed \"abstime\" data type in user tables" msgstr "Vérification du type de données « abstime » supprimé dans les tables utilisateurs" -#: check.c:277 +#: check.c:278 msgid "" "Your installation contains the \"abstime\" data type in user tables.\n" "The \"abstime\" type has been removed in PostgreSQL version 12,\n" @@ -193,11 +193,11 @@ msgstr "" "supprimer les colonnes problématiques ou les convertir en un autre type de données,\n" "et relancer la mise à jour.\n" -#: check.c:285 +#: check.c:286 msgid "Checking for removed \"reltime\" data type in user tables" msgstr "Vérification du type de données « reltime » supprimé dans les tables utilisateurs" -#: check.c:290 +#: check.c:291 msgid "" "Your installation contains the \"reltime\" data type in user tables.\n" "The \"reltime\" type has been removed in PostgreSQL version 12,\n" @@ -211,11 +211,11 @@ msgstr "" "supprimer les colonnes problématiques ou les convertir en un autre type de données,\n" "et relancer la mise à jour.\n" -#: check.c:298 +#: check.c:299 msgid "Checking for removed \"tinterval\" data type in user tables" msgstr "Vérification du type de données « tinterval » supprimé dans les tables utilisateurs" -#: check.c:303 +#: check.c:304 msgid "" "Your installation contains the \"tinterval\" data type in user tables.\n" "The \"tinterval\" type has been removed in PostgreSQL version 12,\n" @@ -229,34 +229,34 @@ msgstr "" "supprimer les colonnes problématiques ou les convertir en un autre type de données,\n" "et relancer la mise à jour.\n" -#: check.c:345 +#: check.c:346 #, c-format msgid "Checking data type usage" msgstr "Vérification de l'utilisation du type de données" -#: check.c:480 +#: check.c:481 #, c-format msgid "failed check: %s" msgstr "vérification échouée : %s" -#: check.c:483 +#: check.c:484 msgid "A list of the problem columns is in the file:" msgstr "Une liste des colonnes problématiques se trouve dans le fichier :" -#: check.c:495 check.c:963 check.c:1136 check.c:1251 check.c:1345 check.c:1473 -#: check.c:1549 check.c:1613 check.c:1686 check.c:1865 check.c:1884 -#: check.c:1953 check.c:2005 file.c:378 file.c:415 function.c:189 option.c:493 -#: version.c:79 version.c:177 +#: check.c:496 check.c:971 check.c:1144 check.c:1259 check.c:1353 check.c:1481 +#: check.c:1557 check.c:1634 check.c:1700 check.c:1773 check.c:1952 +#: check.c:1971 check.c:2040 check.c:2092 file.c:378 file.c:415 function.c:189 +#: option.c:493 version.c:79 version.c:177 #, c-format msgid "could not open file \"%s\": %m" msgstr "n'a pas pu ouvrir le fichier « %s » : %m" -#: check.c:522 +#: check.c:523 #, c-format msgid "Data type checks failed: %s" msgstr "Échec de la vérification des types de données : %s" -#: check.c:563 +#: check.c:564 #, c-format msgid "" "Performing Consistency Checks on Old Live Server\n" @@ -265,7 +265,7 @@ msgstr "" "Exécution de tests de cohérence sur l'ancien serveur\n" "----------------------------------------------------" -#: check.c:569 +#: check.c:570 #, c-format msgid "" "Performing Consistency Checks\n" @@ -274,7 +274,7 @@ msgstr "" "Exécution de tests de cohérence\n" "-------------------------------" -#: check.c:718 +#: check.c:726 #, c-format msgid "" "\n" @@ -283,7 +283,7 @@ msgstr "" "\n" "*Les instances sont compatibles*" -#: check.c:726 +#: check.c:734 #, c-format msgid "" "\n" @@ -294,7 +294,7 @@ msgstr "" "Si pg_upgrade échoue après cela, vous devez ré-exécuter initdb\n" "sur la nouvelle instance avant de continuer." -#: check.c:767 +#: check.c:775 #, c-format msgid "" "Optimizer statistics are not transferred by pg_upgrade.\n" @@ -305,7 +305,7 @@ msgstr "" "Une fois le nouveau serveur démarré, pensez à exécuter :\n" " %s/vacuumdb %s--all --analyze-in-stages" -#: check.c:773 +#: check.c:781 #, c-format msgid "" "Running this script will delete the old cluster's data files:\n" @@ -314,7 +314,7 @@ msgstr "" "Exécuter ce script supprimera les fichiers de données de l'ancienne instance :\n" " %s" -#: check.c:778 +#: check.c:786 #, c-format msgid "" "Could not create a script to delete the old cluster's data files\n" @@ -328,57 +328,57 @@ msgstr "" "de l'ancienne instance. Le contenu de l'ancienne instance doit être supprimé\n" "manuellement." -#: check.c:790 +#: check.c:798 #, c-format msgid "Checking cluster versions" msgstr "Vérification des versions des instances" -#: check.c:802 +#: check.c:810 #, c-format msgid "This utility can only upgrade from PostgreSQL version %s and later." msgstr "Cet outil peut seulement mettre à jour les versions %s et ultérieures de PostgreSQL." -#: check.c:807 +#: check.c:815 #, c-format msgid "This utility can only upgrade to PostgreSQL version %s." msgstr "Cet outil peut seulement mettre à jour vers la version %s de PostgreSQL." -#: check.c:816 +#: check.c:824 #, c-format msgid "This utility cannot be used to downgrade to older major PostgreSQL versions." msgstr "Cet outil ne peut pas être utilisé pour mettre à jour vers des versions majeures plus anciennes de PostgreSQL." -#: check.c:821 +#: check.c:829 #, c-format msgid "Old cluster data and binary directories are from different major versions." msgstr "Les répertoires des données de l'ancienne instance et des binaires sont de versions majeures différentes." -#: check.c:824 +#: check.c:832 #, c-format msgid "New cluster data and binary directories are from different major versions." msgstr "Les répertoires des données de la nouvelle instance et des binaires sont de versions majeures différentes." -#: check.c:839 +#: check.c:847 #, c-format msgid "When checking a live server, the old and new port numbers must be different." msgstr "Lors de la vérification d'un serveur en production, l'ancien numéro de port doit être différent du nouveau." -#: check.c:859 +#: check.c:867 #, c-format msgid "New cluster database \"%s\" is not empty: found relation \"%s.%s\"" msgstr "La nouvelle instance « %s » n'est pas vide : relation « %s.%s » trouvée" -#: check.c:882 +#: check.c:890 #, c-format msgid "Checking for new cluster tablespace directories" msgstr "Vérification des répertoires de tablespace de la nouvelle instance" -#: check.c:893 +#: check.c:901 #, c-format msgid "new cluster tablespace directory already exists: \"%s\"" msgstr "le répertoire du tablespace de la nouvelle instance existe déjà : « %s »" -#: check.c:926 +#: check.c:934 #, c-format msgid "" "\n" @@ -387,7 +387,7 @@ msgstr "" "\n" "AVERTISSEMENT : le nouveau répertoire de données ne doit pas être à l'intérieur de l'ancien répertoire de données, %s" -#: check.c:950 +#: check.c:958 #, c-format msgid "" "\n" @@ -396,53 +396,54 @@ msgstr "" "\n" "AVERTISSEMENT : les emplacements des tablespaces utilisateurs ne doivent pas être à l'intérieur du répertoire de données, %s" -#: check.c:960 +#: check.c:968 #, c-format msgid "Creating script to delete old cluster" msgstr "Création du script pour supprimer l'ancienne instance" -#: check.c:1014 +#: check.c:1022 #, c-format msgid "could not add execute permission to file \"%s\": %m" msgstr "n'a pas pu ajouter les droits d'exécution pour le fichier « %s » : %m" -#: check.c:1034 +#: check.c:1042 #, c-format msgid "Checking database user is the install user" msgstr "Vérification que l'utilisateur de la base de données est l'utilisateur d'installation" -#: check.c:1050 +#: check.c:1058 #, c-format msgid "database user \"%s\" is not the install user" msgstr "l'utilisateur de la base de données « %s » n'est pas l'utilisateur d'installation" -#: check.c:1061 +#: check.c:1069 #, c-format msgid "could not determine the number of users" msgstr "n'a pas pu déterminer le nombre d'utilisateurs" -#: check.c:1069 +#: check.c:1077 #, c-format msgid "Only the install user can be defined in the new cluster." msgstr "Seul l'utilisateur d'installation peut être défini dans la nouvelle instance." -#: check.c:1098 +#: check.c:1106 #, c-format msgid "Checking database connection settings" msgstr "Vérification des paramètres de connexion de la base de données" -#: check.c:1124 +#: check.c:1132 #, c-format msgid "template0 must not allow connections, i.e. its pg_database.datallowconn must be false" msgstr "template0 ne doit pas autoriser les connexions, ie pg_database.datallowconn doit valoir false" -#: check.c:1150 check.c:1270 check.c:1367 check.c:1492 check.c:1568 -#: check.c:1626 check.c:1706 check.c:1897 check.c:2022 function.c:210 +#: check.c:1158 check.c:1278 check.c:1375 check.c:1500 check.c:1576 +#: check.c:1654 check.c:1713 check.c:1793 check.c:1984 check.c:2109 +#: function.c:210 #, c-format msgid "fatal" msgstr "fatal" -#: check.c:1151 +#: check.c:1159 #, c-format msgid "" "All non-template0 databases must allow connections, i.e. their\n" @@ -461,27 +462,27 @@ msgstr "" "le fichier :\n" " %s" -#: check.c:1176 +#: check.c:1184 #, c-format msgid "Checking for prepared transactions" msgstr "Vérification des transactions préparées" -#: check.c:1185 +#: check.c:1193 #, c-format msgid "The source cluster contains prepared transactions" msgstr "L'instance source contient des transactions préparées" -#: check.c:1187 +#: check.c:1195 #, c-format msgid "The target cluster contains prepared transactions" msgstr "L'instance cible contient des transactions préparées" -#: check.c:1212 +#: check.c:1220 #, c-format msgid "Checking for contrib/isn with bigint-passing mismatch" msgstr "Vérification de contrib/isn avec une différence sur le passage des bigint" -#: check.c:1271 +#: check.c:1279 #, c-format msgid "" "Your installation contains \"contrib/isn\" functions which rely on the\n" @@ -502,12 +503,12 @@ msgstr "" "dans le fichier :\n" " %s" -#: check.c:1293 +#: check.c:1301 #, c-format msgid "Checking for user-defined postfix operators" msgstr "Vérification des opérateurs postfixes définis par les utilisateurs" -#: check.c:1368 +#: check.c:1376 #, c-format msgid "" "Your installation contains user-defined postfix operators, which are not\n" @@ -522,12 +523,12 @@ msgstr "" "Une liste des opérateurs postfixes définis par les utilisateurs se trouve dans le fichier :\n" " %s" -#: check.c:1392 +#: check.c:1400 #, c-format msgid "Checking for incompatible polymorphic functions" msgstr "Vérification des fonctions polymorphiques incompatibles" -#: check.c:1493 +#: check.c:1501 #, c-format msgid "" "Your installation contains user-defined objects that refer to internal\n" @@ -547,12 +548,12 @@ msgstr "" "des objets problématiques se trouve dans le fichier\n" " %s" -#: check.c:1517 +#: check.c:1525 #, c-format msgid "Checking for tables WITH OIDS" msgstr "Vérification des tables WITH OIDS" -#: check.c:1569 +#: check.c:1577 #, c-format msgid "" "Your installation contains tables declared WITH OIDS, which is not\n" @@ -567,12 +568,36 @@ msgstr "" "Une liste des tables ayant ce problème se trouve dans le fichier :\n" " %s" -#: check.c:1596 +#: check.c:1602 +#, c-format +msgid "Checking for not-null constraint inconsistencies" +msgstr "Vérification des incohérences des contraintes NOT NULL" + +#: check.c:1655 +#, c-format +msgid "" +"Your installation contains inconsistent NOT NULL constraints.\n" +"If the parent column(s) are NOT NULL, then the child column must\n" +"also be marked NOT NULL, or the upgrade will fail.\n" +"You can fix this by running\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"on each column listed in the file:\n" +" %s" +msgstr "" +"Votre installation contient des contraintes NOT NULL incohérentes.\n" +"Si les colonnes parents sont NOT NULL, alors la colonne enfant doit\n" +"aussi être marquée NOT NULL, sinon la mise à jour échouera.\n" +"Vous pouvez corriger ceci en exécutant\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"sur chaque colonne listée dans le fichier :\n" +" %s" + +#: check.c:1683 #, c-format msgid "Checking for roles starting with \"pg_\"" msgstr "Vérification des rôles commençant avec « pg_ »" -#: check.c:1627 +#: check.c:1714 #, c-format msgid "" "Your installation contains roles starting with \"pg_\".\n" @@ -587,12 +612,12 @@ msgstr "" "Une liste des rôles commençant par « pg_ » se trouve dans le fichier :\n" " %s" -#: check.c:1647 +#: check.c:1734 #, c-format msgid "Checking for user-defined encoding conversions" msgstr "Vérification des conversions d'encodage définies par les utilisateurs" -#: check.c:1707 +#: check.c:1794 #, c-format msgid "" "Your installation contains user-defined encoding conversions.\n" @@ -609,52 +634,52 @@ msgstr "" "Une liste des conversions d'encodage définies par l'utilisateur se trouve dans le fichier :\n" " %s" -#: check.c:1746 +#: check.c:1833 #, c-format msgid "Checking for new cluster logical replication slots" msgstr "Vérification des slots de réplication logique de la nouvelle instance" -#: check.c:1754 +#: check.c:1841 #, c-format msgid "could not count the number of logical replication slots" msgstr "n'a pas pu compter le nombre de slots de réplication logique" -#: check.c:1759 +#: check.c:1846 #, c-format msgid "expected 0 logical replication slots but found %d" msgstr "attendait 0 slot de réplication logique mais en a trouvé %d" -#: check.c:1769 check.c:1820 +#: check.c:1856 check.c:1907 #, c-format msgid "could not determine parameter settings on new cluster" msgstr "n'a pas pu déterminer la configuration sur la nouvelle instance" -#: check.c:1774 +#: check.c:1861 #, c-format msgid "\"wal_level\" must be \"logical\" but is set to \"%s\"" msgstr "« wal_level » doit être configuré à « logical » mais est configuré à « %s »" -#: check.c:1780 +#: check.c:1867 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of logical replication slots (%d) on the old cluster" msgstr "« max_replication_slots » (%d) doit être supérieur ou égal au nombre de slots de réplication logique (%d) sur l'ancienne instance" -#: check.c:1812 +#: check.c:1899 #, c-format msgid "Checking for new cluster configuration for subscriptions" msgstr "Vérification de la configuration de la nouvelle instance pour les souscriptions" -#: check.c:1824 +#: check.c:1911 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of subscriptions (%d) on the old cluster" msgstr "« max_replication_slots » (%d) doit être supérieur ou égal au nombre de souscriptions (%d) sur l'ancienne instance" -#: check.c:1846 +#: check.c:1933 #, c-format msgid "Checking for valid logical replication slots" msgstr "Vérification des slots de réplication logique valides" -#: check.c:1898 +#: check.c:1985 #, c-format msgid "" "Your installation contains logical replication slots that cannot be upgraded.\n" @@ -669,12 +694,12 @@ msgstr "" "une liste des slots problématiques dans le fichier :\n" " %s" -#: check.c:1922 +#: check.c:2009 #, c-format msgid "Checking for subscription state" msgstr "Vérification de l'état de souscription" -#: check.c:2023 +#: check.c:2110 #, c-format msgid "" "Your installation contains subscriptions without origin or having relations not in i (initialize) or r (ready) state.\n" @@ -733,8 +758,8 @@ msgstr "Il manque certaines informations d'état requises sur l'instance source msgid "The target cluster lacks cluster state information:" msgstr "Il manque certaines informations d'état requises sur l'instance cible :" -#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:556 pg_upgrade.c:596 -#: pg_upgrade.c:945 relfilenumber.c:233 server.c:34 util.c:337 +#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:558 pg_upgrade.c:598 +#: pg_upgrade.c:947 relfilenumber.c:233 server.c:34 util.c:337 #, c-format msgid "%s" msgstr "%s" @@ -1739,77 +1764,77 @@ msgstr "" msgid "Setting locale and encoding for new cluster" msgstr "Configuration de la locale et de l'encodage pour la nouvelle instance" -#: pg_upgrade.c:489 +#: pg_upgrade.c:491 #, c-format msgid "Analyzing all rows in the new cluster" msgstr "Analyse de toutes les lignes dans la nouvelle instance" -#: pg_upgrade.c:502 +#: pg_upgrade.c:504 #, c-format msgid "Freezing all rows in the new cluster" msgstr "Gel de toutes les lignes dans la nouvelle instance" -#: pg_upgrade.c:522 +#: pg_upgrade.c:524 #, c-format msgid "Restoring global objects in the new cluster" msgstr "Restauration des objets globaux dans la nouvelle instance" -#: pg_upgrade.c:538 +#: pg_upgrade.c:540 #, c-format msgid "Restoring database schemas in the new cluster" msgstr "Restauration des schémas des bases de données dans la nouvelle instance" -#: pg_upgrade.c:662 +#: pg_upgrade.c:664 #, c-format msgid "Deleting files from new %s" msgstr "Suppression des fichiers à partir du nouveau %s" -#: pg_upgrade.c:666 +#: pg_upgrade.c:668 #, c-format msgid "could not delete directory \"%s\"" msgstr "n'a pas pu supprimer le répertoire « %s »" -#: pg_upgrade.c:685 +#: pg_upgrade.c:687 #, c-format msgid "Copying old %s to new server" msgstr "Copie de l'ancien %s vers le nouveau serveur" -#: pg_upgrade.c:711 +#: pg_upgrade.c:713 #, c-format msgid "Setting oldest XID for new cluster" msgstr "Configuration du plus ancien XID sur la nouvelle instance" -#: pg_upgrade.c:719 +#: pg_upgrade.c:721 #, c-format msgid "Setting next transaction ID and epoch for new cluster" msgstr "Configuration du prochain identifiant de transaction et de l'epoch pour la nouvelle instance" -#: pg_upgrade.c:749 +#: pg_upgrade.c:751 #, c-format msgid "Setting next multixact ID and offset for new cluster" msgstr "Configuration du prochain MultiXactId et décalage pour la nouvelle instance" -#: pg_upgrade.c:773 +#: pg_upgrade.c:775 #, c-format msgid "Setting oldest multixact ID in new cluster" msgstr "Configuration du plus ancien identifiant multixact sur la nouvelle instance" -#: pg_upgrade.c:793 +#: pg_upgrade.c:795 #, c-format msgid "Resetting WAL archives" msgstr "Réinitialisation des archives WAL" -#: pg_upgrade.c:836 +#: pg_upgrade.c:838 #, c-format msgid "Setting frozenxid and minmxid counters in new cluster" msgstr "Configuration des compteurs frozenxid et minmxid dans la nouvelle instance" -#: pg_upgrade.c:838 +#: pg_upgrade.c:840 #, c-format msgid "Setting minmxid counter in new cluster" msgstr "Configuration du compteur minmxid dans la nouvelle instance" -#: pg_upgrade.c:929 +#: pg_upgrade.c:931 #, c-format msgid "Restoring logical replication slots in the new cluster" msgstr "Restauration des slots de réplication logique dans la nouvelle instance" @@ -1900,7 +1925,7 @@ msgstr "n'a pas pu ouvrir le fichier de version « %s » : %m" msgid "could not parse version file \"%s\"" msgstr "n'a pas pu analyser le fichier de version « %s »" -#: server.c:310 +#: server.c:299 #, c-format msgid "" "\n" @@ -1909,7 +1934,7 @@ msgstr "" "\n" "%s" -#: server.c:314 +#: server.c:303 #, c-format msgid "" "could not connect to source postmaster started with the command:\n" @@ -1918,7 +1943,7 @@ msgstr "" "n'a pas pu se connecter au postmaster source lancé avec la commande :\n" "%s" -#: server.c:318 +#: server.c:307 #, c-format msgid "" "could not connect to target postmaster started with the command:\n" @@ -1927,22 +1952,22 @@ msgstr "" "n'a pas pu se connecter au postmaster cible lancé avec la commande :\n" "%s" -#: server.c:332 +#: server.c:321 #, c-format msgid "pg_ctl failed to start the source server, or connection failed" msgstr "pg_ctl a échoué à démarrer le serveur source ou connexion échouée" -#: server.c:334 +#: server.c:323 #, c-format msgid "pg_ctl failed to start the target server, or connection failed" msgstr "pg_ctl a échoué à démarrer le serveur cible ou connexion échouée" -#: server.c:379 +#: server.c:368 #, c-format msgid "out of memory" msgstr "mémoire épuisée" -#: server.c:392 +#: server.c:381 #, c-format msgid "libpq environment variable %s has a non-local server value: %s" msgstr "la variable d'environnement libpq %s a une valeur serveur non locale : %s" diff --git a/src/bin/pg_upgrade/po/ja.po b/src/bin/pg_upgrade/po/ja.po index 5570cf122fe50..01d4620e0b6df 100644 --- a/src/bin/pg_upgrade/po/ja.po +++ b/src/bin/pg_upgrade/po/ja.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: pg_upgrade (PostgreSQL 17)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-28 11:39+0900\n" -"PO-Revision-Date: 2025-03-03 17:36+0900\n" +"POT-Creation-Date: 2025-07-07 17:07+0900\n" +"PO-Revision-Date: 2025-07-08 10:54+0900\n" "Last-Translator: Kyotaro Horiguchi \n" "Language-Team: Japan PostgreSQL Users Group \n" "Language: ja\n" @@ -60,11 +60,11 @@ msgstr "シェルコマンドの引数に改行(LF)または復帰(CR)が含ま msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "データベース名に改行(LF)または復帰(CR)が含まれています: \"%s\"\n" -#: check.c:111 +#: check.c:112 msgid "Checking for system-defined composite types in user tables" msgstr "ユーザーテーブル内のシステム定義複合型を確認しています" -#: check.c:118 +#: check.c:119 msgid "" "Your installation contains system-defined composite types in user tables.\n" "These type OIDs are not stable across PostgreSQL versions,\n" @@ -76,11 +76,11 @@ msgstr "" "このクラスタは現時点ではアップグレードできません。問題の列を削除したのちに\n" "アップグレードを再実行することができます。\n" -#: check.c:132 +#: check.c:133 msgid "Checking for incompatible \"line\" data type" msgstr "非互換の \"line\" データ型を確認しています" -#: check.c:137 +#: check.c:138 msgid "" "Your installation contains the \"line\" data type in user tables.\n" "This data type changed its internal and input/output format\n" @@ -93,11 +93,11 @@ msgstr "" "変更されているため、このクラスタは現時点ではアップグレードできません。\n" "問題の列を削除したのちにアップグレードを再実行できます。\n" -#: check.c:154 +#: check.c:155 msgid "Checking for reg* data types in user tables" msgstr "ユーザーテーブル内の reg * データ型をチェックしています" -#: check.c:181 +#: check.c:182 msgid "" "Your installation contains one of the reg* data types in user tables.\n" "These data types reference system OIDs that are not preserved by\n" @@ -109,11 +109,11 @@ msgstr "" "保存されないため、現時点ではこのクラスタをアップグレードすることはできません。\n" "問題の列を削除したのち、アップグレードを再実行できます。\n" -#: check.c:193 +#: check.c:194 msgid "Checking for incompatible \"aclitem\" data type" msgstr "非互換の\"aclitem\"データ型を確認しています" -#: check.c:198 +#: check.c:199 msgid "" "Your installation contains the \"aclitem\" data type in user tables.\n" "The internal format of \"aclitem\" changed in PostgreSQL version 16\n" @@ -125,11 +125,11 @@ msgstr "" "現時点ではこのクラスタをアップグレードすることはできません。\n" "問題の列を削除したのち、アップグレードを再実行できます。\n" -#: check.c:217 +#: check.c:218 msgid "Checking for invalid \"unknown\" user columns" msgstr "無効な\"unknown\"ユーザー列をチェックしています" -#: check.c:222 +#: check.c:223 msgid "" "Your installation contains the \"unknown\" data type in user tables.\n" "This data type is no longer allowed in tables, so this cluster\n" @@ -141,11 +141,11 @@ msgstr "" "ではアップグレードできません。問題の列を削除したのち、アップグレードを\n" "再実行できます。\n" -#: check.c:239 +#: check.c:240 msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "無効な\"sql_identifier\"ユーザー列を確認しています" -#: check.c:244 +#: check.c:245 msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" "The on-disk format for this data type has changed, so this\n" @@ -157,11 +157,11 @@ msgstr "" "アップグレードできません。問題のある列を削除した後にアップグレードを再実行する\n" "ことができます。\n" -#: check.c:255 +#: check.c:256 msgid "Checking for incompatible \"jsonb\" data type in user tables" msgstr "ユーザーテーブル内の非互換の\"jsonb\"データ型を確認しています" -#: check.c:260 +#: check.c:261 msgid "" "Your installation contains the \"jsonb\" data type in user tables.\n" "The internal format of \"jsonb\" changed during 9.4 beta so this\n" @@ -173,11 +173,11 @@ msgstr "" "クラスタをアップグレードすることはできません。 問題の列を削除したのち、\n" "アップグレードを再実行できます。\n" -#: check.c:272 +#: check.c:273 msgid "Checking for removed \"abstime\" data type in user tables" msgstr "ユーザーテーブル内の削除された\"abstime\"データ型を確認しています" -#: check.c:277 +#: check.c:278 msgid "" "Your installation contains the \"abstime\" data type in user tables.\n" "The \"abstime\" type has been removed in PostgreSQL version 12,\n" @@ -191,11 +191,11 @@ msgstr "" "問題の列を削除するか、他のデータ型に変更した後にアップグレードを\n" "再実行できます。\n" -#: check.c:285 +#: check.c:286 msgid "Checking for removed \"reltime\" data type in user tables" msgstr "ユーザーテーブル中内の削除された\"reltime\"データ型を確認しています" -#: check.c:290 +#: check.c:291 msgid "" "Your installation contains the \"reltime\" data type in user tables.\n" "The \"reltime\" type has been removed in PostgreSQL version 12,\n" @@ -208,11 +208,11 @@ msgstr "" "このクラスタは現時点ではアップグレードできません。問題の列を削除するか、\n" "他のデータ型に変更した後にアップグレードを再実行できます。\n" -#: check.c:298 +#: check.c:299 msgid "Checking for removed \"tinterval\" data type in user tables" msgstr "ユーザーテーブル内の削除された\"tinterval\"データ型を確認しています" -#: check.c:303 +#: check.c:304 msgid "" "Your installation contains the \"tinterval\" data type in user tables.\n" "The \"tinterval\" type has been removed in PostgreSQL version 12,\n" @@ -225,34 +225,34 @@ msgstr "" "このクラスタは現時点ではアップグレードできません。問題の列を削除するか、\n" "他のデータ型に変更した後にアップグレードを再実行できます。\n" -#: check.c:345 +#: check.c:346 #, c-format msgid "Checking data type usage" msgstr "データ型の使用を確認しています" -#: check.c:480 +#: check.c:481 #, c-format msgid "failed check: %s" msgstr "問題を検出した項目: %s" -#: check.c:483 +#: check.c:484 msgid "A list of the problem columns is in the file:" msgstr "問題の列の一覧は以下のファイルにあります:" -#: check.c:495 check.c:963 check.c:1136 check.c:1251 check.c:1345 check.c:1473 -#: check.c:1549 check.c:1613 check.c:1686 check.c:1865 check.c:1884 -#: check.c:1953 check.c:2005 file.c:378 file.c:415 function.c:189 option.c:493 -#: version.c:79 version.c:177 +#: check.c:496 check.c:971 check.c:1144 check.c:1259 check.c:1353 check.c:1481 +#: check.c:1557 check.c:1634 check.c:1700 check.c:1773 check.c:1952 +#: check.c:1971 check.c:2040 check.c:2092 file.c:378 file.c:415 function.c:189 +#: option.c:493 version.c:79 version.c:177 #, c-format msgid "could not open file \"%s\": %m" msgstr "ファイル\"%s\"をオープンできませんでした: %m" -#: check.c:522 +#: check.c:523 #, c-format msgid "Data type checks failed: %s" msgstr "問題を検出したデータ型確認項目: %s" -#: check.c:563 +#: check.c:564 #, c-format msgid "" "Performing Consistency Checks on Old Live Server\n" @@ -261,7 +261,7 @@ msgstr "" "元の実行中サーバーの一貫性チェックを実行しています。\n" "--------------------------------------------------" -#: check.c:569 +#: check.c:570 #, c-format msgid "" "Performing Consistency Checks\n" @@ -270,7 +270,7 @@ msgstr "" "整合性チェックを実行しています。\n" "-----------------------------" -#: check.c:718 +#: check.c:726 #, c-format msgid "" "\n" @@ -279,7 +279,7 @@ msgstr "" "\n" "* クラスタは互換性があります *" -#: check.c:726 +#: check.c:734 #, c-format msgid "" "\n" @@ -290,7 +290,7 @@ msgstr "" "この後pg_upgradeが失敗した場合は、続ける前に新しいクラスタを\n" "initdbで再作成する必要があります。" -#: check.c:767 +#: check.c:775 #, c-format msgid "" "Optimizer statistics are not transferred by pg_upgrade.\n" @@ -301,7 +301,7 @@ msgstr "" "新サーバーを起動した後、以下を行うことを検討してください。\n" " %s/vacuumdb %s--all --analyze-in-stages" -#: check.c:773 +#: check.c:781 #, c-format msgid "" "Running this script will delete the old cluster's data files:\n" @@ -310,7 +310,7 @@ msgstr "" "このスクリプトを実行すると、旧クラスタのデータファイルが削除されます:\n" " %s" -#: check.c:778 +#: check.c:786 #, c-format msgid "" "Could not create a script to delete the old cluster's data files\n" @@ -323,57 +323,57 @@ msgstr "" "ファイルを削除するためのスクリプトを作成できませんでした。 古い\n" "クラスタの内容は手動で削除する必要があります。" -#: check.c:790 +#: check.c:798 #, c-format msgid "Checking cluster versions" msgstr "クラスタのバージョンを確認しています" -#: check.c:802 +#: check.c:810 #, c-format msgid "This utility can only upgrade from PostgreSQL version %s and later." msgstr "このユーティリティではPostgreSQLバージョン%s以降のバージョンからのみアップグレードできます。" -#: check.c:807 +#: check.c:815 #, c-format msgid "This utility can only upgrade to PostgreSQL version %s." msgstr "このユーティリティは、PostgreSQLバージョン%sにのみアップグレードできます。" -#: check.c:816 +#: check.c:824 #, c-format msgid "This utility cannot be used to downgrade to older major PostgreSQL versions." msgstr "このユーティリティは PostgreSQL の過去のメジャーバージョンにダウングレードする用途では使用できません。" -#: check.c:821 +#: check.c:829 #, c-format msgid "Old cluster data and binary directories are from different major versions." msgstr "旧クラスタのデータとバイナリのディレクトリは異なるメジャーバージョンのものです。" -#: check.c:824 +#: check.c:832 #, c-format msgid "New cluster data and binary directories are from different major versions." msgstr "新クラスタのデータとバイナリのディレクトリは異なるメジャーバージョンのものです。" -#: check.c:839 +#: check.c:847 #, c-format msgid "When checking a live server, the old and new port numbers must be different." msgstr "稼働中のサーバーをチェックする場合、新旧のポート番号が異なっている必要があります。" -#: check.c:859 +#: check.c:867 #, c-format msgid "New cluster database \"%s\" is not empty: found relation \"%s.%s\"" msgstr "新クラスタのデータベース\"%s\"が空ではありません: リレーション\"%s.%s\"が見つかりました" -#: check.c:882 +#: check.c:890 #, c-format msgid "Checking for new cluster tablespace directories" msgstr "新しいクラスタのテーブル空間ディレクトリを確認しています" -#: check.c:893 +#: check.c:901 #, c-format msgid "new cluster tablespace directory already exists: \"%s\"" msgstr "新しいクラスタのテーブル空間ディレクトリはすでに存在します: \"%s\"" -#: check.c:926 +#: check.c:934 #, c-format msgid "" "\n" @@ -382,7 +382,7 @@ msgstr "" "\n" "警告: 新データディレクトリが旧データディレクトリ、つまり %sの中にあってはなりません" -#: check.c:950 +#: check.c:958 #, c-format msgid "" "\n" @@ -391,53 +391,54 @@ msgstr "" "\n" "警告: ユーザー定義テーブル空間の場所がデータディレクトリ、つまり %s の中にあってはなりません。" -#: check.c:960 +#: check.c:968 #, c-format msgid "Creating script to delete old cluster" msgstr "旧クラスタを削除するスクリプトを作成しています" -#: check.c:1014 +#: check.c:1022 #, c-format msgid "could not add execute permission to file \"%s\": %m" msgstr "ファイル\"%s\"に実行権限を追加できませんでした: %m" -#: check.c:1034 +#: check.c:1042 #, c-format msgid "Checking database user is the install user" msgstr "データベースユーザーがインストールユーザーかどうかをチェックしています" -#: check.c:1050 +#: check.c:1058 #, c-format msgid "database user \"%s\" is not the install user" msgstr "データベースユーザー\"%s\"がインストールユーザーではありません" -#: check.c:1061 +#: check.c:1069 #, c-format msgid "could not determine the number of users" msgstr "ユーザー数を特定できませんでした" -#: check.c:1069 +#: check.c:1077 #, c-format msgid "Only the install user can be defined in the new cluster." msgstr "新クラスタ内で定義できるのはインストールユーザーのみです。" -#: check.c:1098 +#: check.c:1106 #, c-format msgid "Checking database connection settings" msgstr "データベース接続の設定を確認しています" -#: check.c:1124 +#: check.c:1132 #, c-format msgid "template0 must not allow connections, i.e. its pg_database.datallowconn must be false" msgstr "template0 には接続を許可してはなりません。すなわち、pg_database.datallowconn は false である必要があります" -#: check.c:1150 check.c:1270 check.c:1367 check.c:1492 check.c:1568 -#: check.c:1626 check.c:1706 check.c:1897 check.c:2022 function.c:210 +#: check.c:1158 check.c:1278 check.c:1375 check.c:1500 check.c:1576 +#: check.c:1654 check.c:1713 check.c:1793 check.c:1984 check.c:2109 +#: function.c:210 #, c-format msgid "fatal" msgstr "致命的" -#: check.c:1151 +#: check.c:1159 #, c-format msgid "" "All non-template0 databases must allow connections, i.e. their\n" @@ -456,27 +457,27 @@ msgstr "" "一覧が以下のファイルにあります:\n" " %s" -#: check.c:1176 +#: check.c:1184 #, c-format msgid "Checking for prepared transactions" msgstr "準備済みトランザクションをチェックしています" -#: check.c:1185 +#: check.c:1193 #, c-format msgid "The source cluster contains prepared transactions" msgstr "移行元クラスタに準備済みトランザクションがあります" -#: check.c:1187 +#: check.c:1195 #, c-format msgid "The target cluster contains prepared transactions" msgstr "移行先クラスタに準備済みトランザクションがあります" -#: check.c:1212 +#: check.c:1220 #, c-format msgid "Checking for contrib/isn with bigint-passing mismatch" msgstr "bigint を渡す際にミスマッチが発生する contrib/isn をチェックしています" -#: check.c:1271 +#: check.c:1279 #, c-format msgid "" "Your installation contains \"contrib/isn\" functions which rely on the\n" @@ -496,12 +497,12 @@ msgstr "" "問題のある関数の一覧は以下のファイルにあります:\n" " %s" -#: check.c:1293 +#: check.c:1301 #, c-format msgid "Checking for user-defined postfix operators" msgstr "ユーザー定義の後置演算子を確認しています" -#: check.c:1368 +#: check.c:1376 #, c-format msgid "" "Your installation contains user-defined postfix operators, which are not\n" @@ -516,12 +517,12 @@ msgstr "" "以下のファイルにユーザー定義後置演算子の一覧があります:\n" " %s" -#: check.c:1392 +#: check.c:1400 #, c-format msgid "Checking for incompatible polymorphic functions" msgstr "非互換の多態関数を確認しています" -#: check.c:1493 +#: check.c:1501 #, c-format msgid "" "Your installation contains user-defined objects that refer to internal\n" @@ -539,12 +540,12 @@ msgstr "" "問題となるオブジェクトの一覧は以下のファイルにあります:\n" " %s" -#: check.c:1517 +#: check.c:1525 #, c-format msgid "Checking for tables WITH OIDS" msgstr "WITH OIDS宣言されたテーブルをチェックしています" -#: check.c:1569 +#: check.c:1577 #, c-format msgid "" "Your installation contains tables declared WITH OIDS, which is not\n" @@ -559,12 +560,36 @@ msgstr "" "以下のファイルにこの問題を抱えるテーブルの一覧があります:\n" " %s" -#: check.c:1596 +#: check.c:1602 +#, c-format +msgid "Checking for not-null constraint inconsistencies" +msgstr "非NULL制約の整合性を確認しています" + +#: check.c:1655 +#, c-format +msgid "" +"Your installation contains inconsistent NOT NULL constraints.\n" +"If the parent column(s) are NOT NULL, then the child column must\n" +"also be marked NOT NULL, or the upgrade will fail.\n" +"You can fix this by running\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"on each column listed in the file:\n" +" %s" +msgstr "" +"このクラスタには整合性の取れていない NOT NULL 制約があります。\n" +"親テーブルの列が NOT NULL である場合、子テーブルの列も NOT NULL としてマーク\n" +"されていなければ、アップグレードは失敗します。\n" +"この状態は、次のコマンドを\n" +" ALTER TABLE テーブル名 ALTER 列名 SET NOT NULL;\n" +"以下のファイルにリストされている各列に対して実行することで解消できます:\n" +"%s" + +#: check.c:1683 #, c-format msgid "Checking for roles starting with \"pg_\"" msgstr "'pg_' で始まるロールをチェックしています" -#: check.c:1627 +#: check.c:1714 #, c-format msgid "" "Your installation contains roles starting with \"pg_\".\n" @@ -580,12 +605,12 @@ msgstr "" "\"pg_\"で始まるロールの一覧は以下のファイルにあります:\n" " %s" -#: check.c:1647 +#: check.c:1734 #, c-format msgid "Checking for user-defined encoding conversions" msgstr "ユーザー定義のエンコーディング変換を確認しています" -#: check.c:1707 +#: check.c:1794 #, c-format msgid "" "Your installation contains user-defined encoding conversions.\n" @@ -603,52 +628,52 @@ msgstr "" "ユーザー定義のエンコーディング変換の一覧は以下のファイルにあります:\n" " %s" -#: check.c:1746 +#: check.c:1833 #, c-format msgid "Checking for new cluster logical replication slots" msgstr "新しいクラスタの論理レプリケーションスロットを確認しています" -#: check.c:1754 +#: check.c:1841 #, c-format msgid "could not count the number of logical replication slots" msgstr "論理レプリケーションスロットの数を数えられませんでした" -#: check.c:1759 +#: check.c:1846 #, c-format msgid "expected 0 logical replication slots but found %d" msgstr "論理レプリケーションスロット数は0であることを期待していましたが、%d個ありました" -#: check.c:1769 check.c:1820 +#: check.c:1856 check.c:1907 #, c-format msgid "could not determine parameter settings on new cluster" msgstr "新クラスタ上のパラメータ設定を決定できませんでした" -#: check.c:1774 +#: check.c:1861 #, c-format msgid "\"wal_level\" must be \"logical\" but is set to \"%s\"" msgstr "\"wal_level\"は\"logical\"でなければなりませんが\"%s\"に設定されています" -#: check.c:1780 +#: check.c:1867 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of logical replication slots (%d) on the old cluster" msgstr "\"max_replication_slots\" (%d) は旧クラスタにおける論理レプリケーションスロットの数(%d)以上でなければなりません" -#: check.c:1812 +#: check.c:1899 #, c-format msgid "Checking for new cluster configuration for subscriptions" msgstr "新しいクラスタの構成のサブスクリプションを確認しています" -#: check.c:1824 +#: check.c:1911 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of subscriptions (%d) on the old cluster" msgstr "\"max_replication_slots\" (%d) は旧クラスタにおけるサブスクリプションの数(%d)以上でなければなりません" -#: check.c:1846 +#: check.c:1933 #, c-format msgid "Checking for valid logical replication slots" msgstr "有効な論理レプリケーションスロットを確認しています" -#: check.c:1898 +#: check.c:1985 #, c-format msgid "" "Your installation contains logical replication slots that cannot be upgraded.\n" @@ -663,12 +688,12 @@ msgstr "" "問題のある列の一覧は、以下のファイルにあります: \n" " %s" -#: check.c:1922 +#: check.c:2009 #, c-format msgid "Checking for subscription state" msgstr "サブスクリプション状態を確認しています" -#: check.c:2023 +#: check.c:2110 #, c-format msgid "" "Your installation contains subscriptions without origin or having relations not in i (initialize) or r (ready) state.\n" @@ -727,8 +752,8 @@ msgstr "移行元クラスタにクラスタ状態情報がありません:" msgid "The target cluster lacks cluster state information:" msgstr "移行先クラスタにクラスタ状態情報がありません:" -#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:555 pg_upgrade.c:595 -#: pg_upgrade.c:944 relfilenumber.c:233 server.c:34 util.c:337 +#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:558 pg_upgrade.c:598 +#: pg_upgrade.c:947 relfilenumber.c:233 server.c:34 util.c:337 #, c-format msgid "%s" msgstr "%s" @@ -1701,82 +1726,82 @@ msgstr "" "新クラスタで稼働中のpostmasterがあるようです。\n" "そのpostmasterをシャットダウンしたのちやり直してください。" -#: pg_upgrade.c:414 +#: pg_upgrade.c:413 #, c-format msgid "Setting locale and encoding for new cluster" msgstr "新クラスタの、ロケールとエンコーディングを設定しています" -#: pg_upgrade.c:488 +#: pg_upgrade.c:491 #, c-format msgid "Analyzing all rows in the new cluster" msgstr "新クラスタ内のすべての行を分析しています" -#: pg_upgrade.c:501 +#: pg_upgrade.c:504 #, c-format msgid "Freezing all rows in the new cluster" msgstr "新クラスタ内のすべての行を凍結しています" -#: pg_upgrade.c:521 +#: pg_upgrade.c:524 #, c-format msgid "Restoring global objects in the new cluster" msgstr "新クラスタ内のグローバルオブジェクトを復元しています" -#: pg_upgrade.c:537 +#: pg_upgrade.c:540 #, c-format msgid "Restoring database schemas in the new cluster" msgstr "新クラスタ内にデータベーススキーマを復元しています" -#: pg_upgrade.c:661 +#: pg_upgrade.c:664 #, c-format msgid "Deleting files from new %s" msgstr "新しい %s からファイルを削除しています" -#: pg_upgrade.c:665 +#: pg_upgrade.c:668 #, c-format msgid "could not delete directory \"%s\"" msgstr "ディレクトリ\"%s\"を削除できませんでした" -#: pg_upgrade.c:684 +#: pg_upgrade.c:687 #, c-format msgid "Copying old %s to new server" msgstr "旧の %s を新サーバーにコピーしています" -#: pg_upgrade.c:710 +#: pg_upgrade.c:713 #, c-format msgid "Setting oldest XID for new cluster" msgstr "新クラスタの、最古のXIDを設定しています" -#: pg_upgrade.c:718 +#: pg_upgrade.c:721 #, c-format msgid "Setting next transaction ID and epoch for new cluster" msgstr "新クラスタの、次のトランザクションIDと基点を設定しています" -#: pg_upgrade.c:748 +#: pg_upgrade.c:751 #, c-format msgid "Setting next multixact ID and offset for new cluster" msgstr "新クラスタの、次のmultixact IDとオフセットを設定しています" -#: pg_upgrade.c:772 +#: pg_upgrade.c:775 #, c-format msgid "Setting oldest multixact ID in new cluster" msgstr "新クラスタの最古のmultixact IDを設定しています" -#: pg_upgrade.c:792 +#: pg_upgrade.c:795 #, c-format msgid "Resetting WAL archives" msgstr "WAL アーカイブをリセットしています" -#: pg_upgrade.c:835 +#: pg_upgrade.c:838 #, c-format msgid "Setting frozenxid and minmxid counters in new cluster" msgstr "新クラスタのfrozenxidとminmxidカウンタを設定しています" -#: pg_upgrade.c:837 +#: pg_upgrade.c:840 #, c-format msgid "Setting minmxid counter in new cluster" msgstr "新クラスタのminmxidカウンタを設定しています" -#: pg_upgrade.c:928 +#: pg_upgrade.c:931 #, c-format msgid "Restoring logical replication slots in the new cluster" msgstr "新クラスタ内の論理レプリケーションスロットを復元しています" @@ -2022,6 +2047,3 @@ msgstr "" "あります。以下のファイルをpsqlを使ってデータベースのスーパーユーザーとして実行することで\n" "これらの機能拡張をアップデートできます。\n" " %s" - -#~ msgid " --no-statistics do not import statistics from old cluster\n" -#~ msgstr " --no-statistics 旧クラスタから統計情報をインポートしない\n" diff --git a/src/bin/pg_upgrade/po/ru.po b/src/bin/pg_upgrade/po/ru.po index 353f7bef008de..95e34c358785f 100644 --- a/src/bin/pg_upgrade/po/ru.po +++ b/src/bin/pg_upgrade/po/ru.po @@ -1,14 +1,14 @@ # Russian message translation file for pg_upgrade # Copyright (C) 2017 PostgreSQL Global Development Group # This file is distributed under the same license as the PostgreSQL package. -# Alexander Lakhin , 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024. +# SPDX-FileCopyrightText: 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Alexander Lakhin # Maxim Yablokov , 2021. msgid "" msgstr "" "Project-Id-Version: pg_upgrade (PostgreSQL) 10\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" -"PO-Revision-Date: 2024-11-02 08:31+0300\n" +"POT-Creation-Date: 2025-08-09 07:11+0300\n" +"PO-Revision-Date: 2025-08-09 07:24+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" "Language: ru\n" @@ -61,11 +61,11 @@ msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "" "имя базы данных содержит символ новой строки или перевода каретки: \"%s\"\n" -#: check.c:111 +#: check.c:112 msgid "Checking for system-defined composite types in user tables" msgstr "Проверка системных составных типов в пользовательских таблицах" -#: check.c:118 +#: check.c:119 msgid "" "Your installation contains system-defined composite types in user tables.\n" "These type OIDs are not stable across PostgreSQL versions,\n" @@ -79,11 +79,11 @@ msgstr "" "проблемные\n" "столбцы и перезапустить обновление.\n" -#: check.c:132 +#: check.c:133 msgid "Checking for incompatible \"line\" data type" msgstr "Проверка несовместимого типа данных \"line\"" -#: check.c:137 +#: check.c:138 msgid "" "Your installation contains the \"line\" data type in user tables.\n" "This data type changed its internal and input/output format\n" @@ -99,11 +99,11 @@ msgstr "" "можете\n" "удалить проблемные столбцы и перезапустить обновление.\n" -#: check.c:154 +#: check.c:155 msgid "Checking for reg* data types in user tables" msgstr "Проверка типов данных reg* в пользовательских таблицах" -#: check.c:181 +#: check.c:182 msgid "" "Your installation contains one of the reg* data types in user tables.\n" "These data types reference system OIDs that are not preserved by\n" @@ -115,11 +115,11 @@ msgstr "" "pg_upgrade, так что обновление кластера в текущем состоянии невозможно. Вы\n" "можете удалить проблемные столбцы и перезапустить обновление.\n" -#: check.c:193 +#: check.c:194 msgid "Checking for incompatible \"aclitem\" data type" msgstr "Проверка несовместимого типа данных \"aclitem\"" -#: check.c:198 +#: check.c:199 msgid "" "Your installation contains the \"aclitem\" data type in user tables.\n" "The internal format of \"aclitem\" changed in PostgreSQL version 16\n" @@ -134,11 +134,11 @@ msgstr "" "и\n" "перезапустить обновление.\n" -#: check.c:217 +#: check.c:218 msgid "Checking for invalid \"unknown\" user columns" msgstr "Проверка неправильных пользовательских столбцов типа \"unknown\"" -#: check.c:222 +#: check.c:223 msgid "" "Your installation contains the \"unknown\" data type in user tables.\n" "This data type is no longer allowed in tables, so this cluster\n" @@ -152,12 +152,12 @@ msgstr "" "проблемные\n" "столбцы и перезапустить обновление.\n" -#: check.c:239 +#: check.c:240 msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "" "Проверка неправильных пользовательских столбцов типа \"sql_identifier\"" -#: check.c:244 +#: check.c:245 msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" "The on-disk format for this data type has changed, so this\n" @@ -169,12 +169,12 @@ msgstr "" "поэтому обновить данный кластер невозможно. Вы можете удалить проблемные\n" "столбцы и перезапустить обновление.\n" -#: check.c:255 +#: check.c:256 msgid "Checking for incompatible \"jsonb\" data type in user tables" msgstr "" "Проверка несовместимого типа данных \"jsonb\" в пользовательских таблицах" -#: check.c:260 +#: check.c:261 msgid "" "Your installation contains the \"jsonb\" data type in user tables.\n" "The internal format of \"jsonb\" changed during 9.4 beta so this\n" @@ -187,12 +187,12 @@ msgstr "" "и\n" "перезапустить обновление.\n" -#: check.c:272 +#: check.c:273 msgid "Checking for removed \"abstime\" data type in user tables" msgstr "" "Проверка удалённого типа данных \"abstime\" в пользовательских таблицах" -#: check.c:277 +#: check.c:278 msgid "" "Your installation contains the \"abstime\" data type in user tables.\n" "The \"abstime\" type has been removed in PostgreSQL version 12,\n" @@ -208,12 +208,12 @@ msgstr "" "поменять\n" "их тип на другой, а затем перезапустить обновление.\n" -#: check.c:285 +#: check.c:286 msgid "Checking for removed \"reltime\" data type in user tables" msgstr "" "Проверка удалённого типа данных \"reltime\" в пользовательских таблицах" -#: check.c:290 +#: check.c:291 msgid "" "Your installation contains the \"reltime\" data type in user tables.\n" "The \"reltime\" type has been removed in PostgreSQL version 12,\n" @@ -229,12 +229,12 @@ msgstr "" "поменять\n" "их тип на другой, а затем перезапустить обновление.\n" -#: check.c:298 +#: check.c:299 msgid "Checking for removed \"tinterval\" data type in user tables" msgstr "" "Проверка удалённого типа данных \"tinterval\" в пользовательских таблицах" -#: check.c:303 +#: check.c:304 msgid "" "Your installation contains the \"tinterval\" data type in user tables.\n" "The \"tinterval\" type has been removed in PostgreSQL version 12,\n" @@ -250,34 +250,34 @@ msgstr "" "поменять\n" "их тип на другой, а затем перезапустить обновление.\n" -#: check.c:345 +#: check.c:346 #, c-format msgid "Checking data type usage" msgstr "Проверка использования типов данных" -#: check.c:480 +#: check.c:481 #, c-format msgid "failed check: %s" msgstr "не пройдена проверка: %s" -#: check.c:483 +#: check.c:484 msgid "A list of the problem columns is in the file:" msgstr "Список проблемных столбцов приведён в файле:" -#: check.c:495 check.c:963 check.c:1136 check.c:1251 check.c:1345 check.c:1473 -#: check.c:1549 check.c:1613 check.c:1686 check.c:1865 check.c:1884 -#: check.c:1953 check.c:2005 file.c:378 file.c:415 function.c:189 option.c:493 -#: version.c:79 version.c:177 +#: check.c:496 check.c:971 check.c:1144 check.c:1259 check.c:1353 check.c:1481 +#: check.c:1557 check.c:1634 check.c:1700 check.c:1773 check.c:1952 +#: check.c:1971 check.c:2040 check.c:2092 file.c:378 file.c:415 function.c:189 +#: option.c:493 version.c:79 version.c:177 #, c-format msgid "could not open file \"%s\": %m" msgstr "не удалось открыть файл \"%s\": %m" -#: check.c:522 +#: check.c:523 #, c-format msgid "Data type checks failed: %s" msgstr "Проверки типов данных не пройдены: %s" -#: check.c:563 +#: check.c:564 #, c-format msgid "" "Performing Consistency Checks on Old Live Server\n" @@ -286,7 +286,7 @@ msgstr "" "Проверка целостности на старом работающем сервере\n" "-------------------------------------------------" -#: check.c:569 +#: check.c:570 #, c-format msgid "" "Performing Consistency Checks\n" @@ -295,7 +295,7 @@ msgstr "" "Проведение проверок целостности\n" "-------------------------------" -#: check.c:718 +#: check.c:726 #, c-format msgid "" "\n" @@ -304,7 +304,7 @@ msgstr "" "\n" "*Кластеры совместимы*" -#: check.c:726 +#: check.c:734 #, c-format msgid "" "\n" @@ -316,7 +316,7 @@ msgstr "" "initdb\n" "для нового кластера, чтобы продолжить." -#: check.c:767 +#: check.c:775 #, c-format msgid "" "Optimizer statistics are not transferred by pg_upgrade.\n" @@ -327,7 +327,7 @@ msgstr "" "Запустив новый сервер, имеет смысл выполнить:\n" " %s/vacuumdb %s--all --analyze-in-stages" -#: check.c:773 +#: check.c:781 #, c-format msgid "" "Running this script will delete the old cluster's data files:\n" @@ -336,7 +336,7 @@ msgstr "" "При запуске этого скрипта будут удалены файлы данных старого кластера:\n" " %s" -#: check.c:778 +#: check.c:786 #, c-format msgid "" "Could not create a script to delete the old cluster's data files\n" @@ -349,24 +349,24 @@ msgstr "" "пространства или каталог данных нового кластера.\n" "Содержимое старого кластера нужно будет удалить вручную." -#: check.c:790 +#: check.c:798 #, c-format msgid "Checking cluster versions" msgstr "Проверка версий кластеров" -#: check.c:802 +#: check.c:810 #, c-format msgid "This utility can only upgrade from PostgreSQL version %s and later." msgstr "" "Эта утилита может производить обновление только с версии PostgreSQL %s и " "новее." -#: check.c:807 +#: check.c:815 #, c-format msgid "This utility can only upgrade to PostgreSQL version %s." msgstr "Эта утилита может повышать версию PostgreSQL только до %s." -#: check.c:816 +#: check.c:824 #, c-format msgid "" "This utility cannot be used to downgrade to older major PostgreSQL versions." @@ -374,7 +374,7 @@ msgstr "" "Эта утилита не может понижать версию до более старой основной версии " "PostgreSQL." -#: check.c:821 +#: check.c:829 #, c-format msgid "" "Old cluster data and binary directories are from different major versions." @@ -382,7 +382,7 @@ msgstr "" "Каталоги данных и исполняемых файлов старого кластера относятся к разным " "основным версиям." -#: check.c:824 +#: check.c:832 #, c-format msgid "" "New cluster data and binary directories are from different major versions." @@ -390,7 +390,7 @@ msgstr "" "Каталоги данных и исполняемых файлов нового кластера относятся к разным " "основным версиям." -#: check.c:839 +#: check.c:847 #, c-format msgid "" "When checking a live server, the old and new port numbers must be different." @@ -398,24 +398,24 @@ msgstr "" "Для проверки работающего сервера новый номер порта должен отличаться от " "старого." -#: check.c:859 +#: check.c:867 #, c-format msgid "New cluster database \"%s\" is not empty: found relation \"%s.%s\"" msgstr "" "Новая база данных кластера \"%s\" не пустая: найдено отношение \"%s.%s\"" -#: check.c:882 +#: check.c:890 #, c-format msgid "Checking for new cluster tablespace directories" msgstr "Проверка каталогов табличных пространств в новом кластере" -#: check.c:893 +#: check.c:901 #, c-format msgid "new cluster tablespace directory already exists: \"%s\"" msgstr "" "каталог табличного пространства в новом кластере уже существует: \"%s\"" -#: check.c:926 +#: check.c:934 #, c-format msgid "" "\n" @@ -426,7 +426,7 @@ msgstr "" "ПРЕДУПРЕЖДЕНИЕ: новый каталог данных не должен располагаться внутри старого " "каталога данных, то есть, в %s" -#: check.c:950 +#: check.c:958 #, c-format msgid "" "\n" @@ -437,42 +437,42 @@ msgstr "" "ПРЕДУПРЕЖДЕНИЕ: пользовательские табличные пространства не должны " "располагаться внутри каталога данных, то есть, в %s" -#: check.c:960 +#: check.c:968 #, c-format msgid "Creating script to delete old cluster" msgstr "Создание скрипта для удаления старого кластера" -#: check.c:1014 +#: check.c:1022 #, c-format msgid "could not add execute permission to file \"%s\": %m" msgstr "не удалось добавить право выполнения для файла \"%s\": %m" -#: check.c:1034 +#: check.c:1042 #, c-format msgid "Checking database user is the install user" msgstr "Проверка, является ли пользователь БД стартовым пользователем" -#: check.c:1050 +#: check.c:1058 #, c-format msgid "database user \"%s\" is not the install user" msgstr "пользователь БД \"%s\" не является стартовым пользователем" -#: check.c:1061 +#: check.c:1069 #, c-format msgid "could not determine the number of users" msgstr "не удалось определить количество пользователей" -#: check.c:1069 +#: check.c:1077 #, c-format msgid "Only the install user can be defined in the new cluster." msgstr "В новом кластере может быть определён только стартовый пользователь." -#: check.c:1098 +#: check.c:1106 #, c-format msgid "Checking database connection settings" msgstr "Проверка параметров подключения к базе данных" -#: check.c:1124 +#: check.c:1132 #, c-format msgid "" "template0 must not allow connections, i.e. its pg_database.datallowconn must " @@ -481,13 +481,14 @@ msgstr "" "база template0 не должна допускать подключения, то есть её свойство " "pg_database.datallowconn должно быть false" -#: check.c:1150 check.c:1270 check.c:1367 check.c:1492 check.c:1568 -#: check.c:1626 check.c:1706 check.c:1897 check.c:2022 function.c:210 +#: check.c:1158 check.c:1278 check.c:1375 check.c:1500 check.c:1576 +#: check.c:1654 check.c:1713 check.c:1793 check.c:1984 check.c:2109 +#: function.c:210 #, c-format msgid "fatal" msgstr "сбой" -#: check.c:1151 +#: check.c:1159 #, c-format msgid "" "All non-template0 databases must allow connections, i.e. their\n" @@ -506,27 +507,27 @@ msgstr "" "с этой проблемой содержится в файле:\n" " %s" -#: check.c:1176 +#: check.c:1184 #, c-format msgid "Checking for prepared transactions" msgstr "Проверка наличия подготовленных транзакций" -#: check.c:1185 +#: check.c:1193 #, c-format msgid "The source cluster contains prepared transactions" msgstr "Исходный кластер содержит подготовленные транзакции" -#: check.c:1187 +#: check.c:1195 #, c-format msgid "The target cluster contains prepared transactions" msgstr "Целевой кластер содержит подготовленные транзакции" -#: check.c:1212 +#: check.c:1220 #, c-format msgid "Checking for contrib/isn with bigint-passing mismatch" msgstr "Проверка несоответствия при передаче bigint в contrib/isn" -#: check.c:1271 +#: check.c:1279 #, c-format msgid "" "Your installation contains \"contrib/isn\" functions which rely on the\n" @@ -548,12 +549,12 @@ msgstr "" "проблемных функций приведён в файле:\n" " %s" -#: check.c:1293 +#: check.c:1301 #, c-format msgid "Checking for user-defined postfix operators" msgstr "Проверка пользовательских постфиксных операторов" -#: check.c:1368 +#: check.c:1376 #, c-format msgid "" "Your installation contains user-defined postfix operators, which are not\n" @@ -569,12 +570,12 @@ msgstr "" "Список пользовательских постфиксных операторов приведён в файле:\n" " %s" -#: check.c:1392 +#: check.c:1400 #, c-format msgid "Checking for incompatible polymorphic functions" msgstr "Проверка несовместимых полиморфных функций" -#: check.c:1493 +#: check.c:1501 #, c-format msgid "" "Your installation contains user-defined objects that refer to internal\n" @@ -595,12 +596,12 @@ msgstr "" "Список проблемных объектов приведён в файле:\n" " %s" -#: check.c:1517 +#: check.c:1525 #, c-format msgid "Checking for tables WITH OIDS" msgstr "Проверка таблиц со свойством WITH OIDS" -#: check.c:1569 +#: check.c:1577 #, c-format msgid "" "Your installation contains tables declared WITH OIDS, which is not\n" @@ -616,12 +617,36 @@ msgstr "" "Список проблемных таблиц приведён в файле:\n" " %s" -#: check.c:1596 +#: check.c:1602 +#, c-format +msgid "Checking for not-null constraint inconsistencies" +msgstr "Проверка несогласованных ограничений NOT NULL" + +#: check.c:1655 +#, c-format +msgid "" +"Your installation contains inconsistent NOT NULL constraints.\n" +"If the parent column(s) are NOT NULL, then the child column must\n" +"also be marked NOT NULL, or the upgrade will fail.\n" +"You can fix this by running\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"on each column listed in the file:\n" +" %s" +msgstr "" +"В вашей инсталляции содержатся несогласованные ограничения NOT NULL.\n" +"Если родительские столбцы помечены NOT NULL, пометку NOT NULL должны\n" +"иметь и их дочерние столбцы, иначе обновление невозможно.\n" +"Исправить эту ситуацию можно, выполнив:\n" +" ALTER TABLE имя_таблицы ALTER столбец SET NOT NULL;\n" +"для всех столбцов, перечисленных в файле:\n" +" %s" + +#: check.c:1683 #, c-format msgid "Checking for roles starting with \"pg_\"" msgstr "Проверка ролей с именами, начинающимися с \"pg_\"" -#: check.c:1627 +#: check.c:1714 #, c-format msgid "" "Your installation contains roles starting with \"pg_\".\n" @@ -636,12 +661,12 @@ msgstr "" "Список ролей с префиксом \"pg_\" приведён в файле:\n" " %s" -#: check.c:1647 +#: check.c:1734 #, c-format msgid "Checking for user-defined encoding conversions" msgstr "Проверка пользовательских перекодировок" -#: check.c:1707 +#: check.c:1794 #, c-format msgid "" "Your installation contains user-defined encoding conversions.\n" @@ -658,32 +683,32 @@ msgstr "" "Список пользовательских перекодировок приведён в файле:\n" " %s" -#: check.c:1746 +#: check.c:1833 #, c-format msgid "Checking for new cluster logical replication slots" msgstr "Проверка слотов логической репликации в новом кластере" -#: check.c:1754 +#: check.c:1841 #, c-format msgid "could not count the number of logical replication slots" msgstr "не удалось получить количество слотов логической репликации" -#: check.c:1759 +#: check.c:1846 #, c-format msgid "expected 0 logical replication slots but found %d" msgstr "обнаружено слотов логической репликации: %d, тогда как ожидалось 0" -#: check.c:1769 check.c:1820 +#: check.c:1856 check.c:1907 #, c-format msgid "could not determine parameter settings on new cluster" msgstr "не удалось получить значения параметров в новом кластере" -#: check.c:1774 +#: check.c:1861 #, c-format msgid "\"wal_level\" must be \"logical\" but is set to \"%s\"" msgstr "\"wal_level\" должен иметь значение \"logical\", а имеет \"%s\"" -#: check.c:1780 +#: check.c:1867 #, c-format msgid "" "\"max_replication_slots\" (%d) must be greater than or equal to the number " @@ -692,12 +717,12 @@ msgstr "" "значение \"max_replication_slots\" (%d) должно быть больше или равно числу " "слотов логической репликации (%d) в старом кластере" -#: check.c:1812 +#: check.c:1899 #, c-format msgid "Checking for new cluster configuration for subscriptions" msgstr "Проверка конфигурации нового кластера для использования подписок" -#: check.c:1824 +#: check.c:1911 #, c-format msgid "" "\"max_replication_slots\" (%d) must be greater than or equal to the number " @@ -706,12 +731,12 @@ msgstr "" "значение \"max_replication_slots\" (%d) должно быть больше или равно числу " "подписок (%d) в старом кластере" -#: check.c:1846 +#: check.c:1933 #, c-format msgid "Checking for valid logical replication slots" msgstr "Проверка корректности слотов логической репликации" -#: check.c:1898 +#: check.c:1985 #, c-format msgid "" "Your installation contains logical replication slots that cannot be " @@ -729,12 +754,12 @@ msgstr "" "Список проблемных слотов приведён в файле:\n" " %s" -#: check.c:1922 +#: check.c:2009 #, c-format msgid "Checking for subscription state" msgstr "Проверка состояния подписок" -#: check.c:2023 +#: check.c:2110 #, c-format msgid "" "Your installation contains subscriptions without origin or having relations " @@ -2083,7 +2108,7 @@ msgstr "не удалось открыть файл с версией \"%s\": %m msgid "could not parse version file \"%s\"" msgstr "не удалось разобрать файл с версией \"%s\"" -#: server.c:310 +#: server.c:299 #, c-format msgid "" "\n" @@ -2092,7 +2117,7 @@ msgstr "" "\n" "%s" -#: server.c:314 +#: server.c:303 #, c-format msgid "" "could not connect to source postmaster started with the command:\n" @@ -2102,7 +2127,7 @@ msgstr "" "командой:\n" "%s" -#: server.c:318 +#: server.c:307 #, c-format msgid "" "could not connect to target postmaster started with the command:\n" @@ -2112,26 +2137,26 @@ msgstr "" "командой:\n" "%s" -#: server.c:332 +#: server.c:321 #, c-format msgid "pg_ctl failed to start the source server, or connection failed" msgstr "" "программа pg_ctl не смогла запустить исходный сервер, либо к нему не удалось " "подключиться" -#: server.c:334 +#: server.c:323 #, c-format msgid "pg_ctl failed to start the target server, or connection failed" msgstr "" "программа pg_ctl не смогла запустить целевой сервер, либо к нему не удалось " "подключиться" -#: server.c:379 +#: server.c:368 #, c-format msgid "out of memory" msgstr "нехватка памяти" -#: server.c:392 +#: server.c:381 #, c-format msgid "libpq environment variable %s has a non-local server value: %s" msgstr "" diff --git a/src/bin/pg_upgrade/po/sv.po b/src/bin/pg_upgrade/po/sv.po index 51ad41bff555c..309f6bc7965ad 100644 --- a/src/bin/pg_upgrade/po/sv.po +++ b/src/bin/pg_upgrade/po/sv.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PostgreSQL 17\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-12 13:50+0000\n" -"PO-Revision-Date: 2025-02-12 20:38+0100\n" +"POT-Creation-Date: 2025-08-09 05:19+0000\n" +"PO-Revision-Date: 2025-08-09 20:08+0200\n" "Last-Translator: Dennis Björklund \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -47,21 +47,21 @@ msgstr "användaren finns inte" msgid "user name lookup failure: error code %lu" msgstr "misslyckad sökning efter användarnamn: felkod %lu" -#: ../../fe_utils/string_utils.c:608 +#: ../../fe_utils/string_utils.c:587 #, c-format msgid "shell command argument contains a newline or carriage return: \"%s\"\n" msgstr "shell-kommandots argument innehåller nyrad eller vagnretur: \"%s\"\n" -#: ../../fe_utils/string_utils.c:781 +#: ../../fe_utils/string_utils.c:760 #, c-format msgid "database name contains a newline or carriage return: \"%s\"\n" msgstr "databasnamnet innehåller nyrad eller vagnretur: \"%s\"\n" -#: check.c:111 +#: check.c:112 msgid "Checking for system-defined composite types in user tables" msgstr "Letar i användartabeller efter systemdefinierade typer av sorten \"composite\"" -#: check.c:118 +#: check.c:119 msgid "" "Your installation contains system-defined composite types in user tables.\n" "These type OIDs are not stable across PostgreSQL versions,\n" @@ -73,11 +73,11 @@ msgstr "" "PostgreSQL-versioner så detta kluster kan inte uppgraderas för tillfället.\n" "Du kan slänga problemkolumnerna och återstarta uppgraderingen.\n" -#: check.c:132 +#: check.c:133 msgid "Checking for incompatible \"line\" data type" msgstr "Letar efter inkompatibel \"line\"-datatyp" -#: check.c:137 +#: check.c:138 msgid "" "Your installation contains the \"line\" data type in user tables.\n" "This data type changed its internal and input/output format\n" @@ -91,11 +91,11 @@ msgstr "" "Du kan radera problemkolumnerna och återstarta uppgraderingen.\n" # FIXME: is this msgid correct? -#: check.c:154 +#: check.c:155 msgid "Checking for reg* data types in user tables" msgstr "Letar efter reg*-datatyper i användartabeller" -#: check.c:181 +#: check.c:182 msgid "" "Your installation contains one of the reg* data types in user tables.\n" "These data types reference system OIDs that are not preserved by\n" @@ -107,11 +107,11 @@ msgstr "" "så detta kluster kan för närvarande inte uppgraderas. Du kan ta bort\n" "problemkolumnerna och starta om uppgraderingen.\n" -#: check.c:193 +#: check.c:194 msgid "Checking for incompatible \"aclitem\" data type" msgstr "Letar efter inkompatibel datatype \"aclitem\"" -#: check.c:198 +#: check.c:199 msgid "" "Your installation contains the \"aclitem\" data type in user tables.\n" "The internal format of \"aclitem\" changed in PostgreSQL version 16\n" @@ -123,11 +123,11 @@ msgstr "" "kan för närvarande inte uppgraderas. Du kan ta bort problemkolumnerna\n" "och starta om uppgraderingen.\n" -#: check.c:217 +#: check.c:218 msgid "Checking for invalid \"unknown\" user columns" msgstr "Letar efter ogiltiga användarkolumner av typen \"unknown\"" -#: check.c:222 +#: check.c:223 msgid "" "Your installation contains the \"unknown\" data type in user tables.\n" "This data type is no longer allowed in tables, so this cluster\n" @@ -139,11 +139,11 @@ msgstr "" "för närvarande inte uppgraderas. Du kan radera problemkolumnerna och\n" "återstarta uppgraderingen.\n" -#: check.c:239 +#: check.c:240 msgid "Checking for invalid \"sql_identifier\" user columns" msgstr "Letar efter ogiltiga användarkolumner av typen \"sql_identifier\"" -#: check.c:244 +#: check.c:245 msgid "" "Your installation contains the \"sql_identifier\" data type in user tables.\n" "The on-disk format for this data type has changed, so this\n" @@ -155,11 +155,11 @@ msgstr "" "närvarande inte uppgraderas. Du kan radera problemkolumnerna och\n" "återstarta uppgraderingen.\n" -#: check.c:255 +#: check.c:256 msgid "Checking for incompatible \"jsonb\" data type in user tables" msgstr "Letar efter inkompatibel datatyp \"jsonb\"-datatyp i användartabeller" -#: check.c:260 +#: check.c:261 msgid "" "Your installation contains the \"jsonb\" data type in user tables.\n" "The internal format of \"jsonb\" changed during 9.4 beta so this\n" @@ -171,11 +171,11 @@ msgstr "" "för närvarande inte uppgraderas. Du kan ta bort problemkolumnerna och\n" "starta om uppgraderingen.\n" -#: check.c:272 +#: check.c:273 msgid "Checking for removed \"abstime\" data type in user tables" msgstr "Letar efter borttagen datatype \"abstime\" i användartabeller" -#: check.c:277 +#: check.c:278 msgid "" "Your installation contains the \"abstime\" data type in user tables.\n" "The \"abstime\" type has been removed in PostgreSQL version 12,\n" @@ -188,11 +188,11 @@ msgstr "" "kan för närvarande inte uppgraderas. Du kan ta bort problemkolumnerna\n" "eller ändra dem till en annan datatype och starta om uppgraderingen.\n" -#: check.c:285 +#: check.c:286 msgid "Checking for removed \"reltime\" data type in user tables" msgstr "Letar efter borttagen datatype \"reltime\" i användartabeller" -#: check.c:290 +#: check.c:291 msgid "" "Your installation contains the \"reltime\" data type in user tables.\n" "The \"reltime\" type has been removed in PostgreSQL version 12,\n" @@ -205,11 +205,11 @@ msgstr "" "kan för närvarande inte uppgraderas. Du kan ta bort problemkolumnerna\n" "eller ändra dem till en annan datatype och starta om uppgraderingen.\n" -#: check.c:298 +#: check.c:299 msgid "Checking for removed \"tinterval\" data type in user tables" msgstr "Letar efter borttagen datatype \"tinterval\" i användartabeller" -#: check.c:303 +#: check.c:304 msgid "" "Your installation contains the \"tinterval\" data type in user tables.\n" "The \"tinterval\" type has been removed in PostgreSQL version 12,\n" @@ -222,34 +222,34 @@ msgstr "" "kan för närvarande inte uppgraderas. Du kan ta bort problemkolumnerna\n" "eller ändra dem till en annan datatype och starta om uppgraderingen.\n" -#: check.c:345 +#: check.c:346 #, c-format msgid "Checking data type usage" msgstr "Letar efter användning av datatyp" -#: check.c:480 +#: check.c:481 #, c-format msgid "failed check: %s" msgstr "misslyckad kontroll: %s" -#: check.c:483 +#: check.c:484 msgid "A list of the problem columns is in the file:" msgstr "En lista med problemkolumner finns i filen:" -#: check.c:495 check.c:963 check.c:1136 check.c:1251 check.c:1345 check.c:1473 -#: check.c:1549 check.c:1613 check.c:1686 check.c:1865 check.c:1884 -#: check.c:1953 check.c:2005 file.c:378 file.c:415 function.c:189 option.c:493 -#: version.c:79 version.c:177 +#: check.c:496 check.c:971 check.c:1144 check.c:1259 check.c:1353 check.c:1481 +#: check.c:1557 check.c:1634 check.c:1700 check.c:1773 check.c:1952 +#: check.c:1971 check.c:2040 check.c:2092 file.c:378 file.c:415 function.c:189 +#: option.c:493 version.c:79 version.c:177 #, c-format msgid "could not open file \"%s\": %m" msgstr "kunde inte öppna fil \"%s\": %m" -#: check.c:522 +#: check.c:523 #, c-format msgid "Data type checks failed: %s" msgstr "Kontroll av datatyper misslyckades: %s" -#: check.c:563 +#: check.c:564 #, c-format msgid "" "Performing Consistency Checks on Old Live Server\n" @@ -258,7 +258,7 @@ msgstr "" "Utför konsistenskontroller på gamla live-servern\n" "------------------------------------------------" -#: check.c:569 +#: check.c:570 #, c-format msgid "" "Performing Consistency Checks\n" @@ -267,7 +267,7 @@ msgstr "" "Utför konsistenskontroller\n" "--------------------------" -#: check.c:718 +#: check.c:726 #, c-format msgid "" "\n" @@ -276,7 +276,7 @@ msgstr "" "\n" "*Klustren är kompatibla*" -#: check.c:726 +#: check.c:734 #, c-format msgid "" "\n" @@ -287,7 +287,7 @@ msgstr "" "Om pg_upgrade misslyckas efter denna punkt så måste du\n" "köra om initdb på nya klustret innan du fortsätter." -#: check.c:767 +#: check.c:775 #, c-format msgid "" "Optimizer statistics are not transferred by pg_upgrade.\n" @@ -298,7 +298,7 @@ msgstr "" "När du startar nya servern så vill du nog köra:\n" " %s/vacuumdb %s--all --analyze-in-stages" -#: check.c:773 +#: check.c:781 #, c-format msgid "" "Running this script will delete the old cluster's data files:\n" @@ -307,7 +307,7 @@ msgstr "" "När detta skript körs så raderas gamla klustrets datafiler:\n" " %s" -#: check.c:778 +#: check.c:786 #, c-format msgid "" "Could not create a script to delete the old cluster's data files\n" @@ -320,57 +320,57 @@ msgstr "" "ligger i gamla klusterkatalogen. Det gamla klustrets innehåll\n" "måste raderas för hand." -#: check.c:790 +#: check.c:798 #, c-format msgid "Checking cluster versions" msgstr "Kontrollerar klustrets versioner" -#: check.c:802 +#: check.c:810 #, c-format msgid "This utility can only upgrade from PostgreSQL version %s and later." msgstr "Detta verktyg kan bara uppgradera från PostgreSQL version %s eller senare." -#: check.c:807 +#: check.c:815 #, c-format msgid "This utility can only upgrade to PostgreSQL version %s." msgstr "Detta verktyg kan bara uppgradera till PostgreSQL version %s." -#: check.c:816 +#: check.c:824 #, c-format msgid "This utility cannot be used to downgrade to older major PostgreSQL versions." msgstr "Detta verktyg kan inte användas för att nergradera till äldre major-versioner av PostgreSQL." -#: check.c:821 +#: check.c:829 #, c-format msgid "Old cluster data and binary directories are from different major versions." msgstr "Gammal klusterdata och binära kataloger är från olika major-versioner." -#: check.c:824 +#: check.c:832 #, c-format msgid "New cluster data and binary directories are from different major versions." msgstr "Nya klusterdata och binära kataloger är från olika major-versioner." -#: check.c:839 +#: check.c:847 #, c-format msgid "When checking a live server, the old and new port numbers must be different." msgstr "Vid kontroll av en live-server så måste gamla och nya portnumren vara olika." -#: check.c:859 +#: check.c:867 #, c-format msgid "New cluster database \"%s\" is not empty: found relation \"%s.%s\"" msgstr "Nya databasklustret \"%s\" är inte tomt: hittade relation \"%s.%s\"" -#: check.c:882 +#: check.c:890 #, c-format msgid "Checking for new cluster tablespace directories" msgstr "Letar efter nya tablespace-kataloger i klustret" -#: check.c:893 +#: check.c:901 #, c-format msgid "new cluster tablespace directory already exists: \"%s\"" msgstr "i klustret finns redan ny tablespace-katalog: \"%s\"" -#: check.c:926 +#: check.c:934 #, c-format msgid "" "\n" @@ -379,7 +379,7 @@ msgstr "" "\n" "VARNING: nya datakatalogen skall inte ligga inuti den gamla datakatalogen, dvs. %s" -#: check.c:950 +#: check.c:958 #, c-format msgid "" "\n" @@ -388,53 +388,54 @@ msgstr "" "\n" "VARNING: användardefinierade tabellutrymmens plats skall inte vara i datakatalogen, dvs. %s" -#: check.c:960 +#: check.c:968 #, c-format msgid "Creating script to delete old cluster" msgstr "Skapar skript för att radera gamla klustret" -#: check.c:1014 +#: check.c:1022 #, c-format msgid "could not add execute permission to file \"%s\": %m" msgstr "kunde inte lägga till rättigheten \"körbar\" på filen \"%s\": %m" -#: check.c:1034 +#: check.c:1042 #, c-format msgid "Checking database user is the install user" msgstr "Kontrollerar att databasanvändaren är installationsanvändaren" -#: check.c:1050 +#: check.c:1058 #, c-format msgid "database user \"%s\" is not the install user" msgstr "databasanvändare \"%s\" är inte installationsanvändaren" -#: check.c:1061 +#: check.c:1069 #, c-format msgid "could not determine the number of users" msgstr "kunde inte bestämma antalet användare" -#: check.c:1069 +#: check.c:1077 #, c-format msgid "Only the install user can be defined in the new cluster." msgstr "Bara installationsanvändaren får finnas i nya klustret." -#: check.c:1098 +#: check.c:1106 #, c-format msgid "Checking database connection settings" msgstr "Kontrollerar databasens anslutningsinställningar" -#: check.c:1124 +#: check.c:1132 #, c-format msgid "template0 must not allow connections, i.e. its pg_database.datallowconn must be false" msgstr "template0 får inte tillåta anslutningar, dvs dess pg_database.datallowconn måste vara false" -#: check.c:1150 check.c:1270 check.c:1367 check.c:1492 check.c:1568 -#: check.c:1626 check.c:1706 check.c:1897 check.c:2022 function.c:210 +#: check.c:1158 check.c:1278 check.c:1375 check.c:1500 check.c:1576 +#: check.c:1654 check.c:1713 check.c:1793 check.c:1984 check.c:2109 +#: function.c:210 #, c-format msgid "fatal" msgstr "fatalt" -#: check.c:1151 +#: check.c:1159 #, c-format msgid "" "All non-template0 databases must allow connections, i.e. their\n" @@ -453,27 +454,27 @@ msgstr "" "problemdatabaser finns i filen:\n" " %s" -#: check.c:1176 +#: check.c:1184 #, c-format msgid "Checking for prepared transactions" msgstr "Letar efter förberedda transaktioner" -#: check.c:1185 +#: check.c:1193 #, c-format msgid "The source cluster contains prepared transactions" msgstr "Källklustret innehåller förberedda transaktioner" -#: check.c:1187 +#: check.c:1195 #, c-format msgid "The target cluster contains prepared transactions" msgstr "Målklustret innehåller förberedda transaktioner" -#: check.c:1212 +#: check.c:1220 #, c-format msgid "Checking for contrib/isn with bigint-passing mismatch" msgstr "Letar efter contrib/isn med bigint-anropsfel" -#: check.c:1271 +#: check.c:1279 #, c-format msgid "" "Your installation contains \"contrib/isn\" functions which rely on the\n" @@ -492,12 +493,12 @@ msgstr "" "En lista med problemfunktionerna finns i filen:\n" " %s" -#: check.c:1293 +#: check.c:1301 #, c-format msgid "Checking for user-defined postfix operators" msgstr "Letar efter användardefinierade postfix-operatorer" -#: check.c:1368 +#: check.c:1376 #, c-format msgid "" "Your installation contains user-defined postfix operators, which are not\n" @@ -512,12 +513,12 @@ msgstr "" "En lista med användardefinierade postfix-operatorer finns i filen:\n" " %s" -#: check.c:1392 +#: check.c:1400 #, c-format msgid "Checking for incompatible polymorphic functions" msgstr "Letar efter inkompatibla polymorfa funktioner" -#: check.c:1493 +#: check.c:1501 #, c-format msgid "" "Your installation contains user-defined objects that refer to internal\n" @@ -538,12 +539,12 @@ msgstr "" "En lista med problemobjekten finns i filen:\n" " %s" -#: check.c:1517 +#: check.c:1525 #, c-format msgid "Checking for tables WITH OIDS" msgstr "Letar efter tabeller med WITH OIDS" -#: check.c:1569 +#: check.c:1577 #, c-format msgid "" "Your installation contains tables declared WITH OIDS, which is not\n" @@ -558,12 +559,36 @@ msgstr "" "En lista över tabeller med detta problem finns i filen:\n" " %s" -#: check.c:1596 +#: check.c:1602 +#, c-format +msgid "Checking for not-null constraint inconsistencies" +msgstr "Kontrollerar att icke-null-villkor är konsistenta" + +#: check.c:1655 +#, c-format +msgid "" +"Your installation contains inconsistent NOT NULL constraints.\n" +"If the parent column(s) are NOT NULL, then the child column must\n" +"also be marked NOT NULL, or the upgrade will fail.\n" +"You can fix this by running\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"on each column listed in the file:\n" +" %s" +msgstr "" +"Din installation har inkonsistenta NOT NULL-villkor.\n" +"Om en föräldrakolumn är NOT NULL så måste barnkolumnen också\n" +"sättas till NOT NULL annars så kommer uppgraderingen misslyckas.\n" +"Du kan lösa detta genom att köra\n" +" ALTER TABLE tablename ALTER column SET NOT NULL;\n" +"för varje kolumn som listas i filen:\n" +" %s" + +#: check.c:1683 #, c-format msgid "Checking for roles starting with \"pg_\"" msgstr "Letar efter roller som startar med \"pg_\"" -#: check.c:1627 +#: check.c:1714 #, c-format msgid "" "Your installation contains roles starting with \"pg_\".\n" @@ -578,12 +603,12 @@ msgstr "" "En lista med rollerna som startar på \"pg_\" finns i denna fil:\n" " %s" -#: check.c:1647 +#: check.c:1734 #, c-format msgid "Checking for user-defined encoding conversions" msgstr "Letar efter användardefinierade teckenkodkonverteringar" -#: check.c:1707 +#: check.c:1794 #, c-format msgid "" "Your installation contains user-defined encoding conversions.\n" @@ -600,52 +625,52 @@ msgstr "" "En lista med användardefinierade teckenkodkonverteringar finns i filen:\n" " %s" -#: check.c:1746 +#: check.c:1833 #, c-format msgid "Checking for new cluster logical replication slots" msgstr "Letar efter replikeringsslottar i nya klustret" -#: check.c:1754 +#: check.c:1841 #, c-format msgid "could not count the number of logical replication slots" msgstr "kunde inte räkna antalet logiska replikeringsslottar" -#: check.c:1759 +#: check.c:1846 #, c-format msgid "expected 0 logical replication slots but found %d" msgstr "förväntade 0 logiska replikeringsslottar men hittade %d." -#: check.c:1769 check.c:1820 +#: check.c:1856 check.c:1907 #, c-format msgid "could not determine parameter settings on new cluster" msgstr "kunde inte plocka fram inställningar i det nya klustret" -#: check.c:1774 +#: check.c:1861 #, c-format msgid "\"wal_level\" must be \"logical\" but is set to \"%s\"" msgstr "\"wal_level\" måste vara satt till \"logical\" men är satt som \"%s\"" -#: check.c:1780 +#: check.c:1867 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of logical replication slots (%d) on the old cluster" msgstr "\"max_replication_slots\" (%d) måste vara större än eller lika med antalet logiska replikeringsslottar (%d) i gamla klustret" -#: check.c:1812 +#: check.c:1899 #, c-format msgid "Checking for new cluster configuration for subscriptions" msgstr "Letar efter konfiguration för prenumerationer i nya klustret" -#: check.c:1824 +#: check.c:1911 #, c-format msgid "\"max_replication_slots\" (%d) must be greater than or equal to the number of subscriptions (%d) on the old cluster" -msgstr "\"max_replication_slots\" (%d) måste vara större än eller lika med antaler prenumerationer (%d) i gamla klustret" +msgstr "\"max_replication_slots\" (%d) måste vara större än eller lika med antalet prenumerationer (%d) i gamla klustret" -#: check.c:1846 +#: check.c:1933 #, c-format msgid "Checking for valid logical replication slots" msgstr "Letar efter giltiga logiska replikeringsslottar" -#: check.c:1898 +#: check.c:1985 #, c-format msgid "" "Your installation contains logical replication slots that cannot be upgraded.\n" @@ -660,12 +685,12 @@ msgstr "" "En lista med problemkolumner finns i filen:\n" " %s" -#: check.c:1922 +#: check.c:2009 #, c-format msgid "Checking for subscription state" msgstr "Kontrollerar läget för prenumerationer" -#: check.c:2023 +#: check.c:2110 #, c-format msgid "" "Your installation contains subscriptions without origin or having relations not in i (initialize) or r (ready) state.\n" @@ -725,8 +750,8 @@ msgstr "Källklustret saknar information om kluster-state:" msgid "The target cluster lacks cluster state information:" msgstr "Målklustret saknar information om kluster-state:" -#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:556 pg_upgrade.c:596 -#: pg_upgrade.c:945 relfilenumber.c:233 server.c:34 util.c:337 +#: controldata.c:213 dump.c:50 exec.c:118 pg_upgrade.c:558 pg_upgrade.c:598 +#: pg_upgrade.c:947 relfilenumber.c:233 server.c:34 util.c:337 #, c-format msgid "%s" msgstr "%s" @@ -1446,7 +1471,7 @@ msgstr " -V, --version visa versionsinformation, avsluta sedan\ #: option.c:306 #, c-format msgid " --clone clone instead of copying files to new cluster\n" -msgstr " -clone klona istället för att kopiera filer till nya klustret\n" +msgstr " --clone klona istället för att kopiera filer till nya klustret\n" #: option.c:307 #, c-format @@ -1697,77 +1722,77 @@ msgstr "" msgid "Setting locale and encoding for new cluster" msgstr "Sätter lokal och teckenkodning för nya klustret" -#: pg_upgrade.c:489 +#: pg_upgrade.c:491 #, c-format msgid "Analyzing all rows in the new cluster" msgstr "Analyserar alla rader i nya klustret" -#: pg_upgrade.c:502 +#: pg_upgrade.c:504 #, c-format msgid "Freezing all rows in the new cluster" msgstr "Fryser alla rader i nya klustret" -#: pg_upgrade.c:522 +#: pg_upgrade.c:524 #, c-format msgid "Restoring global objects in the new cluster" msgstr "Återställer globala objekt i nya klustret" -#: pg_upgrade.c:538 +#: pg_upgrade.c:540 #, c-format msgid "Restoring database schemas in the new cluster" msgstr "Återställer databasscheman i nya klustret" -#: pg_upgrade.c:662 +#: pg_upgrade.c:664 #, c-format msgid "Deleting files from new %s" msgstr "Raderar filer från ny %s" -#: pg_upgrade.c:666 +#: pg_upgrade.c:668 #, c-format msgid "could not delete directory \"%s\"" msgstr "kunde inte ta bort katalog \"%s\"" -#: pg_upgrade.c:685 +#: pg_upgrade.c:687 #, c-format msgid "Copying old %s to new server" msgstr "Kopierar gammal %s till ny server" -#: pg_upgrade.c:711 +#: pg_upgrade.c:713 #, c-format msgid "Setting oldest XID for new cluster" msgstr "Sätter äldsta XID för nya klustret" -#: pg_upgrade.c:719 +#: pg_upgrade.c:721 #, c-format msgid "Setting next transaction ID and epoch for new cluster" msgstr "Sätter nästa transaktions-ID och epoch för nytt kluster" -#: pg_upgrade.c:749 +#: pg_upgrade.c:751 #, c-format msgid "Setting next multixact ID and offset for new cluster" msgstr "Sätter nästa multixact-ID och offset för nytt kluster" -#: pg_upgrade.c:773 +#: pg_upgrade.c:775 #, c-format msgid "Setting oldest multixact ID in new cluster" msgstr "Sätter äldsta multixact-ID i nytt kluster" -#: pg_upgrade.c:793 +#: pg_upgrade.c:795 #, c-format msgid "Resetting WAL archives" msgstr "Resettar WAL-arkiv" -#: pg_upgrade.c:836 +#: pg_upgrade.c:838 #, c-format msgid "Setting frozenxid and minmxid counters in new cluster" msgstr "Sätter räknarna frozenxid och minmxid för nytt kluster" -#: pg_upgrade.c:838 +#: pg_upgrade.c:840 #, c-format msgid "Setting minmxid counter in new cluster" msgstr "Sätter räknarenm minmxid för nytt kluster" -#: pg_upgrade.c:929 +#: pg_upgrade.c:931 #, c-format msgid "Restoring logical replication slots in the new cluster" msgstr "Återställer logiska replikeringsslottar i nya klustret" @@ -1858,7 +1883,7 @@ msgstr "kunde inte öppna versionsfil \"%s\": %m" msgid "could not parse version file \"%s\"" msgstr "kunde inte tolka versionsfil \"%s\"" -#: server.c:310 +#: server.c:299 #, c-format msgid "" "\n" @@ -1867,7 +1892,7 @@ msgstr "" "\n" "%s" -#: server.c:314 +#: server.c:303 #, c-format msgid "" "could not connect to source postmaster started with the command:\n" @@ -1876,7 +1901,7 @@ msgstr "" "kunde inte ansluta till käll-postmaster som startats med kommandot:\n" "%s" -#: server.c:318 +#: server.c:307 #, c-format msgid "" "could not connect to target postmaster started with the command:\n" @@ -1885,22 +1910,22 @@ msgstr "" "kunde inte ansluta till mål-postmaster som startats med kommandot:\n" "%s" -#: server.c:332 +#: server.c:321 #, c-format msgid "pg_ctl failed to start the source server, or connection failed" msgstr "pg_ctl misslyckades att start källservern eller så misslyckades anslutningen" -#: server.c:334 +#: server.c:323 #, c-format msgid "pg_ctl failed to start the target server, or connection failed" msgstr "pg_ctl misslyckades att start målservern eller så misslyckades anslutningen" -#: server.c:379 +#: server.c:368 #, c-format msgid "out of memory" msgstr "slut på minne" -#: server.c:392 +#: server.c:381 #, c-format msgid "libpq environment variable %s has a non-local server value: %s" msgstr "libpq:s omgivningsvariabel %s har ett icke-lokalt servervärde: %s" diff --git a/src/bin/pg_verifybackup/po/ru.po b/src/bin/pg_verifybackup/po/ru.po index a5e7d099ba387..d634b785fea9f 100644 --- a/src/bin/pg_verifybackup/po/ru.po +++ b/src/bin/pg_verifybackup/po/ru.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_verifybackup (PostgreSQL) 13\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-08 07:44+0200\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-09-07 09:48+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -121,91 +121,91 @@ msgstr "нехватка памяти\n" msgid "cannot duplicate null pointer (internal error)\n" msgstr "попытка дублирования нулевого указателя (внутренняя ошибка)\n" -#: ../../common/jsonapi.c:2121 +#: ../../common/jsonapi.c:2124 msgid "Recursive descent parser cannot use incremental lexer." msgstr "" "Инкрементальный лексический анализатор не подходит для нисходящего " "рекурсивного разбора." -#: ../../common/jsonapi.c:2123 +#: ../../common/jsonapi.c:2126 msgid "Incremental parser requires incremental lexer." msgstr "" "Для инкрементального разбора требуется инкрементальный лексический " "анализатор." -#: ../../common/jsonapi.c:2125 +#: ../../common/jsonapi.c:2128 msgid "JSON nested too deep, maximum permitted depth is 6400." msgstr "" "Слишком большая вложенность JSON, максимальная допустимая глубина: 6400." -#: ../../common/jsonapi.c:2127 +#: ../../common/jsonapi.c:2130 #, c-format msgid "Escape sequence \"\\%.*s\" is invalid." msgstr "Неверная спецпоследовательность: \"\\%.*s\"." -#: ../../common/jsonapi.c:2131 +#: ../../common/jsonapi.c:2134 #, c-format msgid "Character with value 0x%02x must be escaped." msgstr "Символ с кодом 0x%02x необходимо экранировать." -#: ../../common/jsonapi.c:2135 +#: ../../common/jsonapi.c:2138 #, c-format msgid "Expected end of input, but found \"%.*s\"." msgstr "Ожидался конец текста, но обнаружено продолжение \"%.*s\"." -#: ../../common/jsonapi.c:2138 +#: ../../common/jsonapi.c:2141 #, c-format msgid "Expected array element or \"]\", but found \"%.*s\"." msgstr "Ожидался элемент массива или \"]\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2141 +#: ../../common/jsonapi.c:2144 #, c-format msgid "Expected \",\" or \"]\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"]\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2144 +#: ../../common/jsonapi.c:2147 #, c-format msgid "Expected \":\", but found \"%.*s\"." msgstr "Ожидалось \":\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2147 +#: ../../common/jsonapi.c:2150 #, c-format msgid "Expected JSON value, but found \"%.*s\"." msgstr "Ожидалось значение JSON, но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2150 +#: ../../common/jsonapi.c:2153 msgid "The input string ended unexpectedly." msgstr "Неожиданный конец входной строки." -#: ../../common/jsonapi.c:2152 +#: ../../common/jsonapi.c:2155 #, c-format msgid "Expected string or \"}\", but found \"%.*s\"." msgstr "Ожидалась строка или \"}\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2155 +#: ../../common/jsonapi.c:2158 #, c-format msgid "Expected \",\" or \"}\", but found \"%.*s\"." msgstr "Ожидалась \",\" или \"}\", но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2158 +#: ../../common/jsonapi.c:2161 #, c-format msgid "Expected string, but found \"%.*s\"." msgstr "Ожидалась строка, но обнаружено \"%.*s\"." -#: ../../common/jsonapi.c:2161 +#: ../../common/jsonapi.c:2164 #, c-format msgid "Token \"%.*s\" is invalid." msgstr "Ошибочный элемент \"%.*s\"." -#: ../../common/jsonapi.c:2164 +#: ../../common/jsonapi.c:2167 msgid "\\u0000 cannot be converted to text." msgstr "\\u0000 нельзя преобразовать в текст." -#: ../../common/jsonapi.c:2166 +#: ../../common/jsonapi.c:2169 msgid "\"\\u\" must be followed by four hexadecimal digits." msgstr "За \"\\u\" должны следовать четыре шестнадцатеричные цифры." -#: ../../common/jsonapi.c:2169 +#: ../../common/jsonapi.c:2172 msgid "" "Unicode escape values cannot be used for code point values above 007F when " "the encoding is not UTF8." @@ -213,18 +213,18 @@ msgstr "" "Спецкоды Unicode для значений выше 007F можно использовать только с " "кодировкой UTF8." -#: ../../common/jsonapi.c:2178 +#: ../../common/jsonapi.c:2181 #, c-format msgid "" "Unicode escape value could not be translated to the server's encoding %s." msgstr "Спецкод Unicode нельзя преобразовать в серверную кодировку %s." -#: ../../common/jsonapi.c:2185 +#: ../../common/jsonapi.c:2188 msgid "Unicode high surrogate must not follow a high surrogate." msgstr "" "Старшее слово суррогата Unicode не может следовать за другим старшим словом." -#: ../../common/jsonapi.c:2187 +#: ../../common/jsonapi.c:2190 msgid "Unicode low surrogate must follow a high surrogate." msgstr "Младшее слово суррогата Unicode должно следовать за старшим словом." diff --git a/src/bin/pg_waldump/po/ru.po b/src/bin/pg_waldump/po/ru.po index bafe5a3233c66..12640ebcad14c 100644 --- a/src/bin/pg_waldump/po/ru.po +++ b/src/bin/pg_waldump/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: pg_waldump (PostgreSQL) 10\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-09-02 09:29+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-09-07 08:59+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -501,52 +501,52 @@ msgstr "" msgid "contrecord is requested by %X/%X" msgstr "в позиции %X/%X запрошено продолжение записи" -#: xlogreader.c:669 xlogreader.c:1134 +#: xlogreader.c:669 xlogreader.c:1144 #, c-format msgid "invalid record length at %X/%X: expected at least %u, got %u" msgstr "" "неверная длина записи в позиции %X/%X: ожидалось минимум %u, получено %u" -#: xlogreader.c:758 +#: xlogreader.c:759 #, c-format msgid "there is no contrecord flag at %X/%X" msgstr "нет флага contrecord в позиции %X/%X" -#: xlogreader.c:771 +#: xlogreader.c:772 #, c-format msgid "invalid contrecord length %u (expected %lld) at %X/%X" msgstr "неверная длина contrecord: %u (ожидалась %lld) в позиции %X/%X" -#: xlogreader.c:1142 +#: xlogreader.c:1152 #, c-format msgid "invalid resource manager ID %u at %X/%X" msgstr "неверный ID менеджера ресурсов %u в позиции %X/%X" -#: xlogreader.c:1155 xlogreader.c:1171 +#: xlogreader.c:1165 xlogreader.c:1181 #, c-format msgid "record with incorrect prev-link %X/%X at %X/%X" msgstr "запись с неверной ссылкой назад %X/%X в позиции %X/%X" -#: xlogreader.c:1209 +#: xlogreader.c:1219 #, c-format msgid "incorrect resource manager data checksum in record at %X/%X" msgstr "" "некорректная контрольная сумма данных менеджера ресурсов в записи в позиции " "%X/%X" -#: xlogreader.c:1243 +#: xlogreader.c:1253 #, c-format msgid "invalid magic number %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверное магическое число %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1258 xlogreader.c:1300 +#: xlogreader.c:1268 xlogreader.c:1310 #, c-format msgid "invalid info bits %04X in WAL segment %s, LSN %X/%X, offset %u" msgstr "" "неверные информационные биты %04X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1274 +#: xlogreader.c:1284 #, c-format msgid "" "WAL file is from different database system: WAL file database system " @@ -555,7 +555,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: в нём указан идентификатор системы БД " "%llu, а идентификатор системы pg_control: %llu" -#: xlogreader.c:1282 +#: xlogreader.c:1292 #, c-format msgid "" "WAL file is from different database system: incorrect segment size in page " @@ -564,7 +564,7 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный размер сегмента в заголовке " "страницы" -#: xlogreader.c:1288 +#: xlogreader.c:1298 #, c-format msgid "" "WAL file is from different database system: incorrect XLOG_BLCKSZ in page " @@ -573,12 +573,12 @@ msgstr "" "файл WAL принадлежит другой СУБД: некорректный XLOG_BLCKSZ в заголовке " "страницы" -#: xlogreader.c:1320 +#: xlogreader.c:1330 #, c-format msgid "unexpected pageaddr %X/%X in WAL segment %s, LSN %X/%X, offset %u" msgstr "неожиданный pageaddr %X/%X в сегменте WAL %s, LSN %X/%X, смещение %u" -#: xlogreader.c:1346 +#: xlogreader.c:1356 #, c-format msgid "" "out-of-sequence timeline ID %u (after %u) in WAL segment %s, LSN %X/%X, " @@ -587,23 +587,23 @@ msgstr "" "нарушение последовательности ID линии времени %u (после %u) в сегменте WAL " "%s, LSN %X/%X, смещение %u" -#: xlogreader.c:1749 +#: xlogreader.c:1759 #, c-format msgid "out-of-order block_id %u at %X/%X" msgstr "идентификатор блока %u идёт не по порядку в позиции %X/%X" -#: xlogreader.c:1773 +#: xlogreader.c:1783 #, c-format msgid "BKPBLOCK_HAS_DATA set, but no data included at %X/%X" msgstr "BKPBLOCK_HAS_DATA установлен, но данных в позиции %X/%X нет" -#: xlogreader.c:1780 +#: xlogreader.c:1790 #, c-format msgid "BKPBLOCK_HAS_DATA not set, but data length is %u at %X/%X" msgstr "" "BKPBLOCK_HAS_DATA не установлен, но длина данных равна %u в позиции %X/%X" -#: xlogreader.c:1816 +#: xlogreader.c:1826 #, c-format msgid "" "BKPIMAGE_HAS_HOLE set, but hole offset %u length %u block image length %u at " @@ -612,21 +612,21 @@ msgstr "" "BKPIMAGE_HAS_HOLE установлен, но для пропуска заданы смещение %u и длина %u " "при длине образа блока %u в позиции %X/%X" -#: xlogreader.c:1832 +#: xlogreader.c:1842 #, c-format msgid "BKPIMAGE_HAS_HOLE not set, but hole offset %u length %u at %X/%X" msgstr "" "BKPIMAGE_HAS_HOLE не установлен, но для пропуска заданы смещение %u и длина " "%u в позиции %X/%X" -#: xlogreader.c:1846 +#: xlogreader.c:1856 #, c-format msgid "BKPIMAGE_COMPRESSED set, but block image length %u at %X/%X" msgstr "" "BKPIMAGE_COMPRESSED установлен, но длина образа блока равна %u в позиции %X/" "%X" -#: xlogreader.c:1861 +#: xlogreader.c:1871 #, c-format msgid "" "neither BKPIMAGE_HAS_HOLE nor BKPIMAGE_COMPRESSED set, but block image " @@ -635,41 +635,41 @@ msgstr "" "ни BKPIMAGE_HAS_HOLE, ни BKPIMAGE_COMPRESSED не установлены, но длина образа " "блока равна %u в позиции %X/%X" -#: xlogreader.c:1877 +#: xlogreader.c:1887 #, c-format msgid "BKPBLOCK_SAME_REL set but no previous rel at %X/%X" msgstr "" "BKPBLOCK_SAME_REL установлен, но предыдущее значение не задано в позиции %X/" "%X" -#: xlogreader.c:1889 +#: xlogreader.c:1899 #, c-format msgid "invalid block_id %u at %X/%X" msgstr "неверный идентификатор блока %u в позиции %X/%X" -#: xlogreader.c:1956 +#: xlogreader.c:1966 #, c-format msgid "record with invalid length at %X/%X" msgstr "запись с неверной длиной в позиции %X/%X" -#: xlogreader.c:1982 +#: xlogreader.c:1992 #, c-format msgid "could not locate backup block with ID %d in WAL record" msgstr "не удалось найти копию блока с ID %d в записи журнала WAL" -#: xlogreader.c:2066 +#: xlogreader.c:2076 #, c-format msgid "could not restore image at %X/%X with invalid block %d specified" msgstr "" "не удалось восстановить образ в позиции %X/%X с указанным неверным блоком %d" -#: xlogreader.c:2073 +#: xlogreader.c:2083 #, c-format msgid "could not restore image at %X/%X with invalid state, block %d" msgstr "" "не удалось восстановить образ в позиции %X/%X с неверным состоянием, блок %d" -#: xlogreader.c:2100 xlogreader.c:2117 +#: xlogreader.c:2110 xlogreader.c:2127 #, c-format msgid "" "could not restore image at %X/%X compressed with %s not supported by build, " @@ -678,7 +678,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый методом %s, который не " "поддерживается этой сборкой, блок %d" -#: xlogreader.c:2126 +#: xlogreader.c:2136 #, c-format msgid "" "could not restore image at %X/%X compressed with unknown method, block %d" @@ -686,7 +686,7 @@ msgstr "" "не удалось восстановить образ в позиции %X/%X, сжатый неизвестным методом, " "блок %d" -#: xlogreader.c:2134 +#: xlogreader.c:2144 #, c-format msgid "could not decompress image at %X/%X, block %d" msgstr "не удалось развернуть образ в позиции %X/%X, блок %d" diff --git a/src/bin/psql/po/ru.po b/src/bin/psql/po/ru.po index f57f4d51fb826..c92ba9873deb0 100644 --- a/src/bin/psql/po/ru.po +++ b/src/bin/psql/po/ru.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: psql (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2025-02-08 08:33+0200\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -6889,7 +6889,7 @@ msgstr "лишний аргумент \"%s\" проигнорирован" msgid "could not find own program executable" msgstr "не удалось найти свой исполняемый файл" -#: tab-complete.c:6216 +#: tab-complete.c:6230 #, c-format msgid "" "tab completion query failed: %s\n" diff --git a/src/interfaces/ecpg/ecpglib/po/ru.po b/src/interfaces/ecpg/ecpglib/po/ru.po index 91b23dbc9fc07..bfb49a07e31b9 100644 --- a/src/interfaces/ecpg/ecpglib/po/ru.po +++ b/src/interfaces/ecpg/ecpglib/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: ecpglib (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2024-09-02 09:29+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2019-09-09 13:30+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -17,11 +17,11 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: connect.c:221 +#: connect.c:226 msgid "empty message text" msgstr "пустое сообщение" -#: connect.c:386 connect.c:645 +#: connect.c:391 connect.c:650 msgid "" msgstr "<ПО_УМОЛЧАНИЮ>" diff --git a/src/interfaces/libpq/po/ru.po b/src/interfaces/libpq/po/ru.po index 65fb7a8ed2c97..0b59976f33824 100644 --- a/src/interfaces/libpq/po/ru.po +++ b/src/interfaces/libpq/po/ru.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: libpq (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2025-05-03 16:34+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -76,18 +76,18 @@ msgstr "не удалось сгенерировать разовый код" #: fe-auth-scram.c:614 fe-auth-scram.c:638 fe-auth-scram.c:652 #: fe-auth-scram.c:698 fe-auth-scram.c:734 fe-auth-scram.c:908 fe-auth.c:296 #: fe-auth.c:369 fe-auth.c:403 fe-auth.c:618 fe-auth.c:727 fe-auth.c:1200 -#: fe-auth.c:1363 fe-cancel.c:159 fe-connect.c:936 fe-connect.c:976 +#: fe-auth.c:1363 fe-cancel.c:160 fe-connect.c:936 fe-connect.c:976 #: fe-connect.c:1860 fe-connect.c:2022 fe-connect.c:3430 fe-connect.c:4761 #: fe-connect.c:5073 fe-connect.c:5328 fe-connect.c:5446 fe-connect.c:5693 #: fe-connect.c:5773 fe-connect.c:5871 fe-connect.c:6122 fe-connect.c:6149 #: fe-connect.c:6225 fe-connect.c:6248 fe-connect.c:6272 fe-connect.c:6307 #: fe-connect.c:6393 fe-connect.c:6401 fe-connect.c:6758 fe-connect.c:6908 -#: fe-exec.c:530 fe-exec.c:1315 fe-exec.c:3254 fe-exec.c:4291 fe-exec.c:4457 +#: fe-exec.c:530 fe-exec.c:1315 fe-exec.c:3254 fe-exec.c:4293 fe-exec.c:4459 #: fe-gssapi-common.c:109 fe-lobj.c:870 fe-protocol3.c:209 fe-protocol3.c:232 #: fe-protocol3.c:255 fe-protocol3.c:272 fe-protocol3.c:293 fe-protocol3.c:369 #: fe-protocol3.c:737 fe-protocol3.c:976 fe-protocol3.c:1787 -#: fe-protocol3.c:2187 fe-secure-common.c:110 fe-secure-gssapi.c:496 -#: fe-secure-openssl.c:427 fe-secure-openssl.c:1277 +#: fe-protocol3.c:2187 fe-secure-common.c:110 fe-secure-gssapi.c:506 +#: fe-secure-gssapi.c:696 fe-secure-openssl.c:427 fe-secure-openssl.c:1277 #, c-format msgid "out of memory" msgstr "нехватка памяти" @@ -360,17 +360,17 @@ msgstr "нераспознанный алгоритм шифрования па msgid "connection pointer is NULL" msgstr "нулевой указатель соединения" -#: fe-cancel.c:82 fe-misc.c:572 +#: fe-cancel.c:82 fe-misc.c:598 #, c-format msgid "connection not open" msgstr "соединение не открыто" -#: fe-cancel.c:193 +#: fe-cancel.c:194 #, c-format msgid "cancel request is already being sent on this connection" msgstr "через это соединение уже передаётся запрос отмены" -#: fe-cancel.c:263 +#: fe-cancel.c:264 #, c-format msgid "unexpected response from server" msgstr "неожиданный ответ сервера" @@ -847,21 +847,21 @@ msgstr "неверный символ, закодированный с %%: \"%s\ msgid "forbidden value %%00 in percent-encoded value: \"%s\"" msgstr "недопустимое значение %%00 для символа, закодированного с %%: \"%s\"" -#: fe-connect.c:7160 +#: fe-connect.c:7162 msgid "connection pointer is NULL\n" msgstr "нулевой указатель соединения\n" -#: fe-connect.c:7168 fe-exec.c:713 fe-exec.c:975 fe-exec.c:3459 +#: fe-connect.c:7170 fe-exec.c:713 fe-exec.c:975 fe-exec.c:3459 #: fe-protocol3.c:991 fe-protocol3.c:1024 msgid "out of memory\n" msgstr "нехватка памяти\n" -#: fe-connect.c:7459 +#: fe-connect.c:7461 #, c-format msgid "WARNING: password file \"%s\" is not a plain file\n" msgstr "ПРЕДУПРЕЖДЕНИЕ: файл паролей \"%s\" - не обычный файл\n" -#: fe-connect.c:7468 +#: fe-connect.c:7470 #, c-format msgid "" "WARNING: password file \"%s\" has group or world access; permissions should " @@ -870,12 +870,12 @@ msgstr "" "ПРЕДУПРЕЖДЕНИЕ: к файлу паролей \"%s\" имеют доступ все или группа; права " "должны быть u=rw (0600) или более ограниченные\n" -#: fe-connect.c:7575 +#: fe-connect.c:7577 #, c-format msgid "password retrieved from file \"%s\"" msgstr "пароль получен из файла \"%s\"" -#: fe-connect.c:7727 +#: fe-connect.c:7729 #, c-format msgid "invalid integer value \"%s\" for connection option \"%s\"" msgstr "неверное целочисленное значение \"%s\" для параметра соединения \"%s\"" @@ -1036,12 +1036,12 @@ msgstr "номер параметра %d вне диапазона 0..%d" msgid "could not interpret result from server: %s" msgstr "не удалось интерпретировать ответ сервера: %s" -#: fe-exec.c:4140 fe-exec.c:4253 +#: fe-exec.c:4141 fe-exec.c:4255 #, c-format msgid "incomplete multibyte character" msgstr "неполный многобайтный символ" -#: fe-exec.c:4142 fe-exec.c:4272 +#: fe-exec.c:4143 fe-exec.c:4274 #, c-format msgid "invalid multibyte character" msgstr "неверный многобайтный символ" @@ -1102,7 +1102,7 @@ msgstr "функция pqGetInt не поддерживает integer разме msgid "integer of size %lu not supported by pqPutInt" msgstr "функция pqPutInt не поддерживает integer размером %lu байт" -#: fe-misc.c:750 fe-secure-openssl.c:203 fe-secure-openssl.c:309 +#: fe-misc.c:776 fe-secure-openssl.c:203 fe-secure-openssl.c:309 #: fe-secure.c:237 fe-secure.c:404 #, c-format msgid "" @@ -1114,21 +1114,21 @@ msgstr "" "\tСкорее всего сервер прекратил работу из-за сбоя\n" "\tдо или в процессе выполнения запроса." -#: fe-misc.c:817 +#: fe-misc.c:843 msgid "connection not open\n" msgstr "соединение не открыто\n" -#: fe-misc.c:1005 +#: fe-misc.c:1031 #, c-format msgid "timeout expired" msgstr "тайм-аут" -#: fe-misc.c:1049 +#: fe-misc.c:1075 #, c-format msgid "invalid socket" msgstr "неверный сокет" -#: fe-misc.c:1071 +#: fe-misc.c:1097 #, c-format msgid "%s() failed: %s" msgstr "ошибка в %s(): %s" @@ -1342,46 +1342,42 @@ msgstr "серверный сертификат для \"%s\" не соотве msgid "could not get server's host name from server certificate" msgstr "не удалось получить имя сервера из серверного сертификата" -#: fe-secure-gssapi.c:194 +#: fe-secure-gssapi.c:201 msgid "GSSAPI wrap error" msgstr "ошибка обёртывания сообщения в GSSAPI" -#: fe-secure-gssapi.c:201 +#: fe-secure-gssapi.c:208 #, c-format msgid "outgoing GSSAPI message would not use confidentiality" msgstr "исходящее сообщение GSSAPI не будет защищено" -#: fe-secure-gssapi.c:208 +#: fe-secure-gssapi.c:215 fe-secure-gssapi.c:723 #, c-format msgid "client tried to send oversize GSSAPI packet (%zu > %zu)" msgstr "клиент попытался передать чрезмерно большой пакет GSSAPI (%zu > %zu)" -#: fe-secure-gssapi.c:347 fe-secure-gssapi.c:589 +#: fe-secure-gssapi.c:354 fe-secure-gssapi.c:599 #, c-format msgid "oversize GSSAPI packet sent by the server (%zu > %zu)" msgstr "сервер передал чрезмерно большой пакет GSSAPI (%zu > %zu)" -#: fe-secure-gssapi.c:386 +#: fe-secure-gssapi.c:393 msgid "GSSAPI unwrap error" msgstr "ошибка развёртывания сообщения в GSSAPI" -#: fe-secure-gssapi.c:395 +#: fe-secure-gssapi.c:402 #, c-format msgid "incoming GSSAPI message did not use confidentiality" msgstr "входящее сообщение GSSAPI не защищено" -#: fe-secure-gssapi.c:652 +#: fe-secure-gssapi.c:662 msgid "could not initiate GSSAPI security context" msgstr "не удалось инициализировать контекст безопасности GSSAPI" -#: fe-secure-gssapi.c:681 +#: fe-secure-gssapi.c:712 msgid "GSSAPI size check error" msgstr "ошибка проверки размера в GSSAPI" -#: fe-secure-gssapi.c:692 -msgid "GSSAPI context establishment error" -msgstr "ошибка установления контекста в GSSAPI" - #: fe-secure-openssl.c:207 fe-secure-openssl.c:313 fe-secure-openssl.c:1524 #, c-format msgid "SSL SYSCALL error: %s" @@ -1636,6 +1632,9 @@ msgstr "не удалось передать данные серверу: %s" msgid "unrecognized socket error: 0x%08X/%d" msgstr "нераспознанная ошибка сокета: 0x%08X/%d" +#~ msgid "GSSAPI context establishment error" +#~ msgstr "ошибка установления контекста в GSSAPI" + #, c-format #~ msgid "keepalives parameter must be an integer" #~ msgstr "параметр keepalives должен быть целым числом" diff --git a/src/pl/plpgsql/src/po/ru.po b/src/pl/plpgsql/src/po/ru.po index 189087b1f1cb7..635aa462297f0 100644 --- a/src/pl/plpgsql/src/po/ru.po +++ b/src/pl/plpgsql/src/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: plpgsql (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-05-03 16:06+0300\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2024-09-04 20:00+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -79,7 +79,7 @@ msgid "It could refer to either a PL/pgSQL variable or a table column." msgstr "Подразумевается ссылка на переменную PL/pgSQL или столбец таблицы." #: pl_comp.c:1314 pl_exec.c:5260 pl_exec.c:5433 pl_exec.c:5520 pl_exec.c:5611 -#: pl_exec.c:6636 +#: pl_exec.c:6640 #, c-format msgid "record \"%s\" has no field \"%s\"" msgstr "в записи \"%s\" нет поля \"%s\"" @@ -114,7 +114,7 @@ msgstr "переменная \"%s\" имеет псевдотип %s" msgid "type \"%s\" is only a shell" msgstr "тип \"%s\" является пустышкой" -#: pl_comp.c:2157 pl_exec.c:6937 +#: pl_comp.c:2157 pl_exec.c:6941 #, c-format msgid "type %s is not composite" msgstr "тип %s не является составным" @@ -369,7 +369,7 @@ msgstr "" msgid "structure of query does not match function result type" msgstr "структура запроса не соответствует типу результата функции" -#: pl_exec.c:3632 pl_exec.c:4469 pl_exec.c:8761 +#: pl_exec.c:3632 pl_exec.c:4469 pl_exec.c:8765 #, c-format msgid "query string argument of EXECUTE is null" msgstr "в качестве текста запроса в EXECUTE передан NULL" @@ -501,7 +501,7 @@ msgstr "присвоить значение системному столбцу msgid "query did not return data" msgstr "запрос не вернул данные" -#: pl_exec.c:5719 pl_exec.c:5731 pl_exec.c:5756 pl_exec.c:5832 pl_exec.c:5837 +#: pl_exec.c:5719 pl_exec.c:5731 pl_exec.c:5756 pl_exec.c:5836 pl_exec.c:5841 #, c-format msgid "query: %s" msgstr "запрос: %s" @@ -514,17 +514,17 @@ msgstr[0] "запрос вернул %d столбец" msgstr[1] "запрос вернул %d столбца" msgstr[2] "запрос вернул %d столбцов" -#: pl_exec.c:5831 +#: pl_exec.c:5835 #, c-format msgid "query is SELECT INTO, but it should be plain SELECT" msgstr "запрос - не просто SELECT, а SELECT INTO" -#: pl_exec.c:5836 +#: pl_exec.c:5840 #, c-format msgid "query is not a SELECT" msgstr "запрос - не SELECT" -#: pl_exec.c:6650 pl_exec.c:6690 pl_exec.c:6730 +#: pl_exec.c:6654 pl_exec.c:6694 pl_exec.c:6734 #, c-format msgid "" "type of parameter %d (%s) does not match that when preparing the plan (%s)" @@ -532,35 +532,35 @@ msgstr "" "тип параметра %d (%s) не соответствует тому, с которым подготавливался план " "(%s)" -#: pl_exec.c:7141 pl_exec.c:7175 pl_exec.c:7249 pl_exec.c:7275 +#: pl_exec.c:7145 pl_exec.c:7179 pl_exec.c:7253 pl_exec.c:7279 #, c-format msgid "number of source and target fields in assignment does not match" msgstr "в левой и правой части присваивания разное количество полей" #. translator: %s represents a name of an extra check -#: pl_exec.c:7143 pl_exec.c:7177 pl_exec.c:7251 pl_exec.c:7277 +#: pl_exec.c:7147 pl_exec.c:7181 pl_exec.c:7255 pl_exec.c:7281 #, c-format msgid "%s check of %s is active." msgstr "Включена проверка %s (с %s)." -#: pl_exec.c:7147 pl_exec.c:7181 pl_exec.c:7255 pl_exec.c:7281 +#: pl_exec.c:7151 pl_exec.c:7185 pl_exec.c:7259 pl_exec.c:7285 #, c-format msgid "Make sure the query returns the exact list of columns." msgstr "" "Измените запрос, чтобы он возвращал в точности требуемый список столбцов." -#: pl_exec.c:7668 +#: pl_exec.c:7672 #, c-format msgid "record \"%s\" is not assigned yet" msgstr "записи \"%s\" не присвоено значение" -#: pl_exec.c:7669 +#: pl_exec.c:7673 #, c-format msgid "The tuple structure of a not-yet-assigned record is indeterminate." msgstr "" "Для записи, которой не присвоено значение, структура кортежа не определена." -#: pl_exec.c:8359 pl_gram.y:3498 +#: pl_exec.c:8363 pl_gram.y:3498 #, c-format msgid "variable \"%s\" is declared CONSTANT" msgstr "переменная \"%s\" объявлена как CONSTANT" diff --git a/src/pl/plpython/po/ru.po b/src/pl/plpython/po/ru.po index 0b57951c61093..55c717b2d7011 100644 --- a/src/pl/plpython/po/ru.po +++ b/src/pl/plpython/po/ru.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: plpython (PostgreSQL current)\n" "Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" -"POT-Creation-Date: 2025-02-08 07:44+0200\n" +"POT-Creation-Date: 2025-08-02 11:37+0300\n" "PO-Revision-Date: 2019-08-29 15:42+0300\n" "Last-Translator: Alexander Lakhin \n" "Language-Team: Russian \n" @@ -66,7 +66,7 @@ msgstr "" msgid "closing a cursor in an aborted subtransaction" msgstr "закрытие курсора в прерванной подтранзакции" -#: plpy_elog.c:122 plpy_elog.c:123 plpy_plpymodule.c:530 +#: plpy_elog.c:127 plpy_elog.c:128 plpy_plpymodule.c:530 #, c-format msgid "%s" msgstr "%s" From 86b3e2ba5001f3a8c23ad50213b87797a85afbd5 Mon Sep 17 00:00:00 2001 From: Noah Misch Date: Mon, 11 Aug 2025 06:18:59 -0700 Subject: [PATCH 774/796] Convert newlines to spaces in names written in v11+ pg_dump comments. Maliciously-crafted object names could achieve SQL injection during restore. CVE-2012-0868 fixed this class of problem at the time, but later work reintroduced three cases. Commit bc8cd50fefd369b217f80078585c486505aafb62 (back-patched to v11+ in 2023-05 releases) introduced the pg_dump case. Commit 6cbdbd9e8d8f2986fde44f2431ed8d0c8fce7f5d (v12+) introduced the two pg_dumpall cases. Move sanitize_line(), unchanged, to dumputils.c so pg_dumpall has access to it in all supported versions. Back-patch to v13 (all supported versions). Reviewed-by: Robert Haas Reviewed-by: Nathan Bossart Backpatch-through: 13 Security: CVE-2025-8715 --- src/bin/pg_dump/dumputils.c | 37 ++++++++++++++++++++ src/bin/pg_dump/dumputils.h | 1 + src/bin/pg_dump/pg_backup_archiver.c | 37 -------------------- src/bin/pg_dump/pg_dump.c | 5 ++- src/bin/pg_dump/pg_dumpall.c | 13 +++++-- src/bin/pg_dump/t/002_pg_dump.pl | 21 +++++++++++ src/bin/pg_dump/t/003_pg_dump_with_server.pl | 17 ++++++++- 7 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 5649859aa1e2d..9e5748311e047 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -29,6 +29,43 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname); +/* + * Sanitize a string to be included in an SQL comment or TOC listing, by + * replacing any newlines with spaces. This ensures each logical output line + * is in fact one physical output line, to prevent corruption of the dump + * (which could, in the worst case, present an SQL injection vulnerability + * if someone were to incautiously load a dump containing objects with + * maliciously crafted names). + * + * The result is a freshly malloc'd string. If the input string is NULL, + * return a malloc'ed empty string, unless want_hyphen, in which case return a + * malloc'ed hyphen. + * + * Note that we currently don't bother to quote names, meaning that the name + * fields aren't automatically parseable. "pg_restore -L" doesn't care because + * it only examines the dumpId field, but someday we might want to try harder. + */ +char * +sanitize_line(const char *str, bool want_hyphen) +{ + char *result; + char *s; + + if (!str) + return pg_strdup(want_hyphen ? "-" : ""); + + result = pg_strdup(str); + + for (s = result; *s != '\0'; s++) + { + if (*s == '\n' || *s == '\r') + *s = ' '; + } + + return result; +} + + /* * Build GRANT/REVOKE command(s) for an object. * diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index 98c71c6bf9473..d1248d9061ba1 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -36,6 +36,7 @@ #endif +extern char *sanitize_line(const char *str, bool want_hyphen); extern bool buildACLCommands(const char *name, const char *subname, const char *nspname, const char *type, const char *acls, const char *baseacls, const char *owner, const char *prefix, int remoteVersion, diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index f1ffed038b046..9d0b5d0bd7f87 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -54,7 +54,6 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt, DataDirSyncMethod sync_method); static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te); static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData); -static char *sanitize_line(const char *str, bool want_hyphen); static void _doSetFixedOutputState(ArchiveHandle *AH); static void _doSetSessionAuth(ArchiveHandle *AH, const char *user); static void _reconnectToDB(ArchiveHandle *AH, const char *dbname); @@ -3915,42 +3914,6 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) } } -/* - * Sanitize a string to be included in an SQL comment or TOC listing, by - * replacing any newlines with spaces. This ensures each logical output line - * is in fact one physical output line, to prevent corruption of the dump - * (which could, in the worst case, present an SQL injection vulnerability - * if someone were to incautiously load a dump containing objects with - * maliciously crafted names). - * - * The result is a freshly malloc'd string. If the input string is NULL, - * return a malloc'ed empty string, unless want_hyphen, in which case return a - * malloc'ed hyphen. - * - * Note that we currently don't bother to quote names, meaning that the name - * fields aren't automatically parseable. "pg_restore -L" doesn't care because - * it only examines the dumpId field, but someday we might want to try harder. - */ -static char * -sanitize_line(const char *str, bool want_hyphen) -{ - char *result; - char *s; - - if (!str) - return pg_strdup(want_hyphen ? "-" : ""); - - result = pg_strdup(str); - - for (s = result; *s != '\0'; s++) - { - if (*s == '\n' || *s == '\r') - *s = ' '; - } - - return result; -} - /* * Write the file header for a custom-format archive */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 0f26b018d4afc..201b17697a410 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2657,11 +2657,14 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo) forcePartitionRootLoad(tbinfo))) { TableInfo *parentTbinfo; + char *sanitized; parentTbinfo = getRootTableInfo(tbinfo); copyFrom = fmtQualifiedDumpable(parentTbinfo); + sanitized = sanitize_line(copyFrom, true); printfPQExpBuffer(copyBuf, "-- load via partition root %s", - copyFrom); + sanitized); + free(sanitized); tdDefn = pg_strdup(copyBuf->data); } else diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index d5282ecb17a1a..1f98c89c34a70 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -1462,7 +1462,13 @@ dumpUserConfig(PGconn *conn, const char *username) res = executeQuery(conn, buf->data); if (PQntuples(res) > 0) - fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", username); + { + char *sanitized; + + sanitized = sanitize_line(username, true); + fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", sanitized); + free(sanitized); + } for (int i = 0; i < PQntuples(res); i++) { @@ -1564,6 +1570,7 @@ dumpDatabases(PGconn *conn) for (i = 0; i < PQntuples(res); i++) { char *dbname = PQgetvalue(res, i, 0); + char *sanitized; const char *create_opts; int ret; @@ -1580,7 +1587,9 @@ dumpDatabases(PGconn *conn) pg_log_info("dumping database \"%s\"", dbname); - fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname); + sanitized = sanitize_line(dbname, true); + fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized); + free(sanitized); /* * We assume that "template1" and "postgres" already exist in the diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 58306d307ecee..5e256b52d24e6 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -1893,6 +1893,27 @@ }, }, + 'newline of role or table name in comment' => { + create_sql => qq{CREATE ROLE regress_newline; + ALTER ROLE regress_newline SET enable_seqscan = off; + ALTER ROLE regress_newline + RENAME TO "regress_newline\nattack"; + + -- meet getPartitioningInfo() "unsafe" condition + CREATE TYPE pp_colors AS + ENUM ('green', 'blue', 'black'); + CREATE TABLE pp_enumpart (a pp_colors) + PARTITION BY HASH (a); + CREATE TABLE pp_enumpart1 PARTITION OF pp_enumpart + FOR VALUES WITH (MODULUS 2, REMAINDER 0); + CREATE TABLE pp_enumpart2 PARTITION OF pp_enumpart + FOR VALUES WITH (MODULUS 2, REMAINDER 1); + ALTER TABLE pp_enumpart + RENAME TO "pp_enumpart\nattack";}, + regexp => qr/\n--[^\n]*\nattack/s, + like => {}, + }, + 'CREATE TABLESPACE regress_dump_tablespace' => { create_order => 2, create_sql => q( diff --git a/src/bin/pg_dump/t/003_pg_dump_with_server.pl b/src/bin/pg_dump/t/003_pg_dump_with_server.pl index b5a144555074d..6948dcd298421 100644 --- a/src/bin/pg_dump/t/003_pg_dump_with_server.pl +++ b/src/bin/pg_dump/t/003_pg_dump_with_server.pl @@ -16,6 +16,22 @@ $node->init; $node->start; +######################################### +# pg_dumpall: newline in database name + +$node->safe_psql('postgres', qq{CREATE DATABASE "regress_\nattack"}); + +my (@cmd, $stdout, $stderr); +@cmd = ("pg_dumpall", '--port' => $port, '--exclude-database=postgres'); +print("# Running: " . join(" ", @cmd) . "\n"); +my $result = IPC::Run::run \@cmd, '>' => \$stdout, '2>' => \$stderr; +ok(!$result, "newline in dbname: exit code not 0"); +like( + $stderr, + qr/shell command argument contains a newline/, + "newline in dbname: stderr matches"); +unlike($stdout, qr/^attack/m, "newline in dbname: no comment escape"); + ######################################### # Verify that dumping foreign data includes only foreign tables of # matching servers @@ -26,7 +42,6 @@ $node->safe_psql('postgres', "CREATE SERVER s2 FOREIGN DATA WRAPPER dummy"); $node->safe_psql('postgres', "CREATE FOREIGN TABLE t0 (a int) SERVER s0"); $node->safe_psql('postgres', "CREATE FOREIGN TABLE t1 (a int) SERVER s1"); -my ($cmd, $stdout, $stderr, $result); command_fails_like( [ "pg_dump", '-p', $port, '--include-foreign-data=s0', 'postgres' ], From 6042e42ad2ace4b04f7d362b03005e8274378910 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 11 Aug 2025 09:00:00 -0500 Subject: [PATCH 775/796] Restrict psql meta-commands in plain-text dumps. A malicious server could inject psql meta-commands into plain-text dump output (i.e., scripts created with pg_dump --format=plain, pg_dumpall, or pg_restore --file) that are run at restore time on the machine running psql. To fix, introduce a new "restricted" mode in psql that blocks all meta-commands (except for \unrestrict to exit the mode), and teach pg_dump, pg_dumpall, and pg_restore to use this mode in plain-text dumps. While at it, encourage users to only restore dumps generated from trusted servers or to inspect it beforehand, since restoring causes the destination to execute arbitrary code of the source superusers' choice. However, the client running the dump and restore needn't trust the source or destination superusers. Reported-by: Martin Rakhmanov Reported-by: Matthieu Denais Reported-by: RyotaK Suggested-by: Tom Lane Reviewed-by: Noah Misch Reviewed-by: Michael Paquier Reviewed-by: Peter Eisentraut Security: CVE-2025-8714 Backpatch-through: 13 --- doc/src/sgml/ref/pg_dump.sgml | 35 +++++++ doc/src/sgml/ref/pg_dumpall.sgml | 30 ++++++ doc/src/sgml/ref/pg_restore.sgml | 34 +++++++ doc/src/sgml/ref/pgupgrade.sgml | 8 ++ doc/src/sgml/ref/psql-ref.sgml | 36 +++++++ .../pg_combinebackup/t/002_compare_backups.pl | 2 + src/bin/pg_dump/dumputils.c | 38 ++++++++ src/bin/pg_dump/dumputils.h | 3 + src/bin/pg_dump/pg_backup.h | 4 + src/bin/pg_dump/pg_backup_archiver.c | 32 ++++++- src/bin/pg_dump/pg_dump.c | 21 +++++ src/bin/pg_dump/pg_dumpall.c | 36 +++++++ src/bin/pg_dump/pg_restore.c | 22 +++++ src/bin/pg_dump/t/002_pg_dump.pl | 26 +++-- src/bin/pg_upgrade/t/002_pg_upgrade.pl | 2 + src/bin/psql/command.c | 94 ++++++++++++++++++- src/bin/psql/help.c | 4 + src/bin/psql/t/001_basic.pl | 7 ++ src/bin/psql/tab-complete.c | 4 +- src/test/recovery/t/027_stream_regress.pl | 4 + src/test/regress/expected/psql.out | 2 + src/test/regress/sql/psql.sql | 2 + 22 files changed, 433 insertions(+), 13 deletions(-) diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index cfc74ca6d694a..5ba167b33e722 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -92,6 +92,18 @@ PostgreSQL documentation light of the limitations listed below. + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Non-plain-text dumps can be inspected + by using pg_restore's + option. Note that the client running the dump and restore need not trust + the source or destination superusers. + + + @@ -1207,6 +1219,29 @@ PostgreSQL documentation + + + + + Use the provided string as the psql + \restrict key in the dump output. This can only be + specified for plain-text dumps, i.e., when is + set to plain or the option + is omitted. If no restrict key is specified, + pg_dump will generate a random one as + needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 9624144c1f4a2..84e38ef211574 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -66,6 +66,16 @@ PostgreSQL documentation linkend="libpq-pgpass"/> for more information. + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Note that the client running the dump + and restore need not trust the source or destination superusers. + + + @@ -555,6 +565,26 @@ exclude database PATTERN + + + + + Use the provided string as the psql + \restrict key in the dump output. If no restrict + key is specified, pg_dumpall will generate a + random one as needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 0f23067d78489..8e221d89d283b 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -68,6 +68,18 @@ PostgreSQL documentation pg_restore will not be able to load the data using COPY statements. + + + + Restoring a dump causes the destination to execute arbitrary code of the + source superusers' choice. Partial dumps and partial restores do not limit + that. If the source superusers are not trusted, the dumped SQL statements + must be inspected before restoring. Non-plain-text dumps can be inspected + by using pg_restore's + option. Note that the client running the dump and restore need not trust + the source or destination superusers. + + @@ -755,6 +767,28 @@ PostgreSQL documentation + + + + + Use the provided string as the psql + \restrict key in the dump output. This can only be + specified for SQL script output, i.e., when the + option is used. If no restrict key is specified, + pg_restore will generate a random one as + needed. Keys may contain only alphanumeric characters. + + + This option is primarily intended for testing purposes and other + scenarios that require repeatable output (e.g., comparing dump files). + It is not recommended for general use, as a malicious server with + advance knowledge of the key may be able to inject arbitrary code that + will be executed on the machine that runs + psql with the dump output. + + + + diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml index 9877f2f01c691..cdc37e91abe11 100644 --- a/doc/src/sgml/ref/pgupgrade.sgml +++ b/doc/src/sgml/ref/pgupgrade.sgml @@ -70,6 +70,14 @@ PostgreSQL documentation pg_upgrade supports upgrades from 9.2.X and later to the current major release of PostgreSQL, including snapshot and beta releases. + + + + Upgrading a cluster causes the destination to execute arbitrary code of the + source superusers' choice. Ensure that the source superusers are trusted + before upgrading. + + diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 830306ea1e202..475b84ef54a66 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -3340,6 +3340,24 @@ lo_import 152801 + + \restrict restrict_key + + + Enter "restricted" mode with the provided key. In this mode, the only + allowed meta-command is \unrestrict, to exit + restricted mode. The key may contain only alphanumeric characters. + + + This command is primarily intended for use in plain-text dumps + generated by pg_dump, + pg_dumpall, and + pg_restore, but it may be useful elsewhere. + + + + + \s [ filename ] @@ -3514,6 +3532,24 @@ testdb=> \setenv LESS -imx4F + + \unrestrict restrict_key + + + Exit "restricted" mode (i.e., where all other meta-commands are + blocked), provided the specified key matches the one given to + \restrict when restricted mode was entered. + + + This command is primarily intended for use in plain-text dumps + generated by pg_dump, + pg_dumpall, and + pg_restore, but it may be useful elsewhere. + + + + + \unset name diff --git a/src/bin/pg_combinebackup/t/002_compare_backups.pl b/src/bin/pg_combinebackup/t/002_compare_backups.pl index cfdd25471cb5f..cfb5ce83a9221 100644 --- a/src/bin/pg_combinebackup/t/002_compare_backups.pl +++ b/src/bin/pg_combinebackup/t/002_compare_backups.pl @@ -171,6 +171,7 @@ [ 'pg_dumpall', '-f', $dump1, '--no-sync', + '--restrict-key=test', '--no-unlogged-table-data', '-d', $pitr1->connstr('postgres'), ], @@ -179,6 +180,7 @@ [ 'pg_dumpall', '-f', $dump2, '--no-sync', + '--restrict-key=test', '--no-unlogged-table-data', '-d', $pitr2->connstr('postgres'), ], diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 9e5748311e047..47522d05429bd 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -19,6 +19,7 @@ #include "dumputils.h" #include "fe_utils/string_utils.h" +static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static bool parseAclItem(const char *item, const char *type, const char *name, const char *subname, int remoteVersion, @@ -920,3 +921,40 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem, pg_free(mine); } + +/* + * Generates a valid restrict key (i.e., an alphanumeric string) for use with + * psql's \restrict and \unrestrict meta-commands. For safety, the value is + * chosen at random. + */ +char * +generate_restrict_key(void) +{ + uint8 buf[64]; + char *ret = palloc(sizeof(buf)); + + if (!pg_strong_random(buf, sizeof(buf))) + return NULL; + + for (int i = 0; i < sizeof(buf) - 1; i++) + { + uint8 idx = buf[i] % strlen(restrict_chars); + + ret[i] = restrict_chars[idx]; + } + ret[sizeof(buf) - 1] = '\0'; + + return ret; +} + +/* + * Checks that a given restrict key (intended for use with psql's \restrict and + * \unrestrict meta-commands) contains only alphanumeric characters. + */ +bool +valid_restrict_key(const char *restrict_key) +{ + return restrict_key != NULL && + restrict_key[0] != '\0' && + strspn(restrict_key, restrict_chars) == strlen(restrict_key); +} diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index d1248d9061ba1..f685c616c6461 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -64,4 +64,7 @@ extern void makeAlterConfigCommand(PGconn *conn, const char *configitem, const char *type2, const char *name2, PQExpBuffer buf); +extern char *generate_restrict_key(void); +extern bool valid_restrict_key(const char *restrict_key); + #endif /* DUMPUTILS_H */ diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index fbf5f1c515e5e..609635ccbcb93 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -157,6 +157,8 @@ typedef struct _restoreOptions int enable_row_security; int sequence_data; /* dump sequence data even in schema-only mode */ int binary_upgrade; + + char *restrict_key; } RestoreOptions; typedef struct _dumpOptions @@ -203,6 +205,8 @@ typedef struct _dumpOptions int sequence_data; /* dump sequence data even in schema-only mode */ int do_nothing; + + char *restrict_key; } DumpOptions; /* diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 9d0b5d0bd7f87..4717e6f6f8325 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -187,6 +187,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->include_everything = ropt->include_everything; dopt->enable_row_security = ropt->enable_row_security; dopt->sequence_data = ropt->sequence_data; + dopt->restrict_key = ropt->restrict_key ? pg_strdup(ropt->restrict_key) : NULL; return dopt; } @@ -451,6 +452,17 @@ RestoreArchive(Archive *AHX) ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n"); + /* + * If generating plain-text output, enter restricted mode to block any + * unexpected psql meta-commands. A malicious source might try to inject + * a variety of things via bogus responses to queries. While we cannot + * prevent such sources from affecting the destination at restore time, we + * can block psql meta-commands so that the client machine that runs psql + * with the dump output remains unaffected. + */ + if (ropt->restrict_key) + ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key); + if (AH->archiveRemoteVersion) ahprintf(AH, "-- Dumped from database version %s\n", AH->archiveRemoteVersion); @@ -791,6 +803,14 @@ RestoreArchive(Archive *AHX) ahprintf(AH, "--\n-- PostgreSQL database dump complete\n--\n\n"); + /* + * If generating plain-text output, exit restricted mode at the very end + * of the script. This is not pro forma; in particular, pg_dumpall + * requires this when transitioning from one database to another. + */ + if (ropt->restrict_key) + ahprintf(AH, "\\unrestrict %s\n\n", ropt->restrict_key); + /* * Clean up & we're done. */ @@ -3349,11 +3369,21 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) else { PQExpBufferData connectbuf; + RestoreOptions *ropt = AH->public.ropt; + + /* + * We must temporarily exit restricted mode for \connect, etc. + * Anything added between this line and the following \restrict must + * be careful to avoid any possible meta-command injection vectors. + */ + ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key); initPQExpBuffer(&connectbuf); appendPsqlMetaConnect(&connectbuf, dbname); - ahprintf(AH, "%s\n", connectbuf.data); + ahprintf(AH, "%s", connectbuf.data); termPQExpBuffer(&connectbuf); + + ahprintf(AH, "\\restrict %s\n\n", ropt->restrict_key); } /* diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 201b17697a410..13139c9f0781f 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -450,6 +450,7 @@ main(int argc, char **argv) {"sync-method", required_argument, NULL, 15}, {"filter", required_argument, NULL, 16}, {"exclude-extension", required_argument, NULL, 17}, + {"restrict-key", required_argument, NULL, 25}, {NULL, 0, NULL, 0} }; @@ -690,6 +691,10 @@ main(int argc, char **argv) optarg); break; + case 25: + dopt.restrict_key = pg_strdup(optarg); + break; + default: /* getopt_long already emitted a complaint */ pg_log_error_hint("Try \"%s --help\" for more information.", progname); @@ -752,8 +757,22 @@ main(int argc, char **argv) /* archiveFormat specific setup */ if (archiveFormat == archNull) + { plainText = 1; + /* + * If you don't provide a restrict key, one will be appointed for you. + */ + if (!dopt.restrict_key) + dopt.restrict_key = generate_restrict_key(); + if (!dopt.restrict_key) + pg_fatal("could not generate restrict key"); + if (!valid_restrict_key(dopt.restrict_key)) + pg_fatal("invalid restrict key"); + } + else if (dopt.restrict_key) + pg_fatal("option --restrict-key can only be used with --format=plain"); + /* * Custom and directory formats are compressed by default with gzip when * available, not the others. If gzip is not available, no compression is @@ -1053,6 +1072,7 @@ main(int argc, char **argv) ropt->enable_row_security = dopt.enable_row_security; ropt->sequence_data = dopt.sequence_data; ropt->binary_upgrade = dopt.binary_upgrade; + ropt->restrict_key = dopt.restrict_key ? pg_strdup(dopt.restrict_key) : NULL; ropt->compression_spec = compression_spec; @@ -1160,6 +1180,7 @@ help(const char *progname) printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n")); + printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n")); printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n")); printf(_(" --section=SECTION dump named section (pre-data, data, or post-data)\n")); printf(_(" --serializable-deferrable wait until the dump can run without anomalies\n")); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 1f98c89c34a70..be18bf0146b59 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -121,6 +121,8 @@ static char *filename = NULL; static SimpleStringList database_exclude_patterns = {NULL, NULL}; static SimpleStringList database_exclude_names = {NULL, NULL}; +static char *restrict_key; + #define exit_nicely(code) exit(code) int @@ -178,6 +180,7 @@ main(int argc, char *argv[]) {"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1}, {"rows-per-insert", required_argument, NULL, 7}, {"filter", required_argument, NULL, 8}, + {"restrict-key", required_argument, NULL, 9}, {NULL, 0, NULL, 0} }; @@ -365,6 +368,12 @@ main(int argc, char *argv[]) read_dumpall_filters(optarg, &database_exclude_patterns); break; + case 9: + restrict_key = pg_strdup(optarg); + appendPQExpBufferStr(pgdumpopts, " --restrict-key "); + appendShellString(pgdumpopts, optarg); + break; + default: /* getopt_long already emitted a complaint */ pg_log_error_hint("Try \"%s --help\" for more information.", progname); @@ -460,6 +469,16 @@ main(int argc, char *argv[]) if (on_conflict_do_nothing) appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing"); + /* + * If you don't provide a restrict key, one will be appointed for you. + */ + if (!restrict_key) + restrict_key = generate_restrict_key(); + if (!restrict_key) + pg_fatal("could not generate restrict key"); + if (!valid_restrict_key(restrict_key)) + pg_fatal("invalid restrict key"); + /* * If there was a database specified on the command line, use that, * otherwise try to connect to database "postgres", and failing that @@ -547,6 +566,16 @@ main(int argc, char *argv[]) if (verbose) dumpTimestamp("Started on"); + /* + * Enter restricted mode to block any unexpected psql meta-commands. A + * malicious source might try to inject a variety of things via bogus + * responses to queries. While we cannot prevent such sources from + * affecting the destination at restore time, we can block psql + * meta-commands so that the client machine that runs psql with the dump + * output remains unaffected. + */ + fprintf(OPF, "\\restrict %s\n\n", restrict_key); + /* * We used to emit \connect postgres here, but that served no purpose * other than to break things for installations without a postgres @@ -607,6 +636,12 @@ main(int argc, char *argv[]) dumpTablespaces(conn); } + /* + * Exit restricted mode just before dumping the databases. pg_dump will + * handle entering restricted mode again as appropriate. + */ + fprintf(OPF, "\\unrestrict %s\n\n", restrict_key); + if (!globals_only && !roles_only && !tablespaces_only) dumpDatabases(conn); @@ -675,6 +710,7 @@ help(void) printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n")); + printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n")); printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n")); printf(_(" --use-set-session-authorization\n" " use SET SESSION AUTHORIZATION commands instead of\n" diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index df119591ccaa0..fc6fa923231f2 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -127,6 +127,7 @@ main(int argc, char **argv) {"no-security-labels", no_argument, &no_security_labels, 1}, {"no-subscriptions", no_argument, &no_subscriptions, 1}, {"filter", required_argument, NULL, 4}, + {"restrict-key", required_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -302,6 +303,10 @@ main(int argc, char **argv) opts->exit_on_error = true; break; + case 6: + opts->restrict_key = pg_strdup(optarg); + break; + default: /* getopt_long already emitted a complaint */ pg_log_error_hint("Try \"%s --help\" for more information.", progname); @@ -337,8 +342,24 @@ main(int argc, char **argv) pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit_nicely(1); } + + if (opts->restrict_key) + pg_fatal("options -d/--dbname and --restrict-key cannot be used together"); + opts->useDB = 1; } + else + { + /* + * If you don't provide a restrict key, one will be appointed for you. + */ + if (!opts->restrict_key) + opts->restrict_key = generate_restrict_key(); + if (!opts->restrict_key) + pg_fatal("could not generate restrict key"); + if (!valid_restrict_key(opts->restrict_key)) + pg_fatal("invalid restrict key"); + } if (opts->dataOnly && opts->schemaOnly) pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); @@ -493,6 +514,7 @@ usage(const char *progname) printf(_(" --no-subscriptions do not restore subscriptions\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); + printf(_(" --restrict-key=RESTRICT_KEY use provided string as psql \\restrict key\n")); printf(_(" --section=SECTION restore named section (pre-data, data, or post-data)\n")); printf(_(" --strict-names require table and/or schema include patterns to\n" " match at least one entity each\n")); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 5e256b52d24e6..8d5b2e1dc45e0 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -718,6 +718,16 @@ # This is where the actual tests are defined. my %tests = ( + 'restrict' => { + all_runs => 1, + regexp => qr/^\\restrict [a-zA-Z0-9]+$/m, + }, + + 'unrestrict' => { + all_runs => 1, + regexp => qr/^\\unrestrict [a-zA-Z0-9]+$/m, + }, + 'ALTER DEFAULT PRIVILEGES FOR ROLE regress_dump_test_role GRANT' => { create_order => 14, create_sql => 'ALTER DEFAULT PRIVILEGES @@ -3875,7 +3885,6 @@ }, 'ALTER TABLE measurement PRIMARY KEY' => { - all_runs => 1, catch_all => 'CREATE ... commands', create_order => 93, create_sql => @@ -3927,7 +3936,6 @@ }, 'ALTER INDEX ... ATTACH PARTITION (primary key)' => { - all_runs => 1, catch_all => 'CREATE ... commands', regexp => qr/^ \QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E @@ -4952,9 +4960,10 @@ # Check for proper test definitions # - # There should be a "like" list, even if it is empty. (This - # makes the test more self-documenting.) - if (!defined($tests{$test}->{like})) + # Either "all_runs" should be set or there should be a "like" list, + # even if it is empty. (This makes the test more self-documenting.) + if (!defined($tests{$test}->{all_runs}) + && !defined($tests{$test}->{like})) { die "missing \"like\" in test \"$test\""; } @@ -4990,9 +4999,10 @@ next; } - # Run the test listed as a like, unless it is specifically noted - # as an unlike (generally due to an explicit exclusion or similar). - if ($tests{$test}->{like}->{$test_key} + # Run the test if all_runs is set or if listed as a like, unless it is + # specifically noted as an unlike (generally due to an explicit + # exclusion or similar). + if (($tests{$test}->{like}->{$test_key} || $tests{$test}->{all_runs}) && !defined($tests{$test}->{unlike}->{$test_key})) { if (!ok($output_file =~ $tests{$test}->{regexp}, diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 41d30de36c70d..af8933234f8de 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -318,6 +318,7 @@ sub filter_dump # that we need to use pg_dumpall from the new node here. my @dump_command = ( 'pg_dumpall', '--no-sync', '-d', $oldnode->connstr('postgres'), + '--restrict-key=test', '-f', $dump1_file); # --extra-float-digits is needed when upgrading from a version older than 11. push(@dump_command, '--extra-float-digits', '0') @@ -503,6 +504,7 @@ sub filter_dump # Second dump from the upgraded instance. @dump_command = ( 'pg_dumpall', '--no-sync', '-d', $newnode->connstr('postgres'), + '--restrict-key=test', '-f', $dump2_file); # --extra-float-digits is needed when upgrading from a version older than 11. push(@dump_command, '--extra-float-digits', '0') diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index f3f8fd0765ac7..877ed0796d648 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -123,6 +123,8 @@ static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_b static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf); +static backslashResult exec_command_restrict(PsqlScanState scan_state, bool active_branch, + const char *cmd); static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch, @@ -132,6 +134,8 @@ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_ static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch); +static backslashResult exec_command_unrestrict(PsqlScanState scan_state, bool active_branch, + const char *cmd); static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch, @@ -182,6 +186,8 @@ static char *pset_value_string(const char *param, printQueryOpt *popt); static void checkWin32Codepage(void); #endif +static bool restricted; +static char *restrict_key; /*---------- @@ -227,8 +233,19 @@ HandleSlashCmds(PsqlScanState scan_state, /* Parse off the command name */ cmd = psql_scan_slash_command(scan_state); - /* And try to execute it */ - status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf); + /* + * And try to execute it. + * + * If we are in "restricted" mode, the only allowable backslash command is + * \unrestrict (to exit restricted mode). + */ + if (restricted && strcmp(cmd, "unrestrict") != 0) + { + pg_log_error("backslash commands are restricted; only \\unrestrict is allowed"); + status = PSQL_CMD_ERROR; + } + else + status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf); if (status == PSQL_CMD_UNKNOWN) { @@ -389,6 +406,8 @@ exec_command(const char *cmd, status = exec_command_quit(scan_state, active_branch); else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) status = exec_command_reset(scan_state, active_branch, query_buf); + else if (strcmp(cmd, "restrict") == 0) + status = exec_command_restrict(scan_state, active_branch, cmd); else if (strcmp(cmd, "s") == 0) status = exec_command_s(scan_state, active_branch); else if (strcmp(cmd, "set") == 0) @@ -405,6 +424,8 @@ exec_command(const char *cmd, status = exec_command_T(scan_state, active_branch); else if (strcmp(cmd, "timing") == 0) status = exec_command_timing(scan_state, active_branch); + else if (strcmp(cmd, "unrestrict") == 0) + status = exec_command_unrestrict(scan_state, active_branch, cmd); else if (strcmp(cmd, "unset") == 0) status = exec_command_unset(scan_state, active_branch, cmd); else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) @@ -2337,6 +2358,35 @@ exec_command_reset(PsqlScanState scan_state, bool active_branch, return PSQL_CMD_SKIP_LINE; } +/* + * \restrict -- enter "restricted mode" with the provided key + */ +static backslashResult +exec_command_restrict(PsqlScanState scan_state, bool active_branch, + const char *cmd) +{ + if (active_branch) + { + char *opt; + + Assert(!restricted); + + opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + if (opt == NULL || opt[0] == '\0') + { + pg_log_error("\\%s: missing required argument", cmd); + return PSQL_CMD_ERROR; + } + + restrict_key = pstrdup(opt); + restricted = true; + } + else + ignore_slash_options(scan_state); + + return PSQL_CMD_SKIP_LINE; +} + /* * \s -- save history in a file or show it on the screen */ @@ -2624,6 +2674,46 @@ exec_command_timing(PsqlScanState scan_state, bool active_branch) return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } +/* + * \unrestrict -- exit "restricted mode" if provided key matches + */ +static backslashResult +exec_command_unrestrict(PsqlScanState scan_state, bool active_branch, + const char *cmd) +{ + if (active_branch) + { + char *opt; + + opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); + if (opt == NULL || opt[0] == '\0') + { + pg_log_error("\\%s: missing required argument", cmd); + return PSQL_CMD_ERROR; + } + + if (!restricted) + { + pg_log_error("\\%s: not currently in restricted mode", cmd); + return PSQL_CMD_ERROR; + } + else if (strcmp(opt, restrict_key) == 0) + { + pfree(restrict_key); + restricted = false; + } + else + { + pg_log_error("\\%s: wrong key", cmd); + return PSQL_CMD_ERROR; + } + } + else + ignore_slash_options(scan_state); + + return PSQL_CMD_SKIP_LINE; +} + /* * \unset -- unset variable */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index b880fa2c1dd1e..eb3b80f452090 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -175,6 +175,10 @@ slashUsage(unsigned short int pager) HELP0(" \\gset [PREFIX] execute query and store result in psql variables\n"); HELP0(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n"); HELP0(" \\q quit psql\n"); + HELP0(" \\restrict RESTRICT_KEY\n" + " enter restricted mode with provided key\n"); + HELP0(" \\unrestrict RESTRICT_KEY\n" + " exit restricted mode if key matches\n"); HELP0(" \\watch [[i=]SEC] [c=N] [m=MIN]\n" " execute query every SEC seconds, up to N times,\n" " stop if less than MIN rows are returned\n"); diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl index bd4fdd2030a37..96c0a064ae913 100644 --- a/src/bin/psql/t/001_basic.pl +++ b/src/bin/psql/t/001_basic.pl @@ -443,4 +443,11 @@ sub psql_fails_like my $c4 = slurp_file($g_file); like($c4, qr/foo.*bar/s); +psql_fails_like( + $node, + qq{\\restrict test +\\! should_fail}, + qr/backslash commands are restricted; only \\unrestrict is allowed/, + 'meta-command in restrict mode fails'); + done_testing(); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 6c62c07ce8219..d4c5f6c3798be 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1746,10 +1746,10 @@ psql_completion(const char *text, int start, int end) "\\out", "\\password", "\\print", "\\prompt", "\\pset", "\\qecho", "\\quit", - "\\reset", + "\\reset", "\\restrict", "\\s", "\\set", "\\setenv", "\\sf", "\\sv", "\\t", "\\T", "\\timing", - "\\unset", + "\\unrestrict", "\\unset", "\\x", "\\warn", "\\watch", "\\write", "\\z", diff --git a/src/test/recovery/t/027_stream_regress.pl b/src/test/recovery/t/027_stream_regress.pl index d1ae32d97d603..7bd9054a618c0 100644 --- a/src/test/recovery/t/027_stream_regress.pl +++ b/src/test/recovery/t/027_stream_regress.pl @@ -106,6 +106,7 @@ command_ok( [ 'pg_dumpall', '-f', $outputdir . '/primary.dump', + '--restrict-key=test', '--no-sync', '-p', $node_primary->port, '--no-unlogged-table-data' # if unlogged, standby has schema only ], @@ -113,6 +114,7 @@ command_ok( [ 'pg_dumpall', '-f', $outputdir . '/standby.dump', + '--restrict-key=test', '--no-sync', '-p', $node_standby_1->port ], 'dump standby server'); @@ -131,6 +133,7 @@ ('--schema', 'pg_catalog'), ('-f', $outputdir . '/catalogs_primary.dump'), '--no-sync', + '--restrict-key=test', ('-p', $node_primary->port), '--no-unlogged-table-data', 'regression' @@ -142,6 +145,7 @@ ('--schema', 'pg_catalog'), ('-f', $outputdir . '/catalogs_standby.dump'), '--no-sync', + '--restrict-key=test', ('-p', $node_standby_1->port), 'regression' ], diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 3bbe4c5f974d6..7e39eef6ec0e5 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4543,6 +4543,7 @@ invalid command \lo \pset arg1 arg2 \q \reset + \restrict test \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2 @@ -4551,6 +4552,7 @@ invalid command \lo \t arg1 \T arg1 \timing arg1 + \unrestrict not_valid \unset arg1 \w arg1 \watch arg1 arg2 diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 3b3c6f6e2944d..f8ad7af5a3a84 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1025,6 +1025,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; \pset arg1 arg2 \q \reset + \restrict test \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2 @@ -1033,6 +1034,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two; \t arg1 \T arg1 \timing arg1 + \unrestrict not_valid \unset arg1 \w arg1 \watch arg1 arg2 From 20ff7f9eb2f8aa6ce9a308dd9f3341303cfcec1e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 11 Aug 2025 15:37:32 -0400 Subject: [PATCH 776/796] Last-minute updates for release notes. Security: CVE-2025-8713, CVE-2025-8714, CVE-2025-8715 --- doc/src/sgml/release-17.sgml | 128 ++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/release-17.sgml b/doc/src/sgml/release-17.sgml index 212a38b868ee6..b0014e782df0e 100644 --- a/doc/src/sgml/release-17.sgml +++ b/doc/src/sgml/release-17.sgml @@ -25,7 +25,7 @@ However, if you have any BRIN numeric_minmax_multi_ops indexes, it is - advisable to reindex them after updating. See the first changelog + advisable to reindex them after updating. See the fourth changelog entry below. @@ -42,6 +42,132 @@ + + Tighten security checks in planner estimation functions + (Dean Rasheed) + § + + + + The fix for CVE-2017-7484, plus followup fixes, intended to prevent + leaky functions from being applied to statistics data for columns + that the calling user does not have permission to read. Two gaps in + that protection have been found. One gap applies to partitioning + and inheritance hierarchies where RLS policies on the tables should + restrict access to statistics data, but did not. + + + + The other gap applies to cases where the query accesses a table via + a view, and the view owner has permissions to read the underlying + table but the calling user does not have permissions on the view. + The view owner's permissions satisfied the security checks, and the + leaky function would get applied to the underlying table's + statistics before we check the calling user's permissions on the + view. This has been fixed by making security checks on views occur + at the start of planning. That might cause permissions failures to + occur earlier than before. + + + + The PostgreSQL Project thanks + Dean Rasheed for reporting this problem. + (CVE-2025-8713) + + + + + + + Prevent pg_dump scripts from being used + to attack the user running the restore (Nathan Bossart) + § + + + + Since dump/restore operations typically involve running SQL commands + as superuser, the target database installation must trust the source + server. However, it does not follow that the operating system user + who executes psql to perform the restore + should have to trust the source server. The risk here is that an + attacker who has gained superuser-level control over the source + server might be able to cause it to emit text that would be + interpreted as psql meta-commands. + That would provide shell-level access to the restoring user's own + account, independently of access to the target database. + + + + To provide a positive guarantee that this can't happen, + extend psql with + a \restrict command that prevents execution of + further meta-commands, and teach pg_dump + to issue that before any data coming from the source server. + + + + The PostgreSQL Project thanks Martin Rakhmanov, Matthieu Denais, and + RyotaK for reporting this problem. + (CVE-2025-8714) + + + + + + + Convert newlines to spaces in names included in comments + in pg_dump output + (Noah Misch) + § + + + + Object names containing newlines offered the ability to inject + arbitrary SQL commands into the output script. (Without the + preceding fix, injection of psql + meta-commands would also be possible this way.) + CVE-2012-0868 fixed this class of problem at the time, but later + work reintroduced several cases. + + + + The PostgreSQL Project thanks + Noah Misch for reporting this problem. + (CVE-2025-8715) + + + + +