($INBOX_DIR/description missing)
help / color / mirror / Atom feed[PATCH 3/4] Retrieve-shared-location-for-dict
56+ messages / 12 participants
[nested] [flat]
* [PATCH 3/4] Retrieve-shared-location-for-dict
@ 2019-01-17 12:38 Arthur Zakirov <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Arthur Zakirov @ 2019-01-17 12:38 UTC (permalink / raw)
---
src/backend/commands/tsearchcmds.c | 11 +
src/backend/storage/ipc/ipci.c | 7 +
src/backend/tsearch/Makefile | 2 +-
src/backend/tsearch/ts_shared.c | 494 +++++++++++++++++++++++++++++
src/backend/utils/cache/ts_cache.c | 51 +++
src/include/storage/lwlock.h | 2 +
src/include/tsearch/ts_cache.h | 3 +
src/include/tsearch/ts_shared.h | 28 ++
8 files changed, 597 insertions(+), 1 deletion(-)
create mode 100644 src/backend/tsearch/ts_shared.c
create mode 100644 src/include/tsearch/ts_shared.h
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 30c5eb72a2..90ad24c019 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -40,6 +40,7 @@
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "tsearch/ts_cache.h"
+#include "tsearch/ts_shared.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -528,6 +529,11 @@ RemoveTSDictionaryById(Oid dictId)
CatalogTupleDelete(relation, &tup->t_self);
+ ts_dict_shmem_release(dictId,
+ HeapTupleHeaderGetRawXmin(tup->t_data),
+ HeapTupleHeaderGetRawXmax(tup->t_data),
+ tup->t_self, true);
+
ReleaseSysCache(tup);
table_close(relation, RowExclusiveLock);
@@ -637,6 +643,11 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
ObjectAddressSet(address, TSDictionaryRelationId, dictId);
+ ts_dict_shmem_release(dictId,
+ HeapTupleHeaderGetRawXmin(tup->t_data),
+ HeapTupleHeaderGetRawXmax(tup->t_data),
+ tup->t_self, true);
+
/*
* NOTE: because we only support altering the options, not the template,
* there is no need to update dependencies. This might have to change if
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 5965d3620f..029354b16c 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -44,6 +44,7 @@
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
+#include "tsearch/ts_shared.h"
#include "utils/snapmgr.h"
/* GUCs */
@@ -150,6 +151,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, TsearchShmemSize());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -270,6 +272,11 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
SyncScanShmemInit();
AsyncShmemInit();
+ /*
+ * Set up shared memory to tsearch
+ */
+ TsearchShmemInit();
+
#ifdef EXEC_BACKEND
/*
diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile
index 62d8bb3254..0b25c20fb0 100644
--- a/src/backend/tsearch/Makefile
+++ b/src/backend/tsearch/Makefile
@@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES))
OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \
dict_simple.o dict_synonym.o dict_thesaurus.o \
dict_ispell.o regis.o spell.o \
- to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o
+ to_tsany.o ts_selfuncs.o ts_shared.o ts_typanalyze.o ts_utils.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/tsearch/ts_shared.c b/src/backend/tsearch/ts_shared.c
new file mode 100644
index 0000000000..d84fcb5eff
--- /dev/null
+++ b/src/backend/tsearch/ts_shared.c
@@ -0,0 +1,494 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.c
+ * tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/tsearch/ts_shared.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "lib/dshash.h"
+#include "miscadmin.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "tsearch/ts_shared.h"
+#include "utils/hashutils.h"
+#include "utils/memutils.h"
+
+
+/*
+ * Hash table entries key.
+ */
+typedef struct
+{
+ Oid db_id;
+ DictEntryData dict;
+} TsearchDictKey;
+
+/*
+ * Hash table entries representing shared dictionaries.
+ */
+typedef struct
+{
+ TsearchDictKey key;
+ dsm_handle dict_dsm;
+
+ /*
+ * We need a flag that the DSM segment is pinned/unpinned. Otherwise we can
+ * face double dsm_unpin_segment().
+ */
+ bool segment_ispinned;
+
+ slock_t mutex; /* protects the reference count */
+ uint32 refcnt; /* number of mapped backends */
+} TsearchDictEntry;
+
+/*
+ * Compiled dictionary data stored within the hash table.
+ */
+typedef struct
+{
+ TsearchDictKey dict_key; /* entry's key used to release the entry */
+ char dict[FLEXIBLE_ARRAY_MEMBER];
+} TsearchDictData;
+
+#define TsearchDictDataHdrSize MAXALIGN(offsetof(TsearchDictData, dict))
+
+static dshash_table *dict_table = NULL;
+
+/*
+ * Information about the main shmem segment, used to coordinate
+ * access to the hash table and dictionaries.
+ */
+typedef struct
+{
+ dsa_handle area;
+ dshash_table_handle dict_table_handle;
+
+ LWLock lock;
+} TsearchCtlData;
+
+static TsearchCtlData *tsearch_ctl;
+
+static int tsearch_dict_cmp(const void *a, const void *b, size_t size,
+ void *arg);
+static uint32 tsearch_dict_hash(const void *a, size_t size, void *arg);
+
+static void init_dict_table(void);
+static dsm_segment *dict_entry_init(TsearchDictKey *key,
+ TsearchDictEntry *entry, void *dict,
+ Size dict_size);
+static dsm_segment *dict_entry_attach(TsearchDictEntry *entry);
+static void dict_entry_on_detach(dsm_segment *segment, Datum datum);
+
+/* Parameters for dict_table */
+static const dshash_parameters dict_table_params ={
+ sizeof(TsearchDictKey),
+ sizeof(TsearchDictEntry),
+ tsearch_dict_cmp,
+ tsearch_dict_hash,
+ LWTRANCHE_TSEARCH_TABLE
+};
+
+/*
+ * Build the dictionary using allocate_cb callback.
+ *
+ * Firstly try to find the dictionary in shared hash table. If it was built by
+ * someone earlier just return its location in DSM.
+ *
+ * init_data: an argument used within a template's init method.
+ * allocate_cb: function to build the dictionary, if it wasn't found in DSM.
+ *
+ * Returns address in the dynamic shared memory segment or in backend memory.
+ */
+void *
+ts_dict_shmem_location(DictInitData *init_data,
+ ts_dict_build_callback allocate_cb)
+{
+ TsearchDictKey key;
+ TsearchDictEntry *entry;
+ TsearchDictData *dict_data;
+ bool found;
+ dsm_segment *seg;
+ void *dict;
+ Size dict_size;
+
+ init_dict_table();
+
+ /*
+ * Build the dictionary in backend's memory if dictid is invalid (it may
+ * happen if the dicionary's init method was called within
+ * verify_dictoptions()).
+ */
+ if (!OidIsValid(init_data->dict.id))
+ {
+ dict = allocate_cb(init_data->dict_options, &dict_size);
+
+ return dict;
+ }
+
+ /* Set up key for hashtable search */
+ key.db_id = MyDatabaseId;
+ key.dict = init_data->dict;
+
+ /* Try to find an entry in the hash table */
+ entry = (TsearchDictEntry *) dshash_find(dict_table, &key, false);
+
+ if (entry)
+ {
+ seg = dsm_find_mapping(entry->dict_dsm);
+ if (!seg)
+ seg = dict_entry_attach(entry);
+ dshash_release_lock(dict_table, entry);
+
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ return dict_data->dict;
+ }
+
+ /* Dictionary haven't been loaded into memory yet */
+ entry = (TsearchDictEntry *) dshash_find_or_insert(dict_table, &key,
+ &found);
+
+ if (found)
+ {
+ /*
+ * Someone concurrently inserted a dictionary entry since the first time
+ * we checked.
+ */
+ seg = dict_entry_attach(entry);
+ dshash_release_lock(dict_table, entry);
+
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ return dict_data->dict;
+ }
+
+ /* Build the dictionary */
+ dict = allocate_cb(init_data->dict_options, &dict_size);
+
+ /* At least initialize a dictionary entry */
+ seg = dict_entry_init(&key, entry, dict, dict_size);
+ dshash_release_lock(dict_table, entry);
+
+ pfree(dict);
+
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ return dict_data->dict;
+}
+
+/*
+ * Release memory occupied by the dictionary. Function unpins DSM mapping and
+ * if the dictionary is being dropped or altered unpins the DSM segment.
+ *
+ * The segment still may leak. It may happen if some backend used the
+ * dictionary before dropping, the backend will hold its DSM segment till
+ * disconnecting or calling lookup_ts_dictionary_cache().
+ *
+ * id, xmin, xmax, tid: information to search the dictionary's DSM segment.
+ * unpin_segment: true if we need to unpin the segment in case if the dictionary
+ * was dropped or altered.
+ */
+void
+ts_dict_shmem_release(Oid id, TransactionId xmin, TransactionId xmax,
+ ItemPointerData tid, bool unpin_segment)
+{
+ TsearchDictKey key;
+ TsearchDictEntry *entry;
+
+ /*
+ * If we didn't attach to a hash table then do nothing.
+ */
+ if (!dict_table && !unpin_segment)
+ return;
+ /*
+ * But if we need to unpin the DSM segment to get of rid of the segment when
+ * the last interested process disconnects we need the hash table to find
+ * the dictionary's entry.
+ */
+ else if (unpin_segment)
+ init_dict_table();
+
+ /* Set up key for hashtable search */
+ key.db_id = MyDatabaseId;
+ key.dict.id = id;
+ key.dict.xmin = xmin;
+ key.dict.xmax = xmax;
+ key.dict.tid = tid;
+
+ /* Try to find an entry in the hash table */
+ entry = (TsearchDictEntry *) dshash_find(dict_table, &key, true);
+
+ if (entry)
+ {
+ dsm_segment *seg;
+
+ seg = dsm_find_mapping(entry->dict_dsm);
+
+ if (seg)
+ {
+ TsearchDictData *dict_data;
+
+ dsm_unpin_mapping(seg);
+ /*
+ * Cancel cleanup callback to avoid a deadlock. Cleanup is done
+ * below.
+ */
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ cancel_on_dsm_detach(seg, dict_entry_on_detach,
+ PointerGetDatum(&dict_data->dict_key));
+ dsm_detach(seg);
+
+ entry->refcnt--;
+ }
+
+ if (unpin_segment && entry->segment_ispinned)
+ {
+ dsm_unpin_segment(entry->dict_dsm);
+ entry->segment_ispinned = false;
+
+ Assert(entry->refcnt > 0);
+ entry->refcnt--;
+ }
+
+ if (entry->refcnt == 0)
+ dshash_delete_entry(dict_table, entry);
+ else
+ dshash_release_lock(dict_table, entry);
+ }
+}
+
+/*
+ * Allocate and initialize tsearch-related shared memory.
+ */
+void
+TsearchShmemInit(void)
+{
+ bool found;
+
+ tsearch_ctl = (TsearchCtlData *)
+ ShmemInitStruct("Full Text Search Ctl", sizeof(TsearchCtlData), &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_TSEARCH_DSA, "tsearch_dsa");
+ LWLockRegisterTranche(LWTRANCHE_TSEARCH_TABLE, "tsearch_table");
+
+ LWLockInitialize(&tsearch_ctl->lock, LWTRANCHE_TSEARCH_DSA);
+
+ tsearch_ctl->area = DSM_HANDLE_INVALID;
+ tsearch_ctl->dict_table_handle = InvalidDsaPointer;
+ }
+}
+
+/*
+ * Report shared memory space needed by TsearchShmemInit.
+ */
+Size
+TsearchShmemSize(void)
+{
+ Size size = 0;
+
+ /* size of service structure */
+ size = add_size(size, MAXALIGN(sizeof(TsearchCtlData)));
+
+ return size;
+}
+
+/*
+ * A comparator function for TsearchDictKey.
+ *
+ * Returns 1 if keys are equal.
+ */
+static int
+tsearch_dict_cmp(const void *a, const void *b, size_t size, void *arg)
+{
+ TsearchDictKey *k1 = (TsearchDictKey *) a;
+ TsearchDictKey *k2 = (TsearchDictKey *) b;
+
+ if (k1->db_id == k2->db_id && k1->dict.id == k2->dict.id &&
+ k1->dict.xmin == k2->dict.xmin && k1->dict.xmax == k2->dict.xmax &&
+ ItemPointerEquals(&k1->dict.tid, &k2->dict.tid))
+ return 0;
+ else
+ return 1;
+}
+
+/*
+ * A hash function for TsearchDictKey.
+ */
+static uint32
+tsearch_dict_hash(const void *a, size_t size, void *arg)
+{
+ TsearchDictKey *k = (TsearchDictKey *) a;
+ uint32 s;
+
+ s = hash_combine(0, hash_uint32(k->db_id));
+ s = hash_combine(s, hash_uint32(k->dict.id));
+ s = hash_combine(s, hash_uint32(k->dict.xmin));
+ s = hash_combine(s, hash_uint32(k->dict.xmax));
+ s = hash_combine(s,
+ hash_uint32(BlockIdGetBlockNumber(&k->dict.tid.ip_blkid)));
+ s = hash_combine(s, hash_uint32(k->dict.tid.ip_posid));
+
+ return s;
+}
+
+/*
+ * Initialize hash table located in DSM.
+ *
+ * The hash table should be created and initialized if it doesn't exist yet.
+ */
+static void
+init_dict_table(void)
+{
+ MemoryContext old_context;
+ dsa_area *dsa;
+
+ /* Exit if hash table was initialized alread */
+ if (dict_table)
+ return;
+
+ old_context = MemoryContextSwitchTo(TopMemoryContext);
+
+recheck_table:
+ LWLockAcquire(&tsearch_ctl->lock, LW_SHARED);
+
+ /* Hash table have been created already by someone */
+ if (DsaPointerIsValid(tsearch_ctl->dict_table_handle))
+ {
+ Assert(tsearch_ctl->area != DSM_HANDLE_INVALID);
+
+ dsa = dsa_attach(tsearch_ctl->area);
+
+ dict_table = dshash_attach(dsa,
+ &dict_table_params,
+ tsearch_ctl->dict_table_handle,
+ NULL);
+ }
+ else
+ {
+ /* Try to get exclusive lock */
+ LWLockRelease(&tsearch_ctl->lock);
+ if (!LWLockAcquireOrWait(&tsearch_ctl->lock, LW_EXCLUSIVE))
+ {
+ /*
+ * The lock was released by another backend and other backend
+ * has concurrently created the hash table already.
+ */
+ goto recheck_table;
+ }
+
+ dsa = dsa_create(LWTRANCHE_TSEARCH_DSA);
+ tsearch_ctl->area = dsa_get_handle(dsa);
+
+ dict_table = dshash_create(dsa, &dict_table_params, NULL);
+ tsearch_ctl->dict_table_handle = dshash_get_hash_table_handle(dict_table);
+
+ /* Remain attached until end of postmaster */
+ dsa_pin(dsa);
+ }
+
+ LWLockRelease(&tsearch_ctl->lock);
+
+ /* Remain attached until end of session */
+ dsa_pin_mapping(dsa);
+
+ MemoryContextSwitchTo(old_context);
+}
+
+/*
+ * Initialize a dictionary's DSM segment entry within shared hash table.
+ */
+static dsm_segment *
+dict_entry_init(TsearchDictKey *key, TsearchDictEntry *entry, void *dict,
+ Size dict_size)
+{
+ TsearchDictData *dict_data;
+ dsm_segment *seg;
+
+ /* Allocate a DSM segment for the compiled dictionary */
+ seg = dsm_create(TsearchDictDataHdrSize + dict_size, 0);
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ dict_data->dict_key = *key;
+ memcpy(dict_data->dict, dict, dict_size);
+
+ entry->key = *key;
+ entry->dict_dsm = dsm_segment_handle(seg);
+ entry->segment_ispinned = true;
+ SpinLockInit(&entry->mutex);
+ entry->refcnt = 2; /* 1 for session + 1 for postmaster */
+
+ /* Remain attached until end of postmaster */
+ dsm_pin_segment(seg);
+ /* Remain attached until end of session */
+ dsm_pin_mapping(seg);
+
+ /* Register the shared hash table cleanup callback */
+ on_dsm_detach(seg, dict_entry_on_detach,
+ PointerGetDatum(&dict_data->dict_key));
+
+ return seg;
+}
+
+/*
+ * Attach a dictionary's DSM segment and pin mapping until end of session.
+ *
+ * Entry's reference counter increments to properly release it if no one else
+ * has mapping to this DSM using on-dsm-detach callback.
+ */
+static dsm_segment *
+dict_entry_attach(TsearchDictEntry *entry)
+{
+ dsm_segment *seg;
+ TsearchDictData *dict_data;
+
+ seg = dsm_attach(entry->dict_dsm);
+ if (seg == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not map dynamic shared memory segment")));
+ /* Remain attached until end of session */
+ dsm_pin_mapping(seg);
+
+ /* We need a mutex here since the entry might be locked non-exclusively */
+ SpinLockAcquire(&entry->mutex);
+ entry->refcnt++;
+ SpinLockRelease(&entry->mutex);
+
+ dict_data = (TsearchDictData *) dsm_segment_address(seg);
+ /* Register the shared hash table cleanup callback */
+ on_dsm_detach(seg, dict_entry_on_detach,
+ PointerGetDatum(&dict_data->dict_key));
+
+ return seg;
+}
+
+/*
+ * When a session detaches from a DSM segment we need to check is someone else
+ * attached the segment. If it is not then delete the related shared hash table
+ * entry.
+ */
+static void
+dict_entry_on_detach(dsm_segment *segment, Datum datum)
+{
+ TsearchDictKey *key = (TsearchDictKey *) DatumGetPointer(datum);
+ TsearchDictEntry *entry;
+
+ /* Find the entry and lock it to decrement the refcnt */
+ entry = (TsearchDictEntry *) dshash_find(dict_table, key, true);
+ if (entry)
+ {
+ Assert(entry->refcnt > 0);
+ if (--entry->refcnt == 0)
+ dshash_delete_entry(dict_table, entry);
+ else
+ dshash_release_lock(dict_table, entry);
+ }
+}
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 8bc8d82c76..ea8fb9a039 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -40,6 +40,7 @@
#include "commands/defrem.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_public.h"
+#include "tsearch/ts_shared.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
@@ -75,6 +76,7 @@ static TSConfigCacheEntry *lastUsedConfig = NULL;
char *TSCurrentConfig = NULL;
static Oid TSCurrentConfigCache = InvalidOid;
+static bool has_invalid_dictionary = false;
/*
@@ -86,6 +88,10 @@ static Oid TSCurrentConfigCache = InvalidOid;
* doesn't seem worth the trouble to determine that; we just flush all the
* entries of the related hash table.
*
+ * We set has_invalid_dictionary to true to unpin all used segments later on
+ * a first text search function usage. It isn't safe to call
+ * ts_dict_shmem_release() here since it may call kernel functions.
+ *
* We can use the same function for all TS caches by passing the hash
* table address as the "arg".
*/
@@ -98,13 +104,48 @@ InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
hash_seq_init(&status, hash);
while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (cacheid == TSDICTOID)
+ {
+ TSDictionaryCacheEntry *dict_entry;
+
+ dict_entry = (TSDictionaryCacheEntry *) entry;
+ if (dict_entry->hashvalue == hashvalue)
+ {
+ dict_entry->shmem_valid = false;
+ has_invalid_dictionary = true;
+ }
+ }
+
entry->isvalid = false;
+ }
/* Also invalidate the current-config cache if it's pg_ts_config */
if (hash == TSConfigCacheHash)
TSCurrentConfigCache = InvalidOid;
}
+/*
+ * Unpin shared segments of all invalid dictionary entries.
+ */
+static void
+do_ts_dict_shmem_release(void)
+{
+ HASH_SEQ_STATUS status;
+ TSDictionaryCacheEntry *entry;
+
+ if (!has_invalid_dictionary)
+ return;
+
+ hash_seq_init(&status, TSDictionaryCacheHash);
+ while ((entry = (TSDictionaryCacheEntry *) hash_seq_search(&status)) != NULL)
+ if (!entry->shmem_valid)
+ ts_dict_shmem_release(entry->dictId, entry->dict_xmin,
+ entry->dict_xmax, entry->dict_tid, false);
+
+ has_invalid_dictionary = false;
+}
+
/*
* Fetch parser cache entry
*/
@@ -253,6 +294,13 @@ lookup_ts_dictionary_cache(Oid dictId)
Form_pg_ts_template template;
MemoryContext saveCtx;
+ /*
+ * It is possible that some invalid entries hold a DSM mapping and we
+ * need to unpin it to avoid memory leaking. We will unpin segments of
+ * all other invalid dictionaries.
+ */
+ do_ts_dict_shmem_release();
+
tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
if (!HeapTupleIsValid(tpdict))
elog(ERROR, "cache lookup failed for text search dictionary %u",
@@ -359,6 +407,9 @@ lookup_ts_dictionary_cache(Oid dictId)
fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
entry->isvalid = true;
+ entry->hashvalue =
+ GetSysCacheHashValue1(TSDICTOID, ObjectIdGetDatum(entry->dictId));
+ entry->shmem_valid = true;
}
lastUsedDictionary = entry;
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 96c7732006..49a3319a11 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -219,6 +219,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TUPLESTORE,
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
+ LWTRANCHE_TSEARCH_DSA,
+ LWTRANCHE_TSEARCH_TABLE,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h
index 2298e0a275..14e13bf252 100644
--- a/src/include/tsearch/ts_cache.h
+++ b/src/include/tsearch/ts_cache.h
@@ -54,6 +54,9 @@ typedef struct TSDictionaryCacheEntry
Oid dictId;
bool isvalid;
+ uint32 hashvalue; /* hash value of the dictionary's OID */
+ bool shmem_valid;
+
TransactionId dict_xmin; /* XMIN of the dictionary's tuple */
TransactionId dict_xmax; /* XMAX of the dictionary's tuple */
ItemPointerData dict_tid; /* TID of the dictionary's tuple */
diff --git a/src/include/tsearch/ts_shared.h b/src/include/tsearch/ts_shared.h
new file mode 100644
index 0000000000..1e506ef737
--- /dev/null
+++ b/src/include/tsearch/ts_shared.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.h
+ * tsearch shared memory management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ * src/include/tsearch/ts_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TS_SHARED_H
+#define TS_SHARED_H
+
+#include "tsearch/ts_public.h"
+
+typedef void *(*ts_dict_build_callback) (List *dictoptions, Size *size);
+
+extern void *ts_dict_shmem_location(DictInitData *init_data,
+ ts_dict_build_callback allocate_cb);
+extern void ts_dict_shmem_release(Oid id, TransactionId xmin,
+ TransactionId xmax, ItemPointerData tid,
+ bool unpin_segment);
+
+extern void TsearchShmemInit(void);
+extern Size TsearchShmemSize(void);
+
+#endif /* TS_SHARED_H */
--
2.20.1
--------------9E285E8AF9A980CD1515771F
Content-Type: text/x-patch;
name="0004-Store-ispell-in-shared-location-v18.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0004-Store-ispell-in-shared-location-v18.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* [PATCH 3/4] Retrieve-shared-location-for-dict
@ 2019-01-17 12:38 Arthur Zakirov <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Arthur Zakirov @ 2019-01-17 12:38 UTC (permalink / raw)
Reviewed-by: Tomas Vondra, Ildus Kurbangaliev
---
src/backend/postmaster/pgstat.c | 3 +
src/backend/tsearch/Makefile | 2 +-
src/backend/tsearch/ts_shared.c | 159 ++++++++++++++++++
src/bin/initdb/initdb.c | 1 +
src/bin/pg_rewind/filemap.c | 6 +
.../pg_verify_checksums/pg_verify_checksums | Bin 0 -> 337616 bytes
src/include/pgstat.h | 1 +
src/include/tsearch/ts_shared.h | 27 +++
8 files changed, 198 insertions(+), 1 deletion(-)
create mode 100644 src/backend/tsearch/ts_shared.c
create mode 100755 src/bin/pg_verify_checksums/pg_verify_checksums
create mode 100644 src/include/tsearch/ts_shared.h
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 0355fa65fb..fdcf15575a 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3977,6 +3977,9 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_TIMELINE_HISTORY_WRITE:
event_name = "TimelineHistoryWrite";
break;
+ case WAIT_EVENT_TS_SHARED_DICT_WRITE:
+ event_name = "TSSharedDictWrite";
+ break;
case WAIT_EVENT_TWOPHASE_FILE_READ:
event_name = "TwophaseFileRead";
break;
diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile
index 62d8bb3254..0b25c20fb0 100644
--- a/src/backend/tsearch/Makefile
+++ b/src/backend/tsearch/Makefile
@@ -26,7 +26,7 @@ DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES))
OBJS = ts_locale.o ts_parse.o wparser.o wparser_def.o dict.o \
dict_simple.o dict_synonym.o dict_thesaurus.o \
dict_ispell.o regis.o spell.o \
- to_tsany.o ts_selfuncs.o ts_typanalyze.o ts_utils.o
+ to_tsany.o ts_selfuncs.o ts_shared.o ts_typanalyze.o ts_utils.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/tsearch/ts_shared.c b/src/backend/tsearch/ts_shared.c
new file mode 100644
index 0000000000..c1249f9a75
--- /dev/null
+++ b/src/backend/tsearch/ts_shared.c
@@ -0,0 +1,159 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.c
+ * Text search shared dictionary management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/tsearch/ts_shared.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#ifndef WIN32
+#include <sys/mman.h>
+#endif
+#include <sys/stat.h>
+
+#include "pgstat.h"
+#include "storage/fd.h"
+#include "tsearch/ts_shared.h"
+
+
+char *
+ts_dict_shared_init(DictInitData *init_data, ts_dict_build_callback allocate_cb)
+{
+ char *name;
+ int flags;
+ int fd;
+ void *dict;
+ Size dict_size;
+
+ /*
+ * Build the dictionary in backend's memory if dictid is invalid (it may
+ * happen if the dicionary's init method was called within
+ * verify_dictoptions()).
+ */
+ if (!OidIsValid(init_data->dict.id))
+ {
+ dict = allocate_cb(init_data->dict_options, &dict_size);
+
+ return dict;
+ }
+
+ name = psprintf(PG_SHDICT_DIR "/%u", init_data->dict.id);
+
+ /* Try to create a new file */
+ flags = O_RDWR | O_CREAT | O_EXCL | PG_BINARY;
+ if ((fd = OpenTransientFile(name, flags)) == -1)
+ {
+ if (errno != EEXIST)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open shared dictionary file \"%s\": %m",
+ name)));
+ /* The file was created before */
+ return name;
+ }
+
+ /* Build the dictionary */
+ dict = allocate_cb(init_data->dict_options, &dict_size);
+
+ /* And write it to the shared file */
+ pgstat_report_wait_start(WAIT_EVENT_TS_SHARED_DICT_WRITE);
+ if (write(fd, dict, dict_size) != dict_size)
+ {
+ pgstat_report_wait_end();
+ /* if write didn't set errno, assume problem is no disk space */
+ if (errno == 0)
+ errno = ENOSPC;
+ CloseTransientFile(fd);
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to shared dictionary file \"%s\": %m",
+ name)));
+ }
+ pgstat_report_wait_end();
+
+ pfree(dict);
+
+ if (CloseTransientFile(fd))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close shared dictionary file \"%s\": %m",
+ name)));
+
+ return name;
+}
+
+void *
+ts_dict_shared_attach(const char *dict_name, Size *dict_size)
+{
+ int flags;
+ int fd;
+ void *address;
+ struct stat st;
+
+ /* Open an existing file for attach */
+ flags = O_RDONLY | PG_BINARY;
+ if ((fd = OpenTransientFile(dict_name, flags)) == -1)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open shared dictionary file \"%s\": %m",
+ dict_name)));
+
+ if (fstat(fd, &st) != 0)
+ {
+ int save_errno;
+
+ save_errno = errno;
+ CloseTransientFile(fd);
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat shared dictionary file \"%s\": %m",
+ dict_name)));
+ }
+ *dict_size = st.st_size;
+
+ /* Map the shared file. We need only read access */
+ address = mmap(NULL, *dict_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (address == MAP_FAILED)
+ {
+ int save_errno;
+
+ save_errno = errno;
+ CloseTransientFile(fd);
+ errno = save_errno;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not map shared dictionary file \"%s\": %m",
+ dict_name)));
+ return false;
+ }
+
+ if (CloseTransientFile(fd))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not close shared dictionary file \"%s\": %m",
+ dict_name)));
+
+ return address;
+}
+
+void
+ts_dict_shared_detach(const char *dict_name, void *dict_address, Size dict_size)
+{
+ if (munmap(dict_address, dict_size) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not unmap shared memory segment \"%s\": %m",
+ dict_name)));
+}
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 09b59c8324..29c7afbc27 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -207,6 +207,7 @@ static const char *const subdirs[] = {
"pg_dynshmem",
"pg_notify",
"pg_serial",
+ "pg_shdict",
"pg_snapshots",
"pg_subtrans",
"pg_twophase",
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 63d0baee74..ff76086328 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -58,6 +58,12 @@ static const char *excludeDirContents[] =
*/
"pg_replslot",
+ /*
+ * Skip compiled dictionaries files. A dictionary will be compiled on first
+ * demand.
+ */
+ "pg_shdict",
+
/* Contents removed on startup, see dsm_cleanup_for_mmap(). */
"pg_dynshmem", /* defined as PG_DYNSHMEM_DIR */
diff --git a/src/bin/pg_verify_checksums/pg_verify_checksums b/src/bin/pg_verify_checksums/pg_verify_checksums
new file mode 100755
index 0000000000000000000000000000000000000000..06a474673896c8f16f239cad62250aea269ad7a0
GIT binary patch
literal 337616
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
FzW_Zh=;8nX
literal 0
HcmV?d00001
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 5888242f75..42ad0a1889 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -927,6 +927,7 @@ typedef enum
WAIT_EVENT_TIMELINE_HISTORY_READ,
WAIT_EVENT_TIMELINE_HISTORY_SYNC,
WAIT_EVENT_TIMELINE_HISTORY_WRITE,
+ WAIT_EVENT_TS_SHARED_DICT_WRITE,
WAIT_EVENT_TWOPHASE_FILE_READ,
WAIT_EVENT_TWOPHASE_FILE_SYNC,
WAIT_EVENT_TWOPHASE_FILE_WRITE,
diff --git a/src/include/tsearch/ts_shared.h b/src/include/tsearch/ts_shared.h
new file mode 100644
index 0000000000..f8cff7d8ad
--- /dev/null
+++ b/src/include/tsearch/ts_shared.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * ts_shared.h
+ * Text search shared dictionary management
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ *
+ * src/include/tsearch/ts_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef TS_SHARED_H
+#define TS_SHARED_H
+
+#include "tsearch/ts_public.h"
+
+#define PG_SHDICT_DIR "pg_shdict"
+
+typedef void *(*ts_dict_build_callback) (List *dictoptions, Size *size);
+
+extern char *ts_dict_shared_init(DictInitData *init_data,
+ ts_dict_build_callback allocate_cb);
+extern void *ts_dict_shared_attach(const char *dict_name, Size *dict_size);
+extern void ts_dict_shared_detach(const char *dict_name, void *dict_address,
+ Size dict_size);
+
+#endif /* TS_SHARED_H */
--
2.21.0
--------------C3497FBBD55C428FF91561C0
Content-Type: text/x-patch;
name="0004-Store-ispell-in-shared-location-v19.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0004-Store-ispell-in-shared-location-v19.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 21:24 Justin Pryzby <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Justin Pryzby @ 2021-10-11 21:24 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: pgsql-hackers; Mark Dilger <[email protected]>
This commit broke psql \d datname.nspname.relname
commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868
Author: Robert Haas <[email protected]>
Date: Wed Feb 3 13:19:41 2021 -0500
Factor pattern-construction logic out of processSQLNamePattern.
...
patternToSQLRegex is a little more general than what is required
by processSQLNamePattern. That function is only interested in
patterns that can have up to 2 parts, a schema and a relation;
but patternToSQLRegex can limit the maximum number of parts to
between 1 and 3, so that patterns can look like either
"database.schema.relation", "schema.relation", or "relation"
depending on how it's invoked and what the user specifies.
processSQLNamePattern only passes two buffers, so works exactly
the same as before, always interpreting the pattern as either
a "schema.relation" pattern or a "relation" pattern. But,
future callers can use this function in other ways.
|$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
|psql (15devel)
|Type "help" for help.
|regression=# \d regresion.public.bit_defaults
|Did not find any relation named "regresion.public.bit_defaults".
|regression=# \d public.bit_defaults
| Table "public.bit_defaults"
|...
This worked before v14 (even though the commit message says otherwise).
|$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
|psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
|...
|regression=# \d regresion.public.bit_defaults
| Table "public.bit_defaults"
|...
--
Justin
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 21:47 Mark Dilger <[email protected]>
parent: Justin Pryzby <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-11 21:47 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Robert Haas <[email protected]>; pgsql-hackers
> On Oct 11, 2021, at 2:24 PM, Justin Pryzby <[email protected]> wrote:
>
> This commit broke psql \d datname.nspname.relname
>
> commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868
> Author: Robert Haas <[email protected]>
> Date: Wed Feb 3 13:19:41 2021 -0500
>
> Factor pattern-construction logic out of processSQLNamePattern.
> ...
> patternToSQLRegex is a little more general than what is required
> by processSQLNamePattern. That function is only interested in
> patterns that can have up to 2 parts, a schema and a relation;
> but patternToSQLRegex can limit the maximum number of parts to
> between 1 and 3, so that patterns can look like either
> "database.schema.relation", "schema.relation", or "relation"
> depending on how it's invoked and what the user specifies.
>
> processSQLNamePattern only passes two buffers, so works exactly
> the same as before, always interpreting the pattern as either
> a "schema.relation" pattern or a "relation" pattern. But,
> future callers can use this function in other ways.
>
> |$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
> |psql (15devel)
> |Type "help" for help.
> |regression=# \d regresion.public.bit_defaults
> |Did not find any relation named "regresion.public.bit_defaults".
> |regression=# \d public.bit_defaults
> | Table "public.bit_defaults"
> |...
>
> This worked before v14 (even though the commit message says otherwise).
>
> |$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
> |psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
> |...
> |regression=# \d regresion.public.bit_defaults
> | Table "public.bit_defaults"
> |...
I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
Am I misunderstanding your bug report?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 22:04 Tom Lane <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Tom Lane @ 2021-10-11 22:04 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
Mark Dilger <[email protected]> writes:
> I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
Doesn't work with the correct DB name, either:
regression=# \d public.bit_defaults
Table "public.bit_defaults"
Column | Type | Collation | Nullable | Default
--------+----------------+-----------+----------+---------------------
b1 | bit(4) | | | '1001'::"bit"
b2 | bit(4) | | | '0101'::"bit"
b3 | bit varying(5) | | | '1001'::bit varying
b4 | bit varying(5) | | | '0101'::"bit"
regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 22:25 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-10-11 22:25 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
> On Oct 11, 2021, at 3:04 PM, Tom Lane <[email protected]> wrote:
>
> Doesn't work with the correct DB name, either:
>
> regression=# \d public.bit_defaults
> Table "public.bit_defaults"
> Column | Type | Collation | Nullable | Default
> --------+----------------+-----------+----------+---------------------
> b1 | bit(4) | | | '1001'::"bit"
> b2 | bit(4) | | | '0101'::"bit"
> b3 | bit varying(5) | | | '1001'::bit varying
> b4 | bit varying(5) | | | '0101'::"bit"
>
> regression=# \d regression.public.bit_defaults
> Did not find any relation named "regression.public.bit_defaults".
REL_13_STABLE appears to accept any amount of nonsense you like:
foo=# \d nonesuch.foo.a.b.c.d.bar.baz
Table "bar.baz"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Is this something we're intentionally supporting? There is no regression test covering this, else we'd have seen breakage in the build-farm.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 22:26 Justin Pryzby <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Justin Pryzby @ 2021-10-11 22:26 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Robert Haas <[email protected]>; pgsql-hackers
On Mon, Oct 11, 2021 at 02:47:59PM -0700, Mark Dilger wrote:
> > |$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
> > |psql (15devel)
> > |Type "help" for help.
> > |regression=# \d regresion.public.bit_defaults
> > |Did not find any relation named "regresion.public.bit_defaults".
> > |regression=# \d public.bit_defaults
> > | Table "public.bit_defaults"
> > |...
> >
> > This worked before v14 (even though the commit message says otherwise).
> >
> > |$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
> > |psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
> > |...
> > |regression=# \d regresion.public.bit_defaults
> > | Table "public.bit_defaults"
> > |...
>
> I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
>
> Am I misunderstanding your bug report?
It's not intentional but certainly confusing to put a typo there.
Sorry for that (and good eyes, BTW).
In v15/master:
regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
After reverting that commit and recompiling psql:
regression=# \d regression.public.bit_defaults
Table "public.bit_defaults"
...
In v13 psql:
regression=# \d regression.public.bit_defaults
Table "public.bit_defaults"
...
It looks like before v13 any "datname" prefix was ignored.
But now it fails to show the table because it does:
WHERE c.relname OPERATOR(pg_catalog.~) '^(public.bit_defaults)$' COLLATE pg_catalog.default
AND n.nspname OPERATOR(pg_catalog.~) '^(regression)$' COLLATE pg_catalog.default
--
Justin
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 22:32 Mark Dilger <[email protected]>
parent: Justin Pryzby <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-11 22:32 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Robert Haas <[email protected]>; pgsql-hackers
> On Oct 11, 2021, at 3:26 PM, Justin Pryzby <[email protected]> wrote:
>
> It looks like before v13 any "datname" prefix was ignored.
The evidence so far suggests that something is broken in v14, but it is less clear to me what the appropriate behavior is. The v14 psql is rejecting even a correctly named database.schema.table, but v13 psql accepted lots.of.nonsense.schema.table, and neither of those seems at first glance to be correct. But perhaps there are good reasons for ignoring the nonsense prefixes?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 22:37 Tom Lane <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Tom Lane @ 2021-10-11 22:37 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
Mark Dilger <[email protected]> writes:
>> On Oct 11, 2021, at 3:04 PM, Tom Lane <[email protected]> wrote:
>> Doesn't work with the correct DB name, either:
>> regression=# \d regression.public.bit_defaults
>> Did not find any relation named "regression.public.bit_defaults".
> REL_13_STABLE appears to accept any amount of nonsense you like:
Yeah, I'm pretty sure that the old rule was to just ignore whatever
appeared in the database-name position. While we could tighten that
up to insist that it match the current DB's name, I'm not sure that
I see the point. There's no near-term prospect of doing anything
useful with some other DB's name there, so being more restrictive
seems like it'll probably break peoples' scripts to little purpose.
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 23:35 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-11 23:35 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
> On Oct 11, 2021, at 3:37 PM, Tom Lane <[email protected]> wrote:
>
>> REL_13_STABLE appears to accept any amount of nonsense you like:
>
> Yeah, I'm pretty sure that the old rule was to just ignore whatever
> appeared in the database-name position. While we could tighten that
> up to insist that it match the current DB's name, I'm not sure that
> I see the point. There's no near-term prospect of doing anything
> useful with some other DB's name there, so being more restrictive
> seems like it'll probably break peoples' scripts to little purpose.
You appear correct about the old behavior. It's unclear how intentional it was. There was a schema buffer and a name buffer, and while parsing the name, if a dot was encountered, the contents just parsed were copied into the schema buffer. If multiple dots were encountered, that had the consequence of blowing away the earlier ones.
But since we allow tables and schemas with dotted names in them, I'm uncertain what \d foo.bar.baz is really asking. That could be "foo.bar"."baz", or "foo"."bar"."baz", or "foo"."bar.baz", or even "public"."foo.bar.baz". The old behavior seems a bit dangerous. There may be tables with all those names, and the user may not have meant the one that we gave them.
The v14 code is no better. It just assumes that is "foo"."bar.baz". So (with debugging statements included):
foo=# create table "foo.bar.baz" (i integer);
CREATE TABLE
foo=# \d public.foo.bar.baz
Converting "public.foo.bar.baz"
GOT "^(public)$" . "^(foo.bar.baz)$"
Table "public.foo.bar.baz"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 23:41 Isaac Morland <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Isaac Morland @ 2021-10-11 23:41 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Tom Lane <[email protected]>; Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
On Mon, 11 Oct 2021 at 19:35, Mark Dilger <[email protected]>
wrote:
> But since we allow tables and schemas with dotted names in them, I'm
> uncertain what \d foo.bar.baz is really asking.
>
FWIW, it’s absolutely clear to me that "." is a special character which has
to be quoted in order to be in an identifier. In other words, a.b.c is
three identifiers separated by two period punctuation marks; what exactly
those periods mean is another question. If somebody uses periods in their
names, they have to quote those names just as if they used capital letters
etc.
But that's just my impression. I comment at all because I remember looking
at something to do with the grammar (I think I wanted to implement ALTER …
RENAME TO newschema.newname) and noticed that a database name could be
given in the syntax.
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-11 23:49 Tom Lane <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Tom Lane @ 2021-10-11 23:49 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
Mark Dilger <[email protected]> writes:
> But since we allow tables and schemas with dotted names in them, I'm uncertain what \d foo.bar.baz is really asking. That could be "foo.bar"."baz", or "foo"."bar"."baz", or "foo"."bar.baz", or even "public"."foo.bar.baz". The old behavior seems a bit dangerous. There may be tables with all those names, and the user may not have meant the one that we gave them.
You are attacking a straw man here. To use a period in an identifier,
you have to double-quote it; that's the same in SQL or \d.
regression=# create table "foo.bar" (f1 int);
CREATE TABLE
regression=# \d foo.bar
Did not find any relation named "foo.bar".
regression=# \d "foo.bar"
Table "public.foo.bar"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
f1 | integer | | |
According to a quick test, you did not manage to break that in v14.
> I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
I do not understand why you're even questioning that. The old
behavior had stood for a decade or two without complaints.
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 02:09 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 02:09 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
> On Oct 11, 2021, at 4:49 PM, Tom Lane <[email protected]> wrote:
>
> You are attacking a straw man here. To use a period in an identifier,
> you have to double-quote it; that's the same in SQL or \d.
That's a strange argument. If somebody gives an invalid identifier, we shouldn't assume they know the proper use of quotations. Somebody asking for a.b.c.d.e is clearly in the dark about something. Maybe it's the need to quote the "a.b" part separately from the "c.d.e" part, or maybe it's something else. There are lots of reasonable guesses about what they meant, and for backward compatibility reasons we define using the suffix d.e and ignoring the prefix a.b.c as the correct answer. That's a pretty arbitrary thing to do, but it has the advantage of being backwards compatible.
>> I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
>
> I do not understand why you're even questioning that. The old
> behavior had stood for a decade or two without complaints.
I find the backward compatibility argument appealing, but since we have clients that understand the full database.schema.relation format without ignoring the database portion, our client behavior is getting inconsistent. I'd like to leave the door open for someday supporting server.database.schema.relation format, too. I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 02:33 Peter Geoghegan <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Peter Geoghegan @ 2021-10-12 02:33 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Tom Lane <[email protected]>; Justin Pryzby <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers
On Mon, Oct 11, 2021 at 7:09 PM Mark Dilger
<[email protected]> wrote:
> I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
I suppose that I probably wouldn't have chosen this behavior in a
green field situation. But Hyrum's law tells us that there are bound
to be some number of users relying on it. I don't think that it's
worth inconveniencing those people without getting a clear benefit in
return.
Being lenient here just doesn't have much downside in practice, as
evidenced by the total lack of complaints about that lenience.
--
Peter Geoghegan
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 14:23 Robert Haas <[email protected]>
parent: Peter Geoghegan <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-12 14:23 UTC (permalink / raw)
To: Peter Geoghegan <[email protected]>; +Cc: Mark Dilger <[email protected]>; Tom Lane <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
On Mon, Oct 11, 2021 at 10:33 PM Peter Geoghegan <[email protected]> wrote:
> On Mon, Oct 11, 2021 at 7:09 PM Mark Dilger
> <[email protected]> wrote:
> > I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
>
> I suppose that I probably wouldn't have chosen this behavior in a
> green field situation. But Hyrum's law tells us that there are bound
> to be some number of users relying on it. I don't think that it's
> worth inconveniencing those people without getting a clear benefit in
> return.
>
> Being lenient here just doesn't have much downside in practice, as
> evidenced by the total lack of complaints about that lenience.
I find it kind of surprising to find everyone agreeing with this
argument. I mean, PostgreSQL users are often quick to criticize MySQL
for accepting 0000-00-00 as a date, because it isn't, and you
shouldn't accept garbage and do stuff with it as if it were valid
data. But by the same argument, accepting a database name that we know
is not correct as a request to show data in the current database seems
wrong to me.
I completely agree that somebody might be relying on the fact that \d
thisdb.someschema.sometable does something sensible when logged into
thisdb, but surely no user is relying on \d
jgldslghksdghjsgkhsdgjhskg.someschema.sometable is going to just
ignore the leading gibberish. Nor do I understand why we'd want to
ignore the leading gibberish. Saying, as Tom did, that nobody has
complained about that behavior is just another way of saying that
nobody tested it. Surely if someone had, it wouldn't be like this.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 14:30 Tom Lane <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Tom Lane @ 2021-10-12 14:30 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
Robert Haas <[email protected]> writes:
> On Mon, Oct 11, 2021 at 10:33 PM Peter Geoghegan <[email protected]> wrote:
>> Being lenient here just doesn't have much downside in practice, as
>> evidenced by the total lack of complaints about that lenience.
> I find it kind of surprising to find everyone agreeing with this
> argument.
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 14:37 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 14:37 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Robert Haas <[email protected]>; Peter Geoghegan <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
> On Oct 12, 2021, at 7:30 AM, Tom Lane <[email protected]> wrote:
>
> If the behavior v14 had implemented were "throw an error if the
> first word doesn't match the current database name", perhaps nobody
> would have questioned it. But that's not what we have. It's fairly
> clear that neither you nor Mark thought very much about this case,
> let alone tested it. Given that, I am not very pleased that you
> are retroactively trying to justify breaking it by claiming that
> it was already broken. It's been that way since 7.3 implemented
> schemas, more or less, and nobody's complained about it. Therefore
> I see little argument for changing that behavior. Changing it in
> an already-released branch is especially suspect.
I completely agree that we need to fix this. My question was only whether "fix" means to make it accept database.schema.table or whether it means to accept any.prefix.at.all.schema.table. It sounds like more people like the latter, so I'll go with that unless this debate rages on and a different conclusion is reached.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 14:40 Robert Haas <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 2 replies; 56+ messages in thread
From: Robert Haas @ 2021-10-12 14:40 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <[email protected]> wrote:
> If the behavior v14 had implemented were "throw an error if the
> first word doesn't match the current database name", perhaps nobody
> would have questioned it. But that's not what we have. It's fairly
> clear that neither you nor Mark thought very much about this case,
> let alone tested it. Given that, I am not very pleased that you
> are retroactively trying to justify breaking it by claiming that
> it was already broken. It's been that way since 7.3 implemented
> schemas, more or less, and nobody's complained about it. Therefore
> I see little argument for changing that behavior. Changing it in
> an already-released branch is especially suspect.
Oh, give me a break. The previous behavior obviously hasn't been
tested either, and is broken on its face. If someone *had* complained
about it, I imagine you would have promptly fixed it and likely
back-patched the fix, probably in under 24 hours from the time of the
report. I find it difficult to take seriously the contention that
anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
like \d foo.bar, or that they would even prefer that behavior over an
error message. You're carefully avoiding addressing that question in
favor of having a discussion of backward compatibility, but a better
term for what we're talking about here would be bug-compatibility.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 15:19 Stephen Frost <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 2 replies; 56+ messages in thread
From: Stephen Frost @ 2021-10-12 15:19 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
Greetings,
* Robert Haas ([email protected]) wrote:
> On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <[email protected]> wrote:
> > If the behavior v14 had implemented were "throw an error if the
> > first word doesn't match the current database name", perhaps nobody
> > would have questioned it. But that's not what we have. It's fairly
> > clear that neither you nor Mark thought very much about this case,
> > let alone tested it. Given that, I am not very pleased that you
> > are retroactively trying to justify breaking it by claiming that
> > it was already broken. It's been that way since 7.3 implemented
> > schemas, more or less, and nobody's complained about it. Therefore
> > I see little argument for changing that behavior. Changing it in
> > an already-released branch is especially suspect.
>
> Oh, give me a break. The previous behavior obviously hasn't been
> tested either, and is broken on its face. If someone *had* complained
> about it, I imagine you would have promptly fixed it and likely
> back-patched the fix, probably in under 24 hours from the time of the
> report. I find it difficult to take seriously the contention that
> anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
> like \d foo.bar, or that they would even prefer that behavior over an
> error message. You're carefully avoiding addressing that question in
> favor of having a discussion of backward compatibility, but a better
> term for what we're talking about here would be bug-compatibility.
I tend to agree with Robert on this particular case. Accepting random
nonsense there isn't a feature or something which really needs to be
preserved. For my 2c, I would hope that one day we will be able to
accept other database names there and if that happens, what then? We'd
"break" these cases anyway. Better to be clear that such nonsense isn't
intended to be accepted and clean that up. I do think it'd be good to
accept the current database name there as that's reasonable.
Thanks,
Stephen
Attachments:
[application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 16:44 Peter Geoghegan <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Peter Geoghegan @ 2021-10-12 16:44 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Tom Lane <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 7:41 AM Robert Haas <[email protected]> wrote:
> Oh, give me a break. The previous behavior obviously hasn't been
> tested either, and is broken on its face. If someone *had* complained
> about it, I imagine you would have promptly fixed it and likely
> back-patched the fix, probably in under 24 hours from the time of the
> report.
You're asking us to imagine a counterfactual. But this counterfactual
bug report would have to describe a real practical problem. The
details would matter. It's reasonable to suppose that we haven't seen
such a bug report for a reason.
I can't speak for Tom. My position on this is that it's better to
leave it alone at this time, given the history, and the lack of
complaints from users.
> I find it difficult to take seriously the contention that
> anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
> like \d foo.bar, or that they would even prefer that behavior over an
> error message. You're carefully avoiding addressing that question in
> favor of having a discussion of backward compatibility, but a better
> term for what we're talking about here would be bug-compatibility.
Let's assume that it is bug compatibility. Is that intrinsically a bad thing?
--
Peter Geoghegan
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 16:52 Vik Fearing <[email protected]>
parent: Stephen Frost <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Vik Fearing @ 2021-10-12 16:52 UTC (permalink / raw)
To: Stephen Frost <[email protected]>; Robert Haas <[email protected]>; +Cc: Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
On 10/12/21 5:19 PM, Stephen Frost wrote:
> Greetings,
>
> * Robert Haas ([email protected]) wrote:
>> On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <[email protected]> wrote:
>>> If the behavior v14 had implemented were "throw an error if the
>>> first word doesn't match the current database name", perhaps nobody
>>> would have questioned it. But that's not what we have. It's fairly
>>> clear that neither you nor Mark thought very much about this case,
>>> let alone tested it. Given that, I am not very pleased that you
>>> are retroactively trying to justify breaking it by claiming that
>>> it was already broken. It's been that way since 7.3 implemented
>>> schemas, more or less, and nobody's complained about it. Therefore
>>> I see little argument for changing that behavior. Changing it in
>>> an already-released branch is especially suspect.
>>
>> Oh, give me a break. The previous behavior obviously hasn't been
>> tested either, and is broken on its face. If someone *had* complained
>> about it, I imagine you would have promptly fixed it and likely
>> back-patched the fix, probably in under 24 hours from the time of the
>> report. I find it difficult to take seriously the contention that
>> anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
>> like \d foo.bar, or that they would even prefer that behavior over an
>> error message. You're carefully avoiding addressing that question in
>> favor of having a discussion of backward compatibility, but a better
>> term for what we're talking about here would be bug-compatibility.
>
> I tend to agree with Robert on this particular case. Accepting random
> nonsense there isn't a feature or something which really needs to be
> preserved. For my 2c, I would hope that one day we will be able to
> accept other database names there and if that happens, what then? We'd
> "break" these cases anyway. Better to be clear that such nonsense isn't
> intended to be accepted and clean that up. I do think it'd be good to
> accept the current database name there as that's reasonable.
I am going to throw my hat in with Robert and Stephen, too. At least
for 15 if we don't want to change this behavior in back branches.
--
Vik Fearing
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 16:57 Justin Pryzby <[email protected]>
parent: Stephen Frost <[email protected]>
1 sibling, 2 replies; 56+ messages in thread
From: Justin Pryzby @ 2021-10-12 16:57 UTC (permalink / raw)
To: Stephen Frost <[email protected]>; +Cc: Robert Haas <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
I understand Tom's position to be that the behavior should be changed back,
since it was 1) unintentional; and 2) breaks legitimate use (when the datname
matches current_database).
I think there's an easy answer here that would satisfy everyone; two patches:
0001 to fix the unintentional behavior change;
0002 to reject garbage input: anything with more than 3 dot-separated
components, or with 3 components where the first doesn't match
current_database.
0001 would be backpatched to v14.
If it turns out there's no consensus on 0002, or if it were really hard for
some reason, or (more likely) nobody went to the bother to implement it this
year, then that's okay.
I would prefer if it errored if the datname didn't match the current database.
After all, it would've helped me to avoid making a confusing problem report.
--
Justin
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 17:01 Robert Haas <[email protected]>
parent: Peter Geoghegan <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-12 17:01 UTC (permalink / raw)
To: Peter Geoghegan <[email protected]>; +Cc: Tom Lane <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 12:44 PM Peter Geoghegan <[email protected]> wrote:
> You're asking us to imagine a counterfactual. But this counterfactual
> bug report would have to describe a real practical problem.
Yes. And I think this one should be held to the same standard: \d
mydb.myschema.mytable not working is potentially a real, practical
problem. \d sdlgkjdss.dsgkjsk.sdgskldjgds.myschema.mytable not working
isn't.
> Let's assume that it is bug compatibility. Is that intrinsically a bad thing?
Well my view is that having the same bugs is better than having
different ones, but fixing the bugs is superior to either.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 17:03 Robert Haas <[email protected]>
parent: Justin Pryzby <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-12 17:03 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <[email protected]> wrote:
> I think there's an easy answer here that would satisfy everyone; two patches:
> 0001 to fix the unintentional behavior change;
> 0002 to reject garbage input: anything with more than 3 dot-separated
> components, or with 3 components where the first doesn't match
> current_database.
>
> 0001 would be backpatched to v14.
>
> If it turns out there's no consensus on 0002, or if it were really hard for
> some reason, or (more likely) nobody went to the bother to implement it this
> year, then that's okay.
This might work, but I fear that 0001 would end up being substantially
more complicated than a combined patch that solves both problems
together.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 17:18 Mark Dilger <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 17:18 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 12, 2021, at 10:03 AM, Robert Haas <[email protected]> wrote:
>
> On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <[email protected]> wrote:
>> I think there's an easy answer here that would satisfy everyone; two patches:
>> 0001 to fix the unintentional behavior change;
>> 0002 to reject garbage input: anything with more than 3 dot-separated
>> components, or with 3 components where the first doesn't match
>> current_database.
>>
>> 0001 would be backpatched to v14.
>>
>> If it turns out there's no consensus on 0002, or if it were really hard for
>> some reason, or (more likely) nobody went to the bother to implement it this
>> year, then that's okay.
>
> This might work, but I fear that 0001 would end up being substantially
> more complicated than a combined patch that solves both problems
> together.
Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is. (It passes check-world and I've read it over once, but I'm not ready to stand by this as correct quite yet.) I need to add a regression test to make sure this behavior is not accidentally changed in the future, and will repost after doing so.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] string_utils.patch.WIP (5.6K, ../../[email protected]/2-string_utils.patch.WIP)
download
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 17:38 Mark Dilger <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 17:38 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Peter Geoghegan <[email protected]>; Tom Lane <[email protected]>; Justin Pryzby <[email protected]>; pgsql-hackers
> On Oct 12, 2021, at 10:01 AM, Robert Haas <[email protected]> wrote:
>
> On Tue, Oct 12, 2021 at 12:44 PM Peter Geoghegan <[email protected]> wrote:
>> You're asking us to imagine a counterfactual. But this counterfactual
>> bug report would have to describe a real practical problem.
>
> Yes. And I think this one should be held to the same standard: \d
> mydb.myschema.mytable not working is potentially a real, practical
> problem. \d sdlgkjdss.dsgkjsk.sdgskldjgds.myschema.mytable not working
> isn't.
I favor restoring the v13 behavior, but I don't think \d mydb.myschema.mytable was ever legitimate. You got exactly the same results with \d nosuchdb.myschema.mytable, meaning the user was given a false sense of security that the database name was being used to fetch the definition from the database they specified.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 17:54 Robert Haas <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-12 17:54 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 1:18 PM Mark Dilger
<[email protected]> wrote:
> Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is.
I guess that's not that bad. Why did we end up with the behavior that
the current comment describes this way?
"(Additional dots in the name portion are not treated as special.)"
I thought there was some reason why it needed to work that way.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 19:26 Mark Dilger <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 19:26 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 12, 2021, at 10:54 AM, Robert Haas <[email protected]> wrote:
>
> On Tue, Oct 12, 2021 at 1:18 PM Mark Dilger
> <[email protected]> wrote:
>> Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is.
>
> I guess that's not that bad. Why did we end up with the behavior that
> the current comment describes this way?
>
> "(Additional dots in the name portion are not treated as special.)"
>
> I thought there was some reason why it needed to work that way.
We're not talking about the parsing of string literals, but rather about the parsing of shell-style patterns. The primary caller of this logic is processSQLNamePattern(), which expects only a relname or a (schema,relname) pair, not database names nor anything else.
The pattern myschema.my.*table is not a three-part pattern, but a two part pattern, with a literal schema name and a relation name pattern. In v14 it can be seen to work as follows:
\d pg_toast.pg_.oast_2619
TOAST table "pg_toast.pg_toast_2619"
Column | Type
------------+---------
chunk_id | oid
chunk_seq | integer
chunk_data | bytea
Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
\d pg_toast.pg_.*_2619
TOAST table "pg_toast.pg_toast_2619"
Column | Type
------------+---------
chunk_id | oid
chunk_seq | integer
chunk_data | bytea
Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
In v13, neither of those matched anything (which is defensible, I guess) but the following did match, which is really nuts:
+CREATE SCHEMA g_;
+CREATE TABLE g_.oast_2619 (i integer);
+\d pg_toast..g_.oast_2619
+ Table "g_.oast_2619"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ i | integer | | |
The behavior Justin reported in the original complaint was \d regresion.public.bit_defaults, which gets handled as schema =~ /^(regresion)$/ and relname =~ /^(public.bit_defaults)$/. That gives no results for him, but I tend to think no results is defensible.
Apparently, this behavior breaks an old bug, and we need to restore the old bug and then debate this behavioral change for v15. I'd rather people had engaged in the discussion about this feature during the v14 cycle, since this patch was posted and reviewed on -hackers, and I don't recall anybody complaining about it.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-12 21:21 Mark Dilger <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-10-12 21:21 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 12, 2021, at 10:18 AM, Mark Dilger <[email protected]> wrote:
>
> Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is. (It passes check-world and I've read it over once, but I'm not ready to stand by this as correct quite yet.) I need to add a regression test to make sure this behavior is not accidentally changed in the future, and will repost after doing so.
I wasn't thinking critically enough about how psql handles \d when I accepted Justin's initial characterization of the bug. The psql client has never thought about the stuff to the left of the schema name as a database name, even if some users thought about it that way. It also doesn't think about the pattern as a literal string.
The psql client's interpretation of the pattern is a bit of a chimera, following shell glob patterns for some things and POSIX regex rules for others. The reason for that is shell glob stuff gets transliterated into the corresponding POSIX syntax, but non-shell-glob stuff is left in tact, with the one outlier being dots, which have a very special interpretation. The interpretation of a dot as meaning "match one character" is not a shell glob rule but a regex one, and one that psql never supported because it split the pattern on all dots and threw away stuff to the left. There was therefore never an opportunity for an unquoted dot to make it through to the POSIX regular expression for processing. For other regex type stuff, it happily passed it through to the POSIX regex, so that the following examples work even though they contain non-shell-glob regex stuff:
v13=# create table ababab (i integer);
CREATE TABLE
v13=# \dt (ab){3}
List of relations
Schema | Name | Type | Owner
--------+--------+-------+-------------
public | ababab | table | mark.dilger
(1 row)
v13=# \dt pg_catalog.pg_clas{1,2}
List of relations
Schema | Name | Type | Owner
------------+----------+-------+-------------
pg_catalog | pg_class | table | mark.dilger
v13=# \dt pg_catalog.pg_[am]{1,3}
List of relations
Schema | Name | Type | Owner
------------+-------+-------+-------------
pg_catalog | pg_am | table | mark.dilger
(1 row)
Splitting the pattern on all the dots and throwing away any additional leftmost fields is a bug, and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 13:24 Robert Haas <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Robert Haas @ 2021-10-13 13:24 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 5:21 PM Mark Dilger
<[email protected]> wrote:
> I wasn't thinking critically enough about how psql handles \d when I accepted Justin's initial characterization of the bug. The psql client has never thought about the stuff to the left of the schema name as a database name, even if some users thought about it that way. It also doesn't think about the pattern as a literal string.
I agree.
> The psql client's interpretation of the pattern is a bit of a chimera, following shell glob patterns for some things and POSIX regex rules for others.
Yes. And that's pretty weird, but it's long-established precedent so
we have to deal with it.
> Splitting the pattern on all the dots and throwing away any additional leftmost fields is a bug, ...
I also agree with you right up to here.
> and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
But not this part, or at least not entirely.
If we pass the dots through to the POSIX regular expression, we can
only do that either for the table name or the schema name, not both -
either the first or last dot must mark the boundary between the two.
That means that you can't use all the same regexy things for one as
you can for the other, which is a strange system. I knew that your
patch made it do that, and I committed it that way because I didn't
think it really mattered, and also because the whole system is already
pretty strange, so what's one more bit of strangeness?
I think there are at least 3 defensible behaviors here:
1. Leave it like it is. If there is more than one dot, the extra ones
are part of one of the regex-glob thingies.
2. If there is more than one dot, error! Tell the user they messed up.
3. If there are exactly two dots, treat it as db-schema-user. Accept
it if the dbname matches the current db, and otherwise say we can't
access the named db. If there are more than two dots, then (a) it's an
error as in (2) or (b) the extra ones become part of the regex-glob
thingies as in (2).
The thing that's unprincipled about (3) is that we can't support a
regexp-glob thingy there -- we can only test for a literal string
match. And I already said what I thought was wrong with (1). But none
of these are horrible, and I don't think it really matters which one
we adopt. I don't even know if I can really rank the choices I just
listed against each other. Before I was arguing for (3a) but I'm not
sure I actually like that one particularly better.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 14:40 Mark Dilger <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-10-13 14:40 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 13, 2021, at 6:24 AM, Robert Haas <[email protected]> wrote:
>
>> and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
>
> But not this part, or at least not entirely.
>
> If we pass the dots through to the POSIX regular expression, we can
> only do that either for the table name or the schema name, not both -
Agreed.
> either the first or last dot must mark the boundary between the two.
> That means that you can't use all the same regexy things for one as
> you can for the other, which is a strange system.
The closest analogy is how regular expressions consider \1 \2 .. \9 as backreferences, but \10 \11 ... are dependent on context: "A multi-digit sequence not starting with a zero is taken as a back reference if it comes after a suitable subexpression (i.e., the number is in the legal range for a back reference), and otherwise is taken as octal." Taking a dot as a separator if it can be taken that way, and as a regex character otherwise, is not totally out of line with existing precedent. On the other hand, the backreference vs. octal precedent is not one I particularly like.
> I knew that your
> patch made it do that, and I committed it that way because I didn't
> think it really mattered, and also because the whole system is already
> pretty strange, so what's one more bit of strangeness?
>
> I think there are at least 3 defensible behaviors here:
>
> 1. Leave it like it is. If there is more than one dot, the extra ones
> are part of one of the regex-glob thingies.
>
> 2. If there is more than one dot, error! Tell the user they messed up.
I don't like the backward compatibility issues with this one. Justin's use of database.schema.relname will work up until v14 (by throwing away the database part), then draw an error in v14, then (assuming we support the database portion in v15 onward) start working again.
> 3. If there are exactly two dots, treat it as db-schema-user. Accept
> it if the dbname matches the current db, and otherwise say we can't
> access the named db. If there are more than two dots, then (a) it's an
> error as in (2) or (b) the extra ones become part of the regex-glob
> thingies as in (2).
3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 15:43 Robert Haas <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-13 15:43 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Wed, Oct 13, 2021 at 10:40 AM Mark Dilger
<[email protected]> wrote:
> 3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
>
> 3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
Well, like I said, we can't treat a part that's purportedly a DB name
as a pattern, so when connected to db1, I presume the command \d
db?.foo.bar would have to behave just like \d
dskjlglsghdksgdjkshg.foo.bar. I suppose technically I'm wrong: db?
could be matched against the list of database names as a pattern, and
then we could complain only if it doesn't match exactly and only the
current DB. But I don't like adding a bunch of extra code to
accomplish nothing useful, so if we're going to match it all I think
it should just strcmp().
But I'm still not sure what the best thing to do overall is here.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 16:46 Robert Haas <[email protected]>
parent: Justin Pryzby <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-13 16:46 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <[email protected]> wrote:
> I would prefer if it errored if the datname didn't match the current database.
> After all, it would've helped me to avoid making a confusing problem report.
How would you have felt if it had said something like:
error: argument to \d should be of the form
[schema-name-pattern.]relation-name-pattern
Would that have been better or worse for you than accepting a third
part of the pattern as a database name if and only if it matched the
current database name exactly?
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 16:54 Justin Pryzby <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Justin Pryzby @ 2021-10-13 16:54 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
On Wed, Oct 13, 2021 at 12:46:27PM -0400, Robert Haas wrote:
> On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <[email protected]> wrote:
> > I would prefer if it errored if the datname didn't match the current database.
> > After all, it would've helped me to avoid making a confusing problem report.
>
> How would you have felt if it had said something like:
>
> error: argument to \d should be of the form
> [schema-name-pattern.]relation-name-pattern
>
> Would that have been better or worse for you than accepting a third
> part of the pattern as a database name if and only if it matched the
> current database name exactly?
I don't normally type \d a.b.c. I think I copied it out of a log message and
pasted it, and didn't even really know or expect it to work without removing
the datname prefix. After it worked, I noticed a short while later when using
the pg14 client that it had stopped working.
It seems unfortunate if names from log messages qualified with datname were now
rejected. Like this one:
| automatic analyze of table "ts.child.cdrs_2021_10_12"...
--
Justin
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 17:05 Robert Haas <[email protected]>
parent: Justin Pryzby <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Robert Haas @ 2021-10-13 17:05 UTC (permalink / raw)
To: Justin Pryzby <[email protected]>; +Cc: Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
On Wed, Oct 13, 2021 at 12:54 PM Justin Pryzby <[email protected]> wrote:
> It seems unfortunate if names from log messages qualified with datname were now
> rejected. Like this one:
>
> | automatic analyze of table "ts.child.cdrs_2021_10_12"...
That's a good argument, IMHO.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 18:55 Stephen Frost <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Stephen Frost @ 2021-10-13 18:55 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; Mark Dilger <[email protected]>; pgsql-hackers
Greetings,
* Robert Haas ([email protected]) wrote:
> On Wed, Oct 13, 2021 at 12:54 PM Justin Pryzby <[email protected]> wrote:
> > It seems unfortunate if names from log messages qualified with datname were now
> > rejected. Like this one:
> >
> > | automatic analyze of table "ts.child.cdrs_2021_10_12"...
>
> That's a good argument, IMHO.
Agreed.
Thanks,
Stephen
Attachments:
[application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-13 20:43 Mark Dilger <[email protected]>
parent: Robert Haas <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-13 20:43 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 13, 2021, at 8:43 AM, Robert Haas <[email protected]> wrote:
>
> On Wed, Oct 13, 2021 at 10:40 AM Mark Dilger
> <[email protected]> wrote:
>> 3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
>>
>> 3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
>
> Well, like I said, we can't treat a part that's purportedly a DB name
> as a pattern, so when connected to db1, I presume the command \d
> db?.foo.bar would have to behave just like \d
> dskjlglsghdksgdjkshg.foo.bar. I suppose technically I'm wrong: db?
> could be matched against the list of database names as a pattern, and
> then we could complain only if it doesn't match exactly and only the
> current DB. But I don't like adding a bunch of extra code to
> accomplish nothing useful, so if we're going to match it all I think
> it should just strcmp().
>
> But I'm still not sure what the best thing to do overall is here.
The issue of name parsing impacts pg_dump and pg_dumpall, also. Consider what happens with:
pg_dump -t production.critical.secrets > secrets.dump
dropdb production
In v13, if your default database is "testing", and database "testing" has the same schemas and tables (but not data) as production, you are unhappy. You just dumped a copy of your test data and blew away the production data.
You could end up unhappy in v14, if database "testing" has a schema named "production" and a table that matches the pattern /^critical.secrets$/, but otherwise, you'll get an error from pg_dump, "pg_dump: error: no matching tables were found". Neither behavior seems correct.
The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
For psql and pg_dump, I'm tempted to restrict the database portion (if not quoted) to neither contain shell glob characters nor POSIX regex characters, and return an error code if any are found, so that the clients can raise an appropriate error to the user.
In psql, this proposal would result in no tables matching \d wrongdb.schema.table, which would differ from v13's behavior. You wouldn't get an error about having specified the wrong database. You'd just get no matching relations. \d ??db??.schema.table would complain about the db portion being a pattern. \d "??db??".schema.table would work, assuming you're connected to a database literally named ??db??
In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
In pg_dump, the -t wrongdb.schema.table would match nothing and give the familiar error "pg_dump: error: no matching tables were found". pg_dump -t too.many.dotted.names would give a different error about too many parts. pg_dump -t db??.foo.bar would give an error about the database needing to be a literal name rather than a pattern.
I don't like your proposal to use a strcmp() rather than a pg_catalog.= match, because it diverges from how the rest of the pattern is treated, including in how encoding settings might interact with the name, needing to be executed on the client side rather than in the server where the rest of the name resolution is happening.
Does this sound like a workable proposal?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-14 12:54 Robert Haas <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Robert Haas @ 2021-10-14 12:54 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Wed, Oct 13, 2021 at 4:43 PM Mark Dilger
<[email protected]> wrote:
> The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
I agree with matching using OPERATOR(pg_catalog.=) but I think it
should be an error, not a silently-return-nothing case.
> In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
Those things sound good.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-10-20 14:15 Mark Dilger <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 2 replies; 56+ messages in thread
From: Mark Dilger @ 2021-10-20 14:15 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Oct 13, 2021, at 1:43 PM, Mark Dilger <[email protected]> wrote:
>
> The issue of name parsing impacts pg_dump and pg_dumpall, also. Consider what happens with:
>
> pg_dump -t production.critical.secrets > secrets.dump
> dropdb production
>
> In v13, if your default database is "testing", and database "testing" has the same schemas and tables (but not data) as production, you are unhappy. You just dumped a copy of your test data and blew away the production data.
>
> You could end up unhappy in v14, if database "testing" has a schema named "production" and a table that matches the pattern /^critical.secrets$/, but otherwise, you'll get an error from pg_dump, "pg_dump: error: no matching tables were found". Neither behavior seems correct.
With the attached patch, this scenario results in a "cross-database references are not implemented" error.
> The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
I ultimately went with your strcmp idea rather than OPERATOR(pg_catalog.=), as rejecting the database name as part of the query complicates the calling convention for no apparent benefit. I had been concerned about database names that were collation-wise equal but byte-wise unequal, but it seems we already treat those as distinct database names, so my concern was unnecessary. We already use strcmp on database names from frontend clients (fe_utils/parallel_slots.c, psql/prompt.c, pg_amcheck.c, pg_dump.c, pg_upgrade/relfilenode.c), from libpq (libpq/hba.c) and from the backend (commands/dbcommands.c, init/postinit.c).
I tried testing how this plays out by handing `createdb` the name é (U+00E9 "LATIN SMALL LETTER E WITH ACCUTE") and then again the name é (U+0065 "LATIN SMALL LETTER E" followed by U+0301 "COMBINING ACCUTE ACCENT".) That results in two distinct databases, not an error about a duplicate database name:
# select oid, datname, datdba, encoding, datcollate, datctype from pg_catalog.pg_database where datname IN ('é', 'é');
oid | datname | datdba | encoding | datcollate | datctype
-------+---------+--------+----------+-------------+-------------
37852 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
37855 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
(2 rows)
But that doesn't seem to prove much, as other tools in my locale don't treat those as equal either. (Testing with perl's "eq" operator, they compare as distinct.) I expected to find regression tests providing better coverage for this somewhere, but did not. Anybody know more about it?
> For psql and pg_dump, I'm tempted to restrict the database portion (if not quoted) to neither contain shell glob characters nor POSIX regex characters, and return an error code if any are found, so that the clients can raise an appropriate error to the user.
With the patch, using pattern characters in an unquoted database portion results in a "database name must be literal" error. Using them in a quoted database name is allowed, but unless you are connected to a database that literally equals that name, you will get a "cross-database references are not implemented" error.
> In psql, this proposal would result in no tables matching \d wrongdb.schema.table, which would differ from v13's behavior. You wouldn't get an error about having specified the wrong database. You'd just get no matching relations. \d ??db??.schema.table would complain about the db portion being a pattern. \d "??db??".schema.table would work, assuming you're connected to a database literally named ??db??
With the patch, psql will treat \d wrongdb.schema.table as a "cross-database references are not implemented" error.
> In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
The patch implements this.
> In pg_dump, the -t wrongdb.schema.table would match nothing and give the familiar error "pg_dump: error: no matching tables were found".
With the patch, pg_dump instead gives a "cross-database references are not implemented" error.
> pg_dump -t too.many.dotted.names would give a different error about too many parts.
With the patch, pg_dump instead gives a "improper qualified name (too many dotted names)" error.
> pg_dump -t db??.foo.bar would give an error about the database needing to be a literal name rather than a pattern.
With the patch, pg_dump gives a "database name must be literal" error. This is the only new error message in the patch, which puts a burden on translators, but I didn't see any existing message that would serve. Suggestions welcome.
> I don't like your proposal to use a strcmp() rather than a pg_catalog.= match, because it diverges from how the rest of the pattern is treated, including in how encoding settings might interact with the name, needing to be executed on the client side rather than in the server where the rest of the name resolution is happening.
Recanted, as discussed above.
The patch only changes the behavior of pg_amcheck in that it now rejects patterns with too many parts. Using database patterns was and remains legal for this tool.
The patch changes nothing about reindexdb. That's a debatable design choice, but reindexdb doesn't use string_utils's processSQLNamePattern() function as the other tools do, nor does its documentation reference psql's #APP-PSQL-PATTERNS documentation. It's --schema option only takes literal names.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch (76.1K, ../../[email protected]/2-v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch)
download | inline diff:
From 3136d4fe27052146782f28bf176898277306abc0 Mon Sep 17 00:00:00 2001
From: Mark Dilger <[email protected]>
Date: Fri, 15 Oct 2021 09:07:15 -0700
Subject: [PATCH v1] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 510 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1010 insertions(+), 235 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..7c00a65966 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3605,14 +3605,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index e30c1cc546..800203886f 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ed8ed2f266..dc577a6d1d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -167,6 +167,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1341,10 +1344,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1388,10 +1407,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1435,10 +1461,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1481,6 +1514,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1496,9 +1533,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1519,6 +1568,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..42820adcac 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1438,10 +1438,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..126be5f4e4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3588,7 +3588,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3761,6 +3761,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..8a1aa8ca88 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -45,6 +45,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -121,9 +127,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -189,9 +197,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -276,9 +286,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -639,9 +651,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -663,10 +677,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -804,11 +820,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -958,10 +976,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -986,10 +1006,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -1067,8 +1089,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1218,9 +1242,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1295,11 +1321,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1371,9 +1399,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1393,9 +1423,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/*
@@ -1421,9 +1453,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
}
/*
@@ -1450,9 +1484,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
}
/* Rule descriptions (ignore rules for views) */
@@ -1472,9 +1508,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1492,9 +1530,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1548,9 +1588,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3783,8 +3825,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
}
else
{
@@ -3798,8 +3842,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
" ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
"\nFROM pg_catalog.pg_user u\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "u.usename", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "u.usename", NULL, NULL,
+ NULL, 1))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3934,10 +3980,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -4152,9 +4201,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4367,9 +4418,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4447,8 +4500,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4537,9 +4592,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4611,9 +4668,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4678,8 +4737,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4770,10 +4831,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4879,17 +4942,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4986,9 +5053,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5044,10 +5113,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5105,9 +5176,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5146,9 +5219,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5363,9 +5438,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5434,9 +5511,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5494,9 +5573,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5536,9 +5617,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5724,8 +5807,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5806,8 +5891,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5867,8 +5954,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5944,9 +6033,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6000,10 +6091,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6049,10 +6142,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6134,6 +6229,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -6185,9 +6335,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6252,9 +6404,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6442,9 +6596,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6550,15 +6706,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6622,8 +6782,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6635,10 +6797,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6716,13 +6880,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6800,12 +6968,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..c3ee191369 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -819,6 +819,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -827,16 +829,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -848,16 +855,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -870,25 +885,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -896,7 +911,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -904,16 +919,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -929,8 +944,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -965,32 +982,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1003,6 +1028,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1013,32 +1040,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1050,6 +1093,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1074,25 +1119,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index caafb97d29..d271cbc4bf 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -54,10 +54,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 930ce8597a..d770f980b3 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5278,3 +5278,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index e9d504baf2..042237f3ee 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1304,3 +1304,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-03 19:07 Tom Lane <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Tom Lane @ 2021-11-03 19:07 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
Mark Dilger <[email protected]> writes:
> [ v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch ]
This needs a rebase over the recent renaming of our Perl test modules.
(Per the cfbot, so do several of your other pending patches.)
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-03 21:52 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-11-03 21:52 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Nov 3, 2021, at 12:07 PM, Tom Lane <[email protected]> wrote:
>
> Mark Dilger <[email protected]> writes:
>> [ v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch ]
>
> This needs a rebase over the recent renaming of our Perl test modules.
> (Per the cfbot, so do several of your other pending patches.)
>
> regards, tom lane
Thanks for calling my attention to it.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] v2-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch (76.2K, ../../[email protected]/2-v2-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch)
download | inline diff:
From 524d32cf08ff85b32a2cd596b867279395a2cb96 Mon Sep 17 00:00:00 2001
From: Mark Dilger <[email protected]>
Date: Wed, 3 Nov 2021 13:42:30 -0700
Subject: [PATCH v2] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 510 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1010 insertions(+), 235 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 48248f750e..b2bcc7e296 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3605,14 +3605,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 513a18d671..5c38916a70 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6..e143ee4efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -167,6 +167,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1341,10 +1344,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1388,10 +1407,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1435,10 +1461,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1481,6 +1514,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1496,9 +1533,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1519,6 +1568,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..42820adcac 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1438,10 +1438,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d293f52b05..ddc103ea9c 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3637,7 +3637,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3810,6 +3810,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412e..f13fc356f7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -45,6 +45,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -121,9 +127,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -189,9 +197,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -276,9 +286,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -639,9 +651,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -663,10 +677,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -804,11 +820,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -958,10 +976,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -986,10 +1006,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -1067,8 +1089,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1218,9 +1242,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1295,11 +1321,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1371,9 +1399,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1393,9 +1423,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/*
@@ -1421,9 +1453,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
}
/*
@@ -1450,9 +1484,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
}
/* Rule descriptions (ignore rules for views) */
@@ -1472,9 +1508,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1492,9 +1530,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1548,9 +1588,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3806,8 +3848,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
}
else
{
@@ -3821,8 +3865,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
" ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
"\nFROM pg_catalog.pg_user u\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "u.usename", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "u.usename", NULL, NULL,
+ NULL, 1))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3957,10 +4003,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -4175,9 +4224,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4390,9 +4441,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4470,8 +4523,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4560,9 +4615,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4634,9 +4691,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4701,8 +4760,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4793,10 +4854,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4902,17 +4965,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -5009,9 +5076,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5069,10 +5138,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5193,9 +5264,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5234,9 +5307,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5451,9 +5526,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5522,9 +5599,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5582,9 +5661,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5624,9 +5705,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5812,8 +5895,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5894,8 +5979,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5955,8 +6042,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6032,9 +6121,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6088,10 +6179,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6137,10 +6230,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6222,6 +6317,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -6273,9 +6423,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6378,9 +6530,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6562,9 +6716,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6670,15 +6826,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6742,8 +6902,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6755,10 +6917,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6836,13 +7000,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6920,12 +7088,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..c3ee191369 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -819,6 +819,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -827,16 +829,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -848,16 +855,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -870,25 +885,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -896,7 +911,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -904,16 +919,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -929,8 +944,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -965,32 +982,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1003,6 +1028,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1013,32 +1040,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1050,6 +1093,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1074,25 +1119,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index caafb97d29..d271cbc4bf 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -54,10 +54,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 930ce8597a..d770f980b3 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5278,3 +5278,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index e9d504baf2..042237f3ee 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1304,3 +1304,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-04 13:37 Hamlin, Garick L <[email protected]>
parent: Robert Haas <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Hamlin, Garick L @ 2021-11-04 13:37 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Wed, Oct 13, 2021 at 09:24:53AM -0400, Robert Haas wrote:
> > Splitting the pattern on all the dots and throwing away any additional
> > leftmost fields is a bug, ...
>
> I also agree with you right up to here.
>
> > and when you stop doing that, passing additional dots through to the POSIX
> > regular expression for processing is the most natural thing to do. This
> > is, in fact, how v14 works. It is a bit debatable whether treating the
> > first dot as a separator and the additional dots as stuff to be passed
> > through is the right thing, so we could call the v14 behavior a
> > mis-feature, but it's not as clearcut as the discussion upthread suggested.
> > Reverting to v13 behavior seems wrong, but I'm now uncertain how to
> > proceed.
>
> But not this part, or at least not entirely.
>
> If we pass the dots through to the POSIX regular expression, we can
> only do that either for the table name or the schema name, not both -
> either the first or last dot must mark the boundary between the two.
> That means that you can't use all the same regexy things for one as
> you can for the other, which is a strange system. I knew that your
> patch made it do that, and I committed it that way because I didn't
> think it really mattered, and also because the whole system is already
> pretty strange, so what's one more bit of strangeness?
Rather than trying to guess at the meaning of each '.' based on the total
string. I wonder, if we could for v15 require '.' to be spelled in longer way
if it needs to be treated as part of the regex.
Perhaps requiring something like '(.)' be used rather than a bare '.'
might be good enough and documenting otherwise it's really a separator?
I suppose we could also invent a non-standard class as a stand in like
'[::any::]', but that seems kinda weird.
I think it might be possible to give better error messages long term
if we knew what '.' should mean without looking at the whole thing.
Garick
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-04 16:08 Mark Dilger <[email protected]>
parent: Hamlin, Garick L <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-11-04 16:08 UTC (permalink / raw)
To: Hamlin, Garick L <[email protected]>; +Cc: Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Nov 4, 2021, at 6:37 AM, Hamlin, Garick L <[email protected]> wrote:
>
>> If we pass the dots through to the POSIX regular expression, we can
>> only do that either for the table name or the schema name, not both -
>> either the first or last dot must mark the boundary between the two.
>> That means that you can't use all the same regexy things for one as
>> you can for the other, which is a strange system. I knew that your
>> patch made it do that, and I committed it that way because I didn't
>> think it really mattered, and also because the whole system is already
>> pretty strange, so what's one more bit of strangeness?
>
> Rather than trying to guess at the meaning of each '.' based on the total
> string. I wonder, if we could for v15 require '.' to be spelled in longer way
> if it needs to be treated as part of the regex.
We're trying to fix an edge case, not change how the basic case works. Most users are accustomed to using patterns from within psql like:
\dt myschema.mytable
Whatever patch we accept must not break these totally normal and currently working cases.
> Perhaps requiring something like '(.)' be used rather than a bare '.'
> might be good enough and documenting otherwise it's really a separator?
> I suppose we could also invent a non-standard class as a stand in like
> '[::any::]', but that seems kinda weird.
If I understand you, that would require the above example to be written as:
\dt myschema(.)mytable
which nobody expects to have to do, and which would be a very significant breaking change in v15. I can't see anything like that being accepted.
> I think it might be possible to give better error messages long term
> if we knew what '.' should mean without looking at the whole thing.
You quote a portion of an email from Robert. After that email, there were several more, and a new patch. The commit message of the new patch explains what it does. I wonder if you'd review that message, quoted here, or even better, review the entire patch. Does this seem like an ok fix to you?
Subject: [PATCH v2] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-05 13:33 Alvaro Herrera <[email protected]>
parent: Mark Dilger <[email protected]>
1 sibling, 1 reply; 56+ messages in thread
From: Alvaro Herrera @ 2021-11-05 13:33 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Tom Lane <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On 2021-Oct-20, Mark Dilger wrote:
> I tried testing how this plays out by handing `createdb` the name é
> (U+00E9 "LATIN SMALL LETTER E WITH ACCUTE") and then again the name é
> (U+0065 "LATIN SMALL LETTER E" followed by U+0301 "COMBINING ACCUTE
> ACCENT".) That results in two distinct databases, not an error about
> a duplicate database name:
>
> # select oid, datname, datdba, encoding, datcollate, datctype from pg_catalog.pg_database where datname IN ('é', 'é');
> oid | datname | datdba | encoding | datcollate | datctype
> -------+---------+--------+----------+-------------+-------------
> 37852 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
> 37855 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
> (2 rows)
>
> But that doesn't seem to prove much, as other tools in my locale don't
> treat those as equal either. (Testing with perl's "eq" operator, they
> compare as distinct.) I expected to find regression tests providing
> better coverage for this somewhere, but did not. Anybody know more
> about it?
I think it would appropriate to normalize identifiers that are going to
be stored in catalogs. As presented, this is a bit ridiculous and I see
no reason to continue to support it.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Ed is the standard text editor."
http://groups.google.com/group/alt.religion.emacs/msg/8d94ddab6a9b0ad3
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-05 13:59 Tom Lane <[email protected]>
parent: Alvaro Herrera <[email protected]>
0 siblings, 2 replies; 56+ messages in thread
From: Tom Lane @ 2021-11-05 13:59 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Mark Dilger <[email protected]>; Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
Alvaro Herrera <[email protected]> writes:
> I think it would appropriate to normalize identifiers that are going to
> be stored in catalogs. As presented, this is a bit ridiculous and I see
> no reason to continue to support it.
If we had any sort of convention about the encoding of identifiers stored
in shared catalogs, maybe we could do something about that. But we don't,
so any change is inevitably going to break someone's use-case.
In any case, that seems quite orthogonal to the question of how to treat
names with too many dots in them. Considering we are three days out from
freezing 14.1, I think it is time to stop the meandering discussion and
fix it. And by "fix", I mean revert to the pre-14 behavior.
regards, tom lane
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-05 14:37 Robert Haas <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Robert Haas @ 2021-11-05 14:37 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Mark Dilger <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
On Fri, Nov 5, 2021 at 9:59 AM Tom Lane <[email protected]> wrote:
> In any case, that seems quite orthogonal to the question of how to treat
> names with too many dots in them. Considering we are three days out from
> freezing 14.1, I think it is time to stop the meandering discussion and
> fix it. And by "fix", I mean revert to the pre-14 behavior.
I do not think that there is consensus on that proposal.
And FWIW, I still oppose it. It's debatable whether this even
qualifies as a bug in the first place, and even more debatable whether
accepting and ignoring arbitrary garbage is the right solution.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-11-05 14:58 Mark Dilger <[email protected]>
parent: Tom Lane <[email protected]>
1 sibling, 0 replies; 56+ messages in thread
From: Mark Dilger @ 2021-11-05 14:58 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; pgsql-hackers
> On Nov 5, 2021, at 6:59 AM, Tom Lane <[email protected]> wrote:
>
> Alvaro Herrera <[email protected]> writes:
>> I think it would appropriate to normalize identifiers that are going to
>> be stored in catalogs. As presented, this is a bit ridiculous and I see
>> no reason to continue to support it.
>
> If we had any sort of convention about the encoding of identifiers stored
> in shared catalogs, maybe we could do something about that. But we don't,
> so any change is inevitably going to break someone's use-case.
I only started the discussion about normalization to demonstrate that existing behavior does not require it.
> In any case, that seems quite orthogonal to the question of how to treat
> names with too many dots in them.
Agreed.
> Considering we are three days out from
> freezing 14.1, I think it is time to stop the meandering discussion and
> fix it.
Agreed.
> And by "fix", I mean revert to the pre-14 behavior.
That's one solution. The patch I posted on October 20, and rebased two days ago, has not received any negative feedback. If you want to revert to pre-14 behavior for 14.1, do you oppose the patch going in for v15? (I'm not taking a position here, just asking what you'd prefer.)
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2021-12-21 18:58 Mark Dilger <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2021-12-21 18:58 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Robert Haas <[email protected]>; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; Tom Lane <[email protected]>
Rebased patch attached:
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] v3-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch (75.8K, ../../[email protected]/2-v3-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch)
download | inline diff:
From edad6436177c0110e9f87c300e5e8ea56cb086a6 Mon Sep 17 00:00:00 2001
From: Mark Dilger <[email protected]>
Date: Tue, 21 Dec 2021 08:17:37 -0800
Subject: [PATCH v3] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1006 insertions(+), 233 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae38d3dcc3..03c33bde69 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3627,14 +3627,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 513a18d671..5c38916a70 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b52f3ccda2..603202cbcc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -171,6 +171,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1308,10 +1311,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1355,10 +1374,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1402,10 +1428,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1448,6 +1481,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1463,9 +1500,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1486,6 +1535,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9ff0c091a9..9caa5f64ad 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1215,10 +1215,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd01651d9c..b03496c532 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3704,7 +3704,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3877,6 +3877,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c28788e84f..4ae51fd8df 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -916,8 +938,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1066,9 +1090,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1133,11 +1159,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1209,9 +1237,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1231,9 +1261,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1253,9 +1285,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1275,9 +1309,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1296,9 +1332,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1316,9 +1354,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1372,9 +1412,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3493,8 +3535,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3617,10 +3661,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3813,9 +3860,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4028,9 +4077,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4103,8 +4154,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4186,9 +4239,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4260,9 +4315,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4337,8 +4394,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4429,10 +4488,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4531,17 +4592,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4628,9 +4693,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4688,10 +4755,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4802,9 +4871,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4843,9 +4914,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5050,9 +5123,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5111,9 +5186,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5161,9 +5238,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5203,9 +5282,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5375,8 +5456,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5447,8 +5530,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5498,8 +5583,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5565,9 +5652,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5611,10 +5700,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5650,10 +5741,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5735,6 +5828,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5786,9 +5934,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5891,9 +6041,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6075,9 +6227,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6178,15 +6332,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6250,8 +6408,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6263,10 +6423,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6344,13 +6506,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6428,12 +6594,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 81e623602e..dc010dc4c6 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index e12e61cddb..095b8729ad 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d4e4fdbbb7..cd065b4a7f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2022-03-25 19:42 Mark Dilger <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Mark Dilger @ 2022-03-25 19:42 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Andres Freund <[email protected]>; Julien Rouhaud <[email protected]>; pgsql-hackers; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; Tom Lane <[email protected]>
> On Mar 22, 2022, at 11:04 AM, Robert Haas <[email protected]> wrote:
>
> This patch adds three new arguments to processSQLNamePattern() and
> documents one of them. It adds three new parameters to
> patternToSQLRegex() as well, and documents none of them.
This next patch adds the missing comments.
> I think that
> the text of the comment might need some updating too, in particular
> the sentence "Additional dots in the name portion are not treated as
> special."
Changed.
> There are no comments explaining the left_is_literal stuff. It appears
> that your intention here is that if the pattern string supplied by the
> user contains any of *?|+()[]{}.^\ not surrounded by double-quotes, we
> signal the caller. Some callers then use this to issue a complaint
> that the database name must be a literal. To me, this behavior doesn't
> really make sense. If something is a literal, that means we're not
> going to interpret the special characters that it contains. Here, we
> are interpreting the special characters just so we can complain that
> they exist. It seems to me that a simpler solution would be to not
> interpret them at all. I attach a patch showing what I mean by that.
> It just rips out the dbname_is_literal stuff in favor of doing nothing
> at all. To put the whole thing another way, if the user types "\d
> }.public.ft", your code wants to complain about the fact that the user
> is trying to use regular expression characters in a place where they
> are not allowed to do that. I argue that we should instead just be
> comparing "}" against the database name and see whether it happens to
> match.
I think your change is fine, so I've rolled it into this next patch.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
[application/octet-stream] v7-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch (76.8K, ../../[email protected]/2-v7-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch)
download | inline diff:
From e0ec1ac75f84c97ac5dddbd92f6ca016682d4df8 Mon Sep 17 00:00:00 2001
From: Mark Dilger <[email protected]>
Date: Mon, 21 Mar 2022 18:30:37 -0700
Subject: [PATCH v7] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 38 +-
src/bin/pg_dump/pg_dump.c | 65 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 53 +++
src/bin/psql/describe.c | 495 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 167 ++++++---
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 980 insertions(+), 234 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index caabb06c53..2139673fbd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..522dccf15a 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..75d1ebc6fb 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 00629f0836..2470be768f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1382,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1435,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1487,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1505,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1536,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..c35fb05ee5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1226,10 +1226,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 0e724b0366..77cdc30310 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3879,6 +3879,59 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b8a532a45f..0c251dddc8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3625,8 +3667,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3749,10 +3793,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3945,9 +3992,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4160,9 +4209,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4235,8 +4286,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4318,9 +4371,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4392,9 +4447,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4469,8 +4526,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4561,10 +4620,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4663,17 +4724,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4769,9 +4834,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4829,10 +4896,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4944,9 +5013,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4985,9 +5056,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5192,9 +5265,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5253,9 +5328,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5303,9 +5380,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5345,9 +5424,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5517,8 +5598,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5589,8 +5672,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5640,8 +5725,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5707,9 +5794,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5753,10 +5842,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5792,10 +5883,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5877,6 +5970,52 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1 &&
+ ((PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)))
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5950,9 +6089,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6066,9 +6207,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6312,9 +6455,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6415,15 +6560,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6487,8 +6636,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6500,10 +6651,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6581,13 +6734,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6665,12 +6822,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..d734d38b37 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,10 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +894,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +920,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), dbnamebuf, schema, name, pattern,
+ force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +950,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +976,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +984,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1009,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1008,8 +1027,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -1017,43 +1035,70 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1111,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1123,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1168,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1194,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..a228fe4520 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..64d8cba5a2 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..5edbb0f0da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,115 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.35.1
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: pg14 psql broke \d datname.nspname.relname
@ 2022-03-29 15:20 Robert Haas <[email protected]>
parent: Mark Dilger <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Robert Haas @ 2022-03-29 15:20 UTC (permalink / raw)
To: Mark Dilger <[email protected]>; +Cc: Andres Freund <[email protected]>; Julien Rouhaud <[email protected]>; pgsql-hackers; Justin Pryzby <[email protected]>; Stephen Frost <[email protected]>; Peter Geoghegan <[email protected]>; Tom Lane <[email protected]>
On Fri, Mar 25, 2022 at 3:42 PM Mark Dilger
<[email protected]> wrote:
> I think your change is fine, so I've rolled it into this next patch.
OK, cool. Here are some more comments.
In describe.c, why are the various describeWhatever functions
returning true when validateSQLNamePattern returns false? It seems to
me that they should return false. That would cause exec_command_d() to
set status = PSQL_CMD_ERROR, which seems appropriate. I wondered
whether we should return PSQL_CMD_ERROR only for database errors, but
that doesn't seem to be the case. For example, exec_command_a() sets
PSQL_CMD_ERROR for a failure in do_pset().
pg_dump's prohibit_crossdb_refs() has a special case for you are not
connected to a database, but psql's validateSQLNamePattern() treats it
as an invalid cross-database reference. Maybe that should be
consistent, or just the other way around. After all, I would expect
pg_dump to just bail out if we lose the database connection, but psql
may continue, because we can reconnect. Putting more code into the
tool where reconnecting doesn't really make sense seems odd.
processSQLNamePattern() documents that dotcnt can be NULL, and then
asserts that it isn't.
processSQLNamePattern() introduces new local variables schema and
name, which account for most of the notational churn in that function.
I can't see a reason why those changes are needed. You do test whether
the new variables are NULL in a couple of places, but you could
equally well test schemavar/namevar/altnamevar directly. Actually, I
don't really understand why this function needs any changes other than
passing dbnamebuf and dotcnt through to patternToSQLRegex(). Is there
a reason?
patternToSQLRegex() restructures the system of buffers as well, and I
don't understand the purpose of that either. It sort of looks like the
idea might be to relax the rule against dbname.relname patterns, but
why would we want to do that? If we don't want to do that, why remove
the assertion?
It is not very nice that patternToSQLRegex() ends up repeating the
locution "if (left && want_literal_dbname)
appendPQExpBufferChar(&left_literal, '"')" a whole bunch of times.
Suppose we remove all that. Then, in the if (!inquotes && ch == '.')
case, if left = true, we copy "cp - pattern" bytes starting at
"pattern" into the buffer. Wouldn't that accomplish the same thing
with less code?
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 56+ messages in thread
* [PATCH 3/4] store oids as integer instead of string
@ 2023-07-27 08:04 Pierre Ducroquet <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Pierre Ducroquet @ 2023-07-27 08:04 UTC (permalink / raw)
---
src/bin/pg_dump/pg_backup_archiver.c | 63 ++++++++++++++++++----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 0fd6510c10..b33cf60546 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2466,7 +2466,6 @@ void
WriteToc(ArchiveHandle *AH)
{
TocEntry *te;
- char workbuf[32];
int tocCount;
int i;
@@ -2490,11 +2489,8 @@ WriteToc(ArchiveHandle *AH)
WriteInt(AH, te->dumpId);
WriteInt(AH, te->dataDumper ? 1 : 0);
- /* OID is recorded as a string for historical reasons */
- sprintf(workbuf, "%u", te->catalogId.tableoid);
- WriteStr(AH, workbuf);
- sprintf(workbuf, "%u", te->catalogId.oid);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->catalogId.tableoid);
+ WriteInt(AH, te->catalogId.oid);
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
@@ -2510,10 +2506,9 @@ WriteToc(ArchiveHandle *AH)
/* Dump list of dependencies */
for (i = 0; i < te->nDeps; i++)
{
- sprintf(workbuf, "%d", te->dependencies[i]);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->dependencies[i]);
}
- WriteStr(AH, NULL); /* Terminate List */
+ WriteInt(AH, -1); /* Terminate List */
if (AH->WriteExtraTocPtr)
AH->WriteExtraTocPtr(AH, te);
@@ -2525,6 +2520,7 @@ ReadToc(ArchiveHandle *AH)
{
int i;
char *tmp;
+ int depId;
DumpId *deps;
int depIdx;
int depSize;
@@ -2549,7 +2545,11 @@ ReadToc(ArchiveHandle *AH)
te->hadDumper = ReadInt(AH);
- if (AH->version >= K_VERS_1_8)
+ if (AH->version >= K_VERS_1_16)
+ {
+ te->catalogId.tableoid = ReadInt(AH);
+ }
+ else if (AH->version >= K_VERS_1_8)
{
tmp = ReadStr(AH);
te->catalogId.tableoid = strtoul(tmp, NULL, 10);
@@ -2557,9 +2557,15 @@ ReadToc(ArchiveHandle *AH)
}
else
te->catalogId.tableoid = InvalidOid;
- tmp = ReadStr(AH);
- te->catalogId.oid = strtoul(tmp, NULL, 10);
- free(tmp);
+
+ if (AH->version >= K_VERS_1_16)
+ te->catalogId.oid = ReadInt(AH);
+ else
+ {
+ tmp = ReadStr(AH);
+ te->catalogId.oid = strtoul(tmp, NULL, 10);
+ free(tmp);
+ }
te->tag = ReadStr(AH);
te->desc = ReadStr(AH);
@@ -2634,16 +2640,31 @@ ReadToc(ArchiveHandle *AH)
depIdx = 0;
for (;;)
{
- tmp = ReadStr(AH);
- if (!tmp)
- break; /* end of list */
- if (depIdx >= depSize)
+ if (AH->version >= K_VERS_1_16)
{
- depSize *= 2;
- deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ depId = ReadInt(AH);
+ if (depId == -1)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = depId;
+ }
+ else
+ {
+ tmp = ReadStr(AH);
+ if (!tmp)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = strtoul(tmp, NULL, 10);
+ free(tmp);
}
- deps[depIdx] = strtoul(tmp, NULL, 10);
- free(tmp);
depIdx++;
}
--
2.41.0
--nextPart3265648.VqM8IeB0Os
Content-Disposition: attachment;
filename="0004-move-static-strings-to-arrays-at-beginning.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="x-UTF_8J";
name="0004-move-static-strings-to-arrays-at-beginning.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* [PATCH 3/4] store oids as integer instead of string
@ 2023-07-27 08:04 Pierre Ducroquet <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Pierre Ducroquet @ 2023-07-27 08:04 UTC (permalink / raw)
---
src/bin/pg_dump/pg_backup_archiver.c | 63 ++++++++++++++++++----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 0fd6510c10..b33cf60546 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2466,7 +2466,6 @@ void
WriteToc(ArchiveHandle *AH)
{
TocEntry *te;
- char workbuf[32];
int tocCount;
int i;
@@ -2490,11 +2489,8 @@ WriteToc(ArchiveHandle *AH)
WriteInt(AH, te->dumpId);
WriteInt(AH, te->dataDumper ? 1 : 0);
- /* OID is recorded as a string for historical reasons */
- sprintf(workbuf, "%u", te->catalogId.tableoid);
- WriteStr(AH, workbuf);
- sprintf(workbuf, "%u", te->catalogId.oid);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->catalogId.tableoid);
+ WriteInt(AH, te->catalogId.oid);
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
@@ -2510,10 +2506,9 @@ WriteToc(ArchiveHandle *AH)
/* Dump list of dependencies */
for (i = 0; i < te->nDeps; i++)
{
- sprintf(workbuf, "%d", te->dependencies[i]);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->dependencies[i]);
}
- WriteStr(AH, NULL); /* Terminate List */
+ WriteInt(AH, -1); /* Terminate List */
if (AH->WriteExtraTocPtr)
AH->WriteExtraTocPtr(AH, te);
@@ -2525,6 +2520,7 @@ ReadToc(ArchiveHandle *AH)
{
int i;
char *tmp;
+ int depId;
DumpId *deps;
int depIdx;
int depSize;
@@ -2549,7 +2545,11 @@ ReadToc(ArchiveHandle *AH)
te->hadDumper = ReadInt(AH);
- if (AH->version >= K_VERS_1_8)
+ if (AH->version >= K_VERS_1_16)
+ {
+ te->catalogId.tableoid = ReadInt(AH);
+ }
+ else if (AH->version >= K_VERS_1_8)
{
tmp = ReadStr(AH);
te->catalogId.tableoid = strtoul(tmp, NULL, 10);
@@ -2557,9 +2557,15 @@ ReadToc(ArchiveHandle *AH)
}
else
te->catalogId.tableoid = InvalidOid;
- tmp = ReadStr(AH);
- te->catalogId.oid = strtoul(tmp, NULL, 10);
- free(tmp);
+
+ if (AH->version >= K_VERS_1_16)
+ te->catalogId.oid = ReadInt(AH);
+ else
+ {
+ tmp = ReadStr(AH);
+ te->catalogId.oid = strtoul(tmp, NULL, 10);
+ free(tmp);
+ }
te->tag = ReadStr(AH);
te->desc = ReadStr(AH);
@@ -2634,16 +2640,31 @@ ReadToc(ArchiveHandle *AH)
depIdx = 0;
for (;;)
{
- tmp = ReadStr(AH);
- if (!tmp)
- break; /* end of list */
- if (depIdx >= depSize)
+ if (AH->version >= K_VERS_1_16)
{
- depSize *= 2;
- deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ depId = ReadInt(AH);
+ if (depId == -1)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = depId;
+ }
+ else
+ {
+ tmp = ReadStr(AH);
+ if (!tmp)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = strtoul(tmp, NULL, 10);
+ free(tmp);
}
- deps[depIdx] = strtoul(tmp, NULL, 10);
- free(tmp);
depIdx++;
}
--
2.41.0
--nextPart3265648.VqM8IeB0Os
Content-Disposition: attachment;
filename="0004-move-static-strings-to-arrays-at-beginning.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="x-UTF_8J";
name="0004-move-static-strings-to-arrays-at-beginning.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* [PATCH 3/4] store oids as integer instead of string
@ 2023-07-27 08:04 Pierre Ducroquet <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Pierre Ducroquet @ 2023-07-27 08:04 UTC (permalink / raw)
---
src/bin/pg_dump/pg_backup_archiver.c | 63 ++++++++++++++++++----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index f9efb2badf..feacc22701 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2470,7 +2470,6 @@ void
WriteToc(ArchiveHandle *AH)
{
TocEntry *te;
- char workbuf[32];
int tocCount;
int i;
@@ -2494,11 +2493,8 @@ WriteToc(ArchiveHandle *AH)
WriteInt(AH, te->dumpId);
WriteInt(AH, te->dataDumper ? 1 : 0);
- /* OID is recorded as a string for historical reasons */
- sprintf(workbuf, "%u", te->catalogId.tableoid);
- WriteStr(AH, workbuf);
- sprintf(workbuf, "%u", te->catalogId.oid);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->catalogId.tableoid);
+ WriteInt(AH, te->catalogId.oid);
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
@@ -2514,10 +2510,9 @@ WriteToc(ArchiveHandle *AH)
/* Dump list of dependencies */
for (i = 0; i < te->nDeps; i++)
{
- sprintf(workbuf, "%d", te->dependencies[i]);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->dependencies[i]);
}
- WriteStr(AH, NULL); /* Terminate List */
+ WriteInt(AH, -1); /* Terminate List */
if (AH->WriteExtraTocPtr)
AH->WriteExtraTocPtr(AH, te);
@@ -2529,6 +2524,7 @@ ReadToc(ArchiveHandle *AH)
{
int i;
char *tmp;
+ int depId;
DumpId *deps;
int depIdx;
int depSize;
@@ -2553,7 +2549,11 @@ ReadToc(ArchiveHandle *AH)
te->hadDumper = ReadInt(AH);
- if (AH->version >= K_VERS_1_8)
+ if (AH->version >= K_VERS_1_16)
+ {
+ te->catalogId.tableoid = ReadInt(AH);
+ }
+ else if (AH->version >= K_VERS_1_8)
{
tmp = ReadStr(AH);
te->catalogId.tableoid = strtoul(tmp, NULL, 10);
@@ -2561,9 +2561,15 @@ ReadToc(ArchiveHandle *AH)
}
else
te->catalogId.tableoid = InvalidOid;
- tmp = ReadStr(AH);
- te->catalogId.oid = strtoul(tmp, NULL, 10);
- free(tmp);
+
+ if (AH->version >= K_VERS_1_16)
+ te->catalogId.oid = ReadInt(AH);
+ else
+ {
+ tmp = ReadStr(AH);
+ te->catalogId.oid = strtoul(tmp, NULL, 10);
+ free(tmp);
+ }
te->tag = ReadStr(AH);
te->desc = ReadStr(AH);
@@ -2638,16 +2644,31 @@ ReadToc(ArchiveHandle *AH)
depIdx = 0;
for (;;)
{
- tmp = ReadStr(AH);
- if (!tmp)
- break; /* end of list */
- if (depIdx >= depSize)
+ if (AH->version >= K_VERS_1_16)
{
- depSize *= 2;
- deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ depId = ReadInt(AH);
+ if (depId == -1)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = depId;
+ }
+ else
+ {
+ tmp = ReadStr(AH);
+ if (!tmp)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = strtoul(tmp, NULL, 10);
+ free(tmp);
}
- deps[depIdx] = strtoul(tmp, NULL, 10);
- free(tmp);
depIdx++;
}
--
2.42.0
--nextPart4294444.ejJDZkT8p0
Content-Disposition: attachment;
filename="0004-move-static-strings-to-arrays-at-beginning.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="x-UTF_8J";
name="0004-move-static-strings-to-arrays-at-beginning.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* [PATCH 3/4] store oids as integer instead of string
@ 2023-07-27 08:04 Pierre Ducroquet <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Pierre Ducroquet @ 2023-07-27 08:04 UTC (permalink / raw)
---
src/bin/pg_dump/pg_backup_archiver.c | 63 ++++++++++++++++++----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index f9efb2badf..feacc22701 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2470,7 +2470,6 @@ void
WriteToc(ArchiveHandle *AH)
{
TocEntry *te;
- char workbuf[32];
int tocCount;
int i;
@@ -2494,11 +2493,8 @@ WriteToc(ArchiveHandle *AH)
WriteInt(AH, te->dumpId);
WriteInt(AH, te->dataDumper ? 1 : 0);
- /* OID is recorded as a string for historical reasons */
- sprintf(workbuf, "%u", te->catalogId.tableoid);
- WriteStr(AH, workbuf);
- sprintf(workbuf, "%u", te->catalogId.oid);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->catalogId.tableoid);
+ WriteInt(AH, te->catalogId.oid);
WriteStr(AH, te->tag);
WriteStr(AH, te->desc);
@@ -2514,10 +2510,9 @@ WriteToc(ArchiveHandle *AH)
/* Dump list of dependencies */
for (i = 0; i < te->nDeps; i++)
{
- sprintf(workbuf, "%d", te->dependencies[i]);
- WriteStr(AH, workbuf);
+ WriteInt(AH, te->dependencies[i]);
}
- WriteStr(AH, NULL); /* Terminate List */
+ WriteInt(AH, -1); /* Terminate List */
if (AH->WriteExtraTocPtr)
AH->WriteExtraTocPtr(AH, te);
@@ -2529,6 +2524,7 @@ ReadToc(ArchiveHandle *AH)
{
int i;
char *tmp;
+ int depId;
DumpId *deps;
int depIdx;
int depSize;
@@ -2553,7 +2549,11 @@ ReadToc(ArchiveHandle *AH)
te->hadDumper = ReadInt(AH);
- if (AH->version >= K_VERS_1_8)
+ if (AH->version >= K_VERS_1_16)
+ {
+ te->catalogId.tableoid = ReadInt(AH);
+ }
+ else if (AH->version >= K_VERS_1_8)
{
tmp = ReadStr(AH);
te->catalogId.tableoid = strtoul(tmp, NULL, 10);
@@ -2561,9 +2561,15 @@ ReadToc(ArchiveHandle *AH)
}
else
te->catalogId.tableoid = InvalidOid;
- tmp = ReadStr(AH);
- te->catalogId.oid = strtoul(tmp, NULL, 10);
- free(tmp);
+
+ if (AH->version >= K_VERS_1_16)
+ te->catalogId.oid = ReadInt(AH);
+ else
+ {
+ tmp = ReadStr(AH);
+ te->catalogId.oid = strtoul(tmp, NULL, 10);
+ free(tmp);
+ }
te->tag = ReadStr(AH);
te->desc = ReadStr(AH);
@@ -2638,16 +2644,31 @@ ReadToc(ArchiveHandle *AH)
depIdx = 0;
for (;;)
{
- tmp = ReadStr(AH);
- if (!tmp)
- break; /* end of list */
- if (depIdx >= depSize)
+ if (AH->version >= K_VERS_1_16)
{
- depSize *= 2;
- deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ depId = ReadInt(AH);
+ if (depId == -1)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = depId;
+ }
+ else
+ {
+ tmp = ReadStr(AH);
+ if (!tmp)
+ break; /* end of list */
+ if (depIdx >= depSize)
+ {
+ depSize *= 2;
+ deps = (DumpId *) pg_realloc(deps, sizeof(DumpId) * depSize);
+ }
+ deps[depIdx] = strtoul(tmp, NULL, 10);
+ free(tmp);
}
- deps[depIdx] = strtoul(tmp, NULL, 10);
- free(tmp);
depIdx++;
}
--
2.42.0
--nextPart4294444.ejJDZkT8p0
Content-Disposition: attachment;
filename="0004-move-static-strings-to-arrays-at-beginning.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="x-UTF_8J";
name="0004-move-static-strings-to-arrays-at-beginning.patch"
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: log_min_messages per backend type
@ 2026-02-09 12:58 Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 56+ messages in thread
From: Alvaro Herrera @ 2026-02-09 12:58 UTC (permalink / raw)
To: Euler Taveira <[email protected]>; +Cc: japin <[email protected]>; Andres Freund <[email protected]>; [email protected]; Chao Li <[email protected]>
On 2026-Feb-06, Euler Taveira wrote:
> On Fri, Feb 6, 2026, at 8:05 AM, Alvaro Herrera wrote:
> > * I'm not fond of the term "generic", so I changed it to "default",
> > which IMO makes more sense from the user point of view.
>
> I'm fine with "default" instead of "generic". However, you forgot to replace
> "generic" in a few places. The attached patch fixes them all.
I just pushed this, and somehow I forgot to squash this into the commit.
I don't think it matters terribly much though, so I'm going to hang onto
this for a while in case some bug is found.
I made some more changes though. Notably, I reworded some things in the
docs that appeared ungrammatical and added a simple example; added the
list of process types to postgresql.conf.sample; and moved the GUC
check/assign hooks to elog.c together with the other elog-affecting
GUCs, as I didn't think they made much sense in variable.c.
Another thing, which is actually quite significant, is that I fixed a
bug I had introduced on Friday without realizing: I had added a "break"
in the loop that searches for process types. But we mustn't break
there! Otherwise we fail to set the level for process types that share
the same process "category" with others, such as "backend" and
"autovacuum". So I removed the break and added a comment about it.
It's sad that no tests broke when I introduced that bug, but I think it
would be difficult to cover this.
Thanks
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"That sort of implies that there are Emacs keystrokes which aren't obscure.
I've been using it daily for 2 years now and have yet to discover any key
sequence which makes any sense." (Paul Thomas)
^ permalink raw reply [nested|flat] 56+ messages in thread
* Re: log_min_messages per backend type
@ 2026-02-09 13:03 Alvaro Herrera <[email protected]>
parent: Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 56+ messages in thread
From: Alvaro Herrera @ 2026-02-09 13:03 UTC (permalink / raw)
To: Euler Taveira <[email protected]>; +Cc: japin <[email protected]>; Andres Freund <[email protected]>; [email protected]; Chao Li <[email protected]>
On 2026-Feb-09, Alvaro Herrera wrote:
> I just pushed this, and somehow I forgot to squash this into the commit.
> I don't think it matters terribly much though, so I'm going to hang onto
> this for a while in case some bug is found.
Euler mentioned offlist that I also forgot to remove
log_min_messages_process_types from guc.h. So that gives us this patch
for now.
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 129906e2daa..59315e94e3e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2190,7 +2190,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
char *result;
int newlevel[BACKEND_NUM_TYPES];
bool assigned[BACKEND_NUM_TYPES] = {0};
- int genericlevel = -1; /* -1 means not assigned */
+ int defaultlevel = -1; /* -1 means not assigned */
const char *const process_types[] = {
#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach) \
@@ -2228,8 +2228,8 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
const struct config_enum_entry *entry;
bool found;
- /* Reject duplicates for generic log level. */
- if (genericlevel != -1)
+ /* Reject duplicates for default log level. */
+ if (defaultlevel != -1)
{
GUC_check_errdetail("Redundant specification of default log level.");
goto lmm_fail;
@@ -2241,7 +2241,7 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
{
if (pg_strcasecmp(entry->name, elem) == 0)
{
- genericlevel = entry->val;
+ defaultlevel = entry->val;
found = true;
break;
}
@@ -2331,9 +2331,9 @@ lmm_fail:
}
/*
- * The generic log level must be specified. It is the fallback value.
+ * The default log level must be specified. It is the fallback value.
*/
- if (genericlevel == -1)
+ if (defaultlevel == -1)
{
GUC_check_errdetail("Default log level was not defined.");
guc_free(rawstring);
@@ -2345,7 +2345,7 @@ lmm_fail:
for (int i = 0; i < BACKEND_NUM_TYPES; i++)
{
if (!assigned[i])
- newlevel[i] = genericlevel;
+ newlevel[i] = defaultlevel;
}
/*
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 8acbdba7ff5..c46203fabfe 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -329,8 +329,6 @@ extern PGDLLIMPORT bool trace_sort;
extern PGDLLIMPORT bool optimize_bounded_sort;
#endif
-extern PGDLLIMPORT const char *const log_min_messages_process_types[];
-
/*
* Declarations for options for enum values
*
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"I suspect most samba developers are already technically insane...
Of course, since many of them are Australians, you can't tell." (L. Torvalds)
^ permalink raw reply [nested|flat] 56+ messages in thread
end of thread, other threads:[~2026-02-09 13:03 UTC | newest]
Thread overview: 56+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-01-17 12:38 [PATCH 3/4] Retrieve-shared-location-for-dict Arthur Zakirov <[email protected]>
2019-01-17 12:38 [PATCH 3/4] Retrieve-shared-location-for-dict Arthur Zakirov <[email protected]>
2021-10-11 21:24 pg14 psql broke \d datname.nspname.relname Justin Pryzby <[email protected]>
2021-10-11 21:47 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-11 22:04 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-10-11 22:25 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-11 22:37 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-10-11 23:35 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-11 23:41 ` Re: pg14 psql broke \d datname.nspname.relname Isaac Morland <[email protected]>
2021-10-11 23:49 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-10-12 02:09 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-12 02:33 ` Re: pg14 psql broke \d datname.nspname.relname Peter Geoghegan <[email protected]>
2021-10-12 14:23 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-12 14:30 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-10-12 14:37 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-12 14:40 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-12 15:19 ` Re: pg14 psql broke \d datname.nspname.relname Stephen Frost <[email protected]>
2021-10-12 16:52 ` Re: pg14 psql broke \d datname.nspname.relname Vik Fearing <[email protected]>
2021-10-12 16:57 ` Re: pg14 psql broke \d datname.nspname.relname Justin Pryzby <[email protected]>
2021-10-12 17:03 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-12 17:18 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-12 17:54 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-12 19:26 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-12 21:21 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-13 13:24 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-13 14:40 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-13 15:43 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-13 20:43 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-14 12:54 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-20 14:15 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-11-03 19:07 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-11-03 21:52 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-12-21 18:58 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2022-03-25 19:42 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2022-03-29 15:20 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-11-05 13:33 ` Re: pg14 psql broke \d datname.nspname.relname Alvaro Herrera <[email protected]>
2021-11-05 13:59 ` Re: pg14 psql broke \d datname.nspname.relname Tom Lane <[email protected]>
2021-11-05 14:37 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-11-05 14:58 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-11-04 13:37 ` Re: pg14 psql broke \d datname.nspname.relname Hamlin, Garick L <[email protected]>
2021-11-04 16:08 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-13 16:46 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-13 16:54 ` Re: pg14 psql broke \d datname.nspname.relname Justin Pryzby <[email protected]>
2021-10-13 17:05 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-13 18:55 ` Re: pg14 psql broke \d datname.nspname.relname Stephen Frost <[email protected]>
2021-10-12 16:44 ` Re: pg14 psql broke \d datname.nspname.relname Peter Geoghegan <[email protected]>
2021-10-12 17:01 ` Re: pg14 psql broke \d datname.nspname.relname Robert Haas <[email protected]>
2021-10-12 17:38 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2021-10-11 22:26 ` Re: pg14 psql broke \d datname.nspname.relname Justin Pryzby <[email protected]>
2021-10-11 22:32 ` Re: pg14 psql broke \d datname.nspname.relname Mark Dilger <[email protected]>
2023-07-27 08:04 [PATCH 3/4] store oids as integer instead of string Pierre Ducroquet <[email protected]>
2023-07-27 08:04 [PATCH 3/4] store oids as integer instead of string Pierre Ducroquet <[email protected]>
2023-07-27 08:04 [PATCH 3/4] store oids as integer instead of string Pierre Ducroquet <[email protected]>
2023-07-27 08:04 [PATCH 3/4] store oids as integer instead of string Pierre Ducroquet <[email protected]>
2026-02-09 12:58 Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
2026-02-09 13:03 ` Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox