/* * Advance the given char pointer over white space and SQL comments. */ static const char * skip_white_space(const char *query) { int cnestlevel = 0; /* slash-star comment nest level */ while (*query) { int mblen = PQmblen(query, pset.encoding); /* * Note: we assume the encoding is a superset of ASCII, so that for * example "query[0] == '/'" is meaningful. However, we do NOT assume * that the second and subsequent bytes of a multibyte character * couldn't look like ASCII characters; so it is critical to advance * by mblen, not 1, whenever we haven't exactly identified the * character we are skipping over. */ if (isspace((unsigned char) *query)) query += mblen; else if (query[0] == '/' && query[1] == '*') { cnestlevel++; query += 2; } else if (cnestlevel > 0 && query[0] == '*' && query[1] == '/') { cnestlevel--; query += 2; } else if (cnestlevel == 0 && query[0] == '-' && query[1] == '-') { query += 2; /* * We have to skip to end of line since any slash-star inside the * -- comment does NOT start a slash-star comment. */ while (*query) { if (*query == '\n') { query++; break; } query += PQmblen(query, pset.encoding); } } else if (cnestlevel > 0) query += mblen; else break; /* found first token */ } return query; }
/* * Check whether the specified command is a SELECT (or VALUES). */ static bool is_select_command(const char *query) { int wordlen; /* * First advance over any whitespace, comments and left parentheses. */ for (;;) { query = skip_white_space(query); if (query[0] == '(') query++; else break; } /* * Check word length (since "selectx" is not "select"). */ wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0) return true; if (wordlen == 6 && pg_strncasecmp(query, "values", 6) == 0) return true; return false; }
/* * strip_quotes * * Remove quotes from the string at *source. Leading and trailing occurrences * of 'quote' are removed; embedded double occurrences of 'quote' are reduced * to single occurrences; if 'escape' is not 0 then 'escape' removes special * significance of next character. * * Note that the source string is overwritten in-place. */ static void strip_quotes(char *source, char quote, char escape, int encoding) { char *src; char *dst; psql_assert(source); psql_assert(quote); src = dst = source; if (*src && *src == quote) src++; /* skip leading quote */ while (*src) { char c = *src; int i; if (c == quote && src[1] == '\0') break; /* skip trailing quote */ else if (c == quote && src[1] == quote) src++; /* process doubled quote */ else if (c == escape && src[1] != '\0') src++; /* process escaped character */ i = PQmblen(src, encoding); while (i--) *dst++ = *src++; } *dst = '\0'; }
/* * quote_if_needed * * Opposite of strip_quotes(). If "source" denotes itself literally without * quoting or escaping, returns NULL. Otherwise, returns a malloc'd copy with * quoting and escaping applied: * * source - string to parse * entails_quote - any of these present? need outer quotes * quote - doubled within string, affixed to both ends * escape - doubled within string * encoding - the active character-set encoding * * Do not use this as a substitute for PQescapeStringConn(). Use it for * strings to be parsed by strtokx() or psql_scan_slash_option(). */ char * quote_if_needed(const char *source, const char *entails_quote, char quote, char escape, int encoding) { const char *src; char *ret; char *dst; bool need_quotes = false; Assert(source != NULL); Assert(quote != '\0'); src = source; dst = ret = pg_malloc(2 * strlen(src) + 3); /* excess */ *dst++ = quote; while (*src) { char c = *src; int i; if (c == quote) { need_quotes = true; *dst++ = quote; } else if (c == escape) { need_quotes = true; *dst++ = escape; } else if (strchr(entails_quote, c)) need_quotes = true; i = PQmblen(src, encoding); while (i--) *dst++ = *src++; } *dst++ = quote; *dst = '\0'; if (!need_quotes) { free(ret); ret = NULL; } return ret; }
/* * processSQLNamePattern * * Scan a wildcard-pattern string and generate appropriate WHERE clauses * to limit the set of objects returned. The WHERE clauses are appended * to the already-partially-constructed query in buf. Returns whether * any clause was added. * * conn: connection query will be sent to (consulted for escaping rules). * buf: output parameter. * pattern: user-specified pattern option, or NULL if none ("*" is implied). * have_where: true if caller already emitted "WHERE" (clauses will be ANDed * onto the existing WHERE clause). * force_escape: always quote regexp special characters, even outside * double quotes (else they are quoted only between double quotes). * schemavar: name of query variable to match against a schema-name pattern. * Can be NULL if no schema. * namevar: name of query variable to match against an object-name pattern. * 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. * * Formatting note: the text already present in buf should end with a newline. * The appended text, if any, will end with one too. */ 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) { PQExpBufferData schemabuf; PQExpBufferData namebuf; int encoding = PQclientEncoding(conn); bool inquotes; const char *cp; int i; bool added_clause = false; #define WHEREAND() \ (appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \ have_where = true, added_clause = true) if (pattern == NULL) { /* Default: select all visible objects */ if (visibilityrule) { WHEREAND(); appendPQExpBuffer(buf, "%s\n", visibilityrule); } return added_clause; } initPQExpBuffer(&schemabuf); initPQExpBuffer(&namebuf); /* * Parse the pattern, converting quotes and lower-casing unquoted letters. * Also, adjust shell-style wildcard characters into regexp notation. * * We surround the pattern with "^(...)$" to force it to match the whole * string, as per SQL practice. We have to have parens in case the string * contains "|", else the "^" and "$" will be bound into the first and * last alternatives which is not what we want. * * Note: the result of this pass is the actual regexp pattern(s) we want * to execute. Quoting/escaping into SQL literal format will be done * below using appendStringLiteralConn(). */ appendPQExpBufferStr(&namebuf, "^("); inquotes = false; cp = pattern; while (*cp) { char ch = *cp; if (ch == '"') { if (inquotes && cp[1] == '"') { /* emit one quote, stay in inquotes mode */ appendPQExpBufferChar(&namebuf, '"'); cp++; } else inquotes = !inquotes; cp++; } else if (!inquotes && isupper((unsigned char) ch)) { appendPQExpBufferChar(&namebuf, pg_tolower((unsigned char) ch)); cp++; } else if (!inquotes && ch == '*') { appendPQExpBufferStr(&namebuf, ".*"); cp++; } else if (!inquotes && ch == '?') { appendPQExpBufferChar(&namebuf, '.'); cp++; } else if (!inquotes && ch == '.') { /* Found schema/name separator, move current pattern to schema */ resetPQExpBuffer(&schemabuf); appendPQExpBufferStr(&schemabuf, namebuf.data); resetPQExpBuffer(&namebuf); appendPQExpBufferStr(&namebuf, "^("); cp++; } else if (ch == '$') { /* * Dollar is always quoted, whether inside quotes or not. The * reason is that it's allowed in SQL identifiers, so there's a * significant use-case for treating it literally, while because * we anchor the pattern automatically there is no use-case for * having it possess its regexp meaning. */ appendPQExpBufferStr(&namebuf, "\\$"); cp++; } else { /* * Ordinary data character, transfer to pattern * * Inside double quotes, or at all times if force_escape is true, * quote regexp special characters with a backslash to avoid * regexp errors. Outside quotes, however, let them pass through * as-is; this lets knowledgeable users build regexp expressions * that are more powerful than shell-style patterns. */ if ((inquotes || force_escape) && strchr("|*+?()[]{}.^$\\", ch)) appendPQExpBufferChar(&namebuf, '\\'); i = PQmblen(cp, encoding); while (i-- && *cp) { appendPQExpBufferChar(&namebuf, *cp); cp++; } } } /* * Now decide what we need to emit. Note there will be a leading "^(" in * the patterns in any case. */ if (namebuf.len > 2) { /* We have a name pattern, so constrain the namevar(s) */ appendPQExpBufferStr(&namebuf, ")$"); /* Optimize away a "*" pattern */ if (strcmp(namebuf.data, "^(.*)$") != 0) { WHEREAND(); if (altnamevar) { appendPQExpBuffer(buf, "(%s ~ ", namevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBuffer(buf, "\n OR %s ~ ", altnamevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBufferStr(buf, ")\n"); } else { appendPQExpBuffer(buf, "%s ~ ", namevar); appendStringLiteralConn(buf, namebuf.data, conn); appendPQExpBufferChar(buf, '\n'); } } } if (schemabuf.len > 2) { /* We have a schema pattern, so constrain the schemavar */ appendPQExpBufferStr(&schemabuf, ")$"); /* Optimize away a "*" pattern */ if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar) { WHEREAND(); appendPQExpBuffer(buf, "%s ~ ", schemavar); appendStringLiteralConn(buf, schemabuf.data, conn); appendPQExpBufferChar(buf, '\n'); } } else { /* No schema pattern given, so select only visible objects */ if (visibilityrule) { WHEREAND(); appendPQExpBuffer(buf, "%s\n", visibilityrule); } } termPQExpBuffer(&schemabuf); termPQExpBuffer(&namebuf); return added_clause; #undef WHEREAND }
/* * Convert a string value to an SQL string literal and append it to * the given buffer. We assume the specified client_encoding and * standard_conforming_strings settings. * * This is essentially equivalent to libpq's PQescapeStringInternal, * except for the output buffer structure. We need it in situations * where we do not have a PGconn available. Where we do, * appendStringLiteralConn is a better choice. */ void appendStringLiteral(PQExpBuffer buf, const char *str, int encoding, bool std_strings) { size_t length = strlen(str); const char *source = str; char *target; if (!enlargePQExpBuffer(buf, 2 * length + 2)) return; target = buf->data + buf->len; *target++ = '\''; while (*source != '\0') { char c = *source; int len; int i; /* Fast path for plain ASCII */ if (!IS_HIGHBIT_SET(c)) { /* Apply quoting if needed */ if (SQL_STR_DOUBLE(c, !std_strings)) *target++ = c; /* Copy the character */ *target++ = c; source++; continue; } /* Slow path for possible multibyte characters */ len = PQmblen(source, encoding); /* Copy the character */ for (i = 0; i < len; i++) { if (*source == '\0') break; *target++ = *source++; } /* * If we hit premature end of string (ie, incomplete multibyte * character), try to pad out to the correct length with spaces. We * may not be able to pad completely, but we will always be able to * insert at least one pad space (since we'd not have quoted a * multibyte character). This should be enough to make a string that * the server will error out on. */ if (i < len) { char *stop = buf->data + buf->maxlen - 2; for (; i < len; i++) { if (target >= stop) break; *target++ = ' '; } break; } } /* Write the terminating quote and NUL character. */ *target++ = '\''; *target = '\0'; buf->len = target - buf->data; }
static void do_field(const PQprintOpt *po, const PGresult *res, const int i, const int j, const int fs_len, char **fields, const int nFields, char const ** fieldNames, unsigned char *fieldNotNum, int *fieldMax, const int fieldMaxLen, FILE *fout) { const char *pval, *p; int plen; bool skipit; plen = PQgetlength(res, i, j); pval = PQgetvalue(res, i, j); if (plen < 1 || !pval || !*pval) { if (po->align || po->expanded) skipit = true; else { skipit = false; goto efield; } } else skipit = false; if (!skipit) { if (po->align && !fieldNotNum[j]) { /* Detect whether field contains non-numeric data */ char ch = '0'; for (p = pval; *p; p += PQmblen(p, res->client_encoding)) { ch = *p; if (!((ch >= '0' && ch <= '9') || ch == '.' || ch == 'E' || ch == 'e' || ch == ' ' || ch == '-')) { fieldNotNum[j] = 1; break; } } /* * Above loop will believe E in first column is numeric; also, we * insist on a digit in the last column for a numeric. This test * is still not bulletproof but it handles most cases. */ if (*pval == 'E' || *pval == 'e' || !(ch >= '0' && ch <= '9')) fieldNotNum[j] = 1; } if (!po->expanded && (po->align || po->html3)) { if (plen > fieldMax[j]) fieldMax[j] = plen; if (!(fields[i * nFields + j] = (char *) malloc(plen + 1))) { fprintf(stderr, libpq_gettext("out of memory\n")); abort(); } strcpy(fields[i * nFields + j], pval); } else { if (po->expanded) { if (po->html3) fprintf(fout, "<tr><td align=\"left\"><b>%s</b></td>" "<td align=\"%s\">%s</td></tr>\n", fieldNames[j], fieldNotNum[j] ? "left" : "right", pval); else { if (po->align) fprintf(fout, "%-*s%s %s\n", fieldMaxLen - fs_len, fieldNames[j], po->fieldSep, pval); else fprintf(fout, "%s%s%s\n", fieldNames[j], po->fieldSep, pval); } } else { if (!po->html3) { fputs(pval, fout); efield: if ((j + 1) < nFields) fputs(po->fieldSep, fout); else fputc('\n', fout); } } } } }
/* * Check whether a command is one of those for which we should NOT start * a new transaction block (ie, send a preceding BEGIN). * * These include the transaction control statements themselves, plus * certain statements that the backend disallows inside transaction blocks. */ static bool command_no_begin(const char *query) { int wordlen; /* * First we must advance over any whitespace and comments. */ query = skip_white_space(query); /* * Check word length (since "beginx" is not "begin"). */ wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); /* * Transaction control commands. These should include every keyword that * gives rise to a TransactionStmt in the backend grammar, except for the * savepoint-related commands. * * (We assume that START must be START TRANSACTION, since there is * presently no other "START foo" command.) */ if (wordlen == 5 && pg_strncasecmp(query, "abort", 5) == 0) return true; if (wordlen == 5 && pg_strncasecmp(query, "begin", 5) == 0) return true; if (wordlen == 5 && pg_strncasecmp(query, "start", 5) == 0) return true; if (wordlen == 6 && pg_strncasecmp(query, "commit", 6) == 0) return true; if (wordlen == 3 && pg_strncasecmp(query, "end", 3) == 0) return true; if (wordlen == 8 && pg_strncasecmp(query, "rollback", 8) == 0) return true; if (wordlen == 7 && pg_strncasecmp(query, "prepare", 7) == 0) { /* PREPARE TRANSACTION is a TC command, PREPARE foo is not */ query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 11 && pg_strncasecmp(query, "transaction", 11) == 0) return true; return false; } /* * Commands not allowed within transactions. The statements checked for * here should be exactly those that call PreventTransactionChain() in the * backend. */ if (wordlen == 6 && pg_strncasecmp(query, "vacuum", 6) == 0) return true; if (wordlen == 7 && pg_strncasecmp(query, "cluster", 7) == 0) { /* CLUSTER with any arguments is allowed in transactions */ query += wordlen; query = skip_white_space(query); if (isalpha((unsigned char) query[0])) return false; /* has additional words */ return true; /* it's CLUSTER without arguments */ } if (wordlen == 6 && pg_strncasecmp(query, "create", 6) == 0) { query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0) return true; if (wordlen == 10 && pg_strncasecmp(query, "tablespace", 10) == 0) return true; /* CREATE [UNIQUE] INDEX CONCURRENTLY isn't allowed in xacts */ if (wordlen == 6 && pg_strncasecmp(query, "unique", 6) == 0) { query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); } if (wordlen == 5 && pg_strncasecmp(query, "index", 5) == 0) { query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 12 && pg_strncasecmp(query, "concurrently", 12) == 0) return true; } return false; } /* * Note: these tests will match DROP SYSTEM and REINDEX TABLESPACE, which * aren't really valid commands so we don't care much. The other four * possible matches are correct. */ if ((wordlen == 4 && pg_strncasecmp(query, "drop", 4) == 0) || (wordlen == 7 && pg_strncasecmp(query, "reindex", 7) == 0)) { query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 8 && pg_strncasecmp(query, "database", 8) == 0) return true; if (wordlen == 6 && pg_strncasecmp(query, "system", 6) == 0) return true; if (wordlen == 10 && pg_strncasecmp(query, "tablespace", 10) == 0) return true; return false; } /* DISCARD ALL isn't allowed in xacts, but other variants are allowed. */ if (wordlen == 7 && pg_strncasecmp(query, "discard", 7) == 0) { query += wordlen; query = skip_white_space(query); wordlen = 0; while (isalpha((unsigned char) query[wordlen])) wordlen += PQmblen(&query[wordlen], pset.encoding); if (wordlen == 3 && pg_strncasecmp(query, "all", 3) == 0) return true; return false; } return false; }
/* * Replacement for strtok() (a.k.a. poor man's flex) * * Splits a string into tokens, returning one token per call, then NULL * when no more tokens exist in the given string. * * The calling convention is similar to that of strtok, but with more * frammishes. * * s - string to parse, if NULL continue parsing the last string * whitespace - set of whitespace characters that separate tokens * delim - set of non-whitespace separator characters (or NULL) * quote - set of characters that can quote a token (NULL if none) * escape - character that can quote quotes (0 if none) * e_strings - if TRUE, treat E'...' syntax as a valid token * del_quotes - if TRUE, strip quotes from the returned token, else return * it exactly as found in the string * encoding - the active character-set encoding * * Characters in 'delim', if any, will be returned as single-character * tokens unless part of a quoted token. * * Double occurrences of the quoting character are always taken to represent * a single quote character in the data. If escape isn't 0, then escape * followed by anything (except \0) is a data character too. * * The combination of e_strings and del_quotes both TRUE is not currently * handled. This could be fixed but it's not needed anywhere at the moment. * * Note that the string s is _not_ overwritten in this implementation. * * NB: it's okay to vary delim, quote, and escape from one call to the * next on a single source string, but changing whitespace is a bad idea * since you might lose data. */ char * strtokx(const char *s, const char *whitespace, const char *delim, const char *quote, char escape, bool e_strings, bool del_quotes, int encoding) { static char *storage = NULL;/* store the local copy of the users string * here */ static char *string = NULL; /* pointer into storage where to continue on * next call */ /* variously abused variables: */ unsigned int offset; char *start; char *p; if (s) { free(storage); /* * We may need extra space to insert delimiter nulls for adjacent * tokens. 2X the space is a gross overestimate, but it's unlikely * that this code will be used on huge strings anyway. */ storage = pg_malloc(2 * strlen(s) + 1); strcpy(storage, s); string = storage; } if (!storage) return NULL; /* skip leading whitespace */ offset = strspn(string, whitespace); start = &string[offset]; /* end of string reached? */ if (*start == '\0') { /* technically we don't need to free here, but we're nice */ free(storage); storage = NULL; string = NULL; return NULL; } /* test if delimiter character */ if (delim && strchr(delim, *start)) { /* * If not at end of string, we need to insert a null to terminate the * returned token. We can just overwrite the next character if it * happens to be in the whitespace set ... otherwise move over the * rest of the string to make room. (This is why we allocated extra * space above). */ p = start + 1; if (*p != '\0') { if (!strchr(whitespace, *p)) memmove(p + 1, p, strlen(p) + 1); *p = '\0'; string = p + 1; } else { /* at end of string, so no extra work */ string = p; } return start; } /* check for E string */ p = start; if (e_strings && (*p == 'E' || *p == 'e') && p[1] == '\'') { quote = "'"; escape = '\\'; /* if std strings before, not any more */ p++; } /* test if quoting character */ if (quote && strchr(quote, *p)) { /* okay, we have a quoted token, now scan for the closer */ char thisquote = *p++; for (; *p; p += PQmblen(p, encoding)) { if (*p == escape && p[1] != '\0') p++; /* process escaped anything */ else if (*p == thisquote && p[1] == thisquote) p++; /* process doubled quote */ else if (*p == thisquote) { p++; /* skip trailing quote */ break; } } /* * If not at end of string, we need to insert a null to terminate the * returned token. See notes above. */ if (*p != '\0') { if (!strchr(whitespace, *p)) memmove(p + 1, p, strlen(p) + 1); *p = '\0'; string = p + 1; } else { /* at end of string, so no extra work */ string = p; } /* Clean up the token if caller wants that */ if (del_quotes) strip_quotes(start, thisquote, escape, encoding); return start; } /* * Otherwise no quoting character. Scan till next whitespace, delimiter * or quote. NB: at this point, *start is known not to be '\0', * whitespace, delim, or quote, so we will consume at least one character. */ offset = strcspn(start, whitespace); if (delim) { unsigned int offset2 = strcspn(start, delim); if (offset > offset2) offset = offset2; } if (quote) { unsigned int offset2 = strcspn(start, quote); if (offset > offset2) offset = offset2; } p = start + offset; /* * If not at end of string, we need to insert a null to terminate the * returned token. See notes above. */ if (*p != '\0') { if (!strchr(whitespace, *p)) memmove(p + 1, p, strlen(p) + 1); *p = '\0'; string = p + 1; } else { /* at end of string, so no extra work */ string = p; } return start; }
/** * @brief Performs data loading. * * Invokes pg_bulkload() user-defined function with given parameters * in single transaction. * * @return exitcode (always 0). */ static int LoaderLoadMain(List *options) { PGresult *res; const char *params[1]; StringInfoData buf; int encoding; int errors; ListCell *cell; if (options == NIL) ereport(ERROR, (errcode(EXIT_FAILURE), errmsg("requires control file or command line options"))); initStringInfo(&buf); reconnect(ERROR); encoding = PQclientEncoding(connection); elog(NOTICE, "BULK LOAD START"); /* form options as text[] */ appendStringInfoString(&buf, "{\""); foreach (cell, options) { const char *item = lfirst(cell); if (buf.len > 2) appendStringInfoString(&buf, "\",\""); /* escape " and \ */ while (*item) { if (*item == '"' || *item == '\\') { appendStringInfoChar(&buf, '\\'); appendStringInfoChar(&buf, *item); item++; } else if (!IS_HIGHBIT_SET(*item)) { appendStringInfoChar(&buf, *item); item++; } else { int n = PQmblen(item, encoding); appendBinaryStringInfo(&buf, item, n); item += n; } } } appendStringInfoString(&buf, "\"}"); command("BEGIN", 0, NULL); params[0] = buf.data; res = execute("SELECT * FROM pg_bulkload($1)", 1, params); if (PQresultStatus(res) == PGRES_COPY_IN) { PQclear(res); res = RemoteLoad(connection, stdin, type_binary); if (PQresultStatus(res) != PGRES_TUPLES_OK) elog(ERROR, "copy failed: %s", PQerrorMessage(connection)); } command("COMMIT", 0, NULL); errors = atoi(PQgetvalue(res, 0, 2)) + /* parse errors */ atoi(PQgetvalue(res, 0, 3)); /* duplicate errors */ elog(NOTICE, "BULK LOAD END\n" "\t%s Rows skipped.\n" "\t%s Rows successfully loaded.\n" "\t%s Rows not loaded due to parse errors.\n" "\t%s Rows not loaded due to duplicate errors.\n" "\t%s Rows replaced with new rows.", PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 1), PQgetvalue(res, 0, 2), PQgetvalue(res, 0, 3), PQgetvalue(res, 0, 4)); PQclear(res); disconnect(); termStringInfo(&buf); if (errors > 0) { elog(WARNING, "some rows were not loaded due to errors."); return E_PG_USER; } else return 0; /* succeeded without errors */ }
/* * Parse "arg", which is a string of column IDs separated by "separator". * * Each column ID can be: * - a number from 1 to PQnfields(res) * - an unquoted column name matching (case insensitively) one of PQfname(res,...) * - a quoted column name matching (case sensitively) one of PQfname(res,...) * * If max_columns > 0, it is the max number of column IDs allowed. * * On success, return number of column IDs found (possibly 0), and return a * malloc'd array of the matching column numbers of "res" into *col_numbers. * * On failure, return -1 and set *col_numbers to NULL. */ static int parseColumnRefs(const char *arg, const PGresult *res, int **col_numbers, int max_columns, char separator) { const char *p = arg; char c; int num_cols = 0; *col_numbers = NULL; while ((c = *p) != '\0') { const char *field_start = p; bool quoted_field = false; /* first char */ if (c == '"') { quoted_field = true; p++; } while ((c = *p) != '\0') { if (c == separator && !quoted_field) break; if (c == '"') /* end of field or embedded double quote */ { p++; if (*p == '"') { if (quoted_field) { p++; continue; } } else if (quoted_field && *p == separator) break; } if (*p) p += PQmblen(p, pset.encoding); } if (p != field_start) { char *col_name; int col_num; /* enforce max_columns limit */ if (max_columns > 0 && num_cols == max_columns) { psql_error(_("No more than %d column references expected\n"), max_columns); goto errfail; } /* look up the column and add its index into *col_numbers */ col_name = pg_malloc(p - field_start + 1); memcpy(col_name, field_start, p - field_start); col_name[p - field_start] = '\0'; col_num = indexOfColumn(col_name, res); pg_free(col_name); if (col_num < 0) goto errfail; *col_numbers = (int *) pg_realloc(*col_numbers, (num_cols + 1) * sizeof(int)); (*col_numbers)[num_cols++] = col_num; } else { psql_error(_("Empty column reference\n")); goto errfail; } if (*p) p += PQmblen(p, pset.encoding); } return num_cols; errfail: pg_free(*col_numbers); *col_numbers = NULL; return -1; }