Add error pattern checks for some TAP tests for non-existing objects
[pgsql.git] / src / bin / psql / stringutils.c
blobf613413553e29578f05c6522c636a88f25fef85b
1 /*
2 * psql - the PostgreSQL interactive terminal
4 * Copyright (c) 2000-2025, PostgreSQL Global Development Group
6 * src/bin/psql/stringutils.c
7 */
8 #include "postgres_fe.h"
10 #include <ctype.h>
12 #include "common.h"
13 #include "stringutils.h"
17 * Replacement for strtok() (a.k.a. poor man's flex)
19 * Splits a string into tokens, returning one token per call, then NULL
20 * when no more tokens exist in the given string.
22 * The calling convention is similar to that of strtok, but with more
23 * frammishes.
25 * s - string to parse, if NULL continue parsing the last string
26 * whitespace - set of whitespace characters that separate tokens
27 * delim - set of non-whitespace separator characters (or NULL)
28 * quote - set of characters that can quote a token (NULL if none)
29 * escape - character that can quote quotes (0 if none)
30 * e_strings - if true, treat E'...' syntax as a valid token
31 * del_quotes - if true, strip quotes from the returned token, else return
32 * it exactly as found in the string
33 * encoding - the active character-set encoding
35 * Characters in 'delim', if any, will be returned as single-character
36 * tokens unless part of a quoted token.
38 * Double occurrences of the quoting character are always taken to represent
39 * a single quote character in the data. If escape isn't 0, then escape
40 * followed by anything (except \0) is a data character too.
42 * The combination of e_strings and del_quotes both true is not currently
43 * handled. This could be fixed but it's not needed anywhere at the moment.
45 * Note that the string s is _not_ overwritten in this implementation.
47 * NB: it's okay to vary delim, quote, and escape from one call to the
48 * next on a single source string, but changing whitespace is a bad idea
49 * since you might lose data.
51 char *
52 strtokx(const char *s,
53 const char *whitespace,
54 const char *delim,
55 const char *quote,
56 char escape,
57 bool e_strings,
58 bool del_quotes,
59 int encoding)
61 static char *storage = NULL; /* store the local copy of the users
62 * string here */
63 static char *string = NULL; /* pointer into storage where to continue on
64 * next call */
66 /* variously abused variables: */
67 unsigned int offset;
68 char *start;
69 char *p;
71 if (s)
73 free(storage);
76 * We may need extra space to insert delimiter nulls for adjacent
77 * tokens. 2X the space is a gross overestimate, but it's unlikely
78 * that this code will be used on huge strings anyway.
80 storage = pg_malloc(2 * strlen(s) + 1);
81 strcpy(storage, s);
82 string = storage;
85 if (!storage)
86 return NULL;
88 /* skip leading whitespace */
89 offset = strspn(string, whitespace);
90 start = &string[offset];
92 /* end of string reached? */
93 if (*start == '\0')
95 /* technically we don't need to free here, but we're nice */
96 free(storage);
97 storage = NULL;
98 string = NULL;
99 return NULL;
102 /* test if delimiter character */
103 if (delim && strchr(delim, *start))
106 * If not at end of string, we need to insert a null to terminate the
107 * returned token. We can just overwrite the next character if it
108 * happens to be in the whitespace set ... otherwise move over the
109 * rest of the string to make room. (This is why we allocated extra
110 * space above).
112 p = start + 1;
113 if (*p != '\0')
115 if (!strchr(whitespace, *p))
116 memmove(p + 1, p, strlen(p) + 1);
117 *p = '\0';
118 string = p + 1;
120 else
122 /* at end of string, so no extra work */
123 string = p;
126 return start;
129 /* check for E string */
130 p = start;
131 if (e_strings &&
132 (*p == 'E' || *p == 'e') &&
133 p[1] == '\'')
135 quote = "'";
136 escape = '\\'; /* if std strings before, not any more */
137 p++;
140 /* test if quoting character */
141 if (quote && strchr(quote, *p))
143 /* okay, we have a quoted token, now scan for the closer */
144 char thisquote = *p++;
146 for (; *p; p += PQmblenBounded(p, encoding))
148 if (*p == escape && p[1] != '\0')
149 p++; /* process escaped anything */
150 else if (*p == thisquote && p[1] == thisquote)
151 p++; /* process doubled quote */
152 else if (*p == thisquote)
154 p++; /* skip trailing quote */
155 break;
160 * If not at end of string, we need to insert a null to terminate the
161 * returned token. See notes above.
163 if (*p != '\0')
165 if (!strchr(whitespace, *p))
166 memmove(p + 1, p, strlen(p) + 1);
167 *p = '\0';
168 string = p + 1;
170 else
172 /* at end of string, so no extra work */
173 string = p;
176 /* Clean up the token if caller wants that */
177 if (del_quotes)
178 strip_quotes(start, thisquote, escape, encoding);
180 return start;
184 * Otherwise no quoting character. Scan till next whitespace, delimiter
185 * or quote. NB: at this point, *start is known not to be '\0',
186 * whitespace, delim, or quote, so we will consume at least one character.
188 offset = strcspn(start, whitespace);
190 if (delim)
192 unsigned int offset2 = strcspn(start, delim);
194 if (offset > offset2)
195 offset = offset2;
198 if (quote)
200 unsigned int offset2 = strcspn(start, quote);
202 if (offset > offset2)
203 offset = offset2;
206 p = start + offset;
209 * If not at end of string, we need to insert a null to terminate the
210 * returned token. See notes above.
212 if (*p != '\0')
214 if (!strchr(whitespace, *p))
215 memmove(p + 1, p, strlen(p) + 1);
216 *p = '\0';
217 string = p + 1;
219 else
221 /* at end of string, so no extra work */
222 string = p;
225 return start;
230 * strip_quotes
232 * Remove quotes from the string at *source. Leading and trailing occurrences
233 * of 'quote' are removed; embedded double occurrences of 'quote' are reduced
234 * to single occurrences; if 'escape' is not 0 then 'escape' removes special
235 * significance of next character.
237 * Note that the source string is overwritten in-place.
239 void
240 strip_quotes(char *source, char quote, char escape, int encoding)
242 char *src;
243 char *dst;
245 Assert(source != NULL);
246 Assert(quote != '\0');
248 src = dst = source;
250 if (*src && *src == quote)
251 src++; /* skip leading quote */
253 while (*src)
255 char c = *src;
256 int i;
258 if (c == quote && src[1] == '\0')
259 break; /* skip trailing quote */
260 else if (c == quote && src[1] == quote)
261 src++; /* process doubled quote */
262 else if (c == escape && src[1] != '\0')
263 src++; /* process escaped character */
265 i = PQmblenBounded(src, encoding);
266 while (i--)
267 *dst++ = *src++;
270 *dst = '\0';
275 * quote_if_needed
277 * Opposite of strip_quotes(). If "source" denotes itself literally without
278 * quoting or escaping, returns NULL. Otherwise, returns a malloc'd copy with
279 * quoting and escaping applied:
281 * source - string to parse
282 * entails_quote - any of these present? need outer quotes
283 * quote - doubled within string, affixed to both ends
284 * escape - doubled within string
285 * force_quote - if true, quote the output even if it doesn't "need" it
286 * encoding - the active character-set encoding
288 * Do not use this as a substitute for PQescapeStringConn(). Use it for
289 * strings to be parsed by strtokx() or psql_scan_slash_option().
291 char *
292 quote_if_needed(const char *source, const char *entails_quote,
293 char quote, char escape, bool force_quote,
294 int encoding)
296 const char *src;
297 char *ret;
298 char *dst;
299 bool need_quotes = force_quote;
301 Assert(source != NULL);
302 Assert(quote != '\0');
304 src = source;
305 dst = ret = pg_malloc(2 * strlen(src) + 3); /* excess */
307 *dst++ = quote;
309 while (*src)
311 char c = *src;
312 int i;
314 if (c == quote)
316 need_quotes = true;
317 *dst++ = quote;
319 else if (c == escape)
321 need_quotes = true;
322 *dst++ = escape;
324 else if (strchr(entails_quote, c))
325 need_quotes = true;
327 i = PQmblenBounded(src, encoding);
328 while (i--)
329 *dst++ = *src++;
332 *dst++ = quote;
333 *dst = '\0';
335 if (!need_quotes)
337 free(ret);
338 ret = NULL;
341 return ret;