/* * 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; }
/* * equivalent_locale() * * Best effort locale-name comparison. Return false if we are not 100% sure * the locales are equivalent. */ static bool equivalent_locale(const char *loca, const char *locb) { const char *chara = strrchr(loca, '.'); const char *charb = strrchr(locb, '.'); int lencmp; /* If they don't both contain an encoding part, just do strcasecmp(). */ if (!chara || !charb) return (pg_strcasecmp(loca, locb) == 0); /* * Compare the encoding parts. Windows tends to use code page numbers for * the encoding part, which equivalent_encoding() won't like, so accept if * the strings are case-insensitive equal; otherwise use * equivalent_encoding() to compare. */ if (pg_strcasecmp(chara + 1, charb + 1) != 0 && !equivalent_encoding(chara + 1, charb + 1)) return false; /* * OK, compare the locale identifiers (e.g. en_US part of en_US.utf8). * * It's tempting to ignore non-alphanumeric chars here, but for now it's * not clear that that's necessary; just do case-insensitive comparison. */ lencmp = chara - loca; if (lencmp != charb - locb) return false; return (pg_strncasecmp(loca, locb, lencmp) == 0); }
/* * equivalent_locale() * * Best effort locale-name comparison. Return false if we are not 100% sure * the locales are equivalent. * * Note: The encoding parts of the names are ignored. This function is * currently used to compare locale names stored in pg_database, and * pg_database contains a separate encoding field. That's compared directly * in check_locale_and_encoding(). */ static bool equivalent_locale(int category, const char *loca, const char *locb) { const char *chara; const char *charb; char *canona; char *canonb; int lena; int lenb; /* * If the names are equal, the locales are equivalent. Checking this * first avoids calling setlocale() in the common case that the names * are equal. That's a good thing, if setlocale() is buggy, for example. */ if (pg_strcasecmp(loca, locb) == 0) return true; /* * Not identical. Canonicalize both names, remove the encoding parts, * and try again. */ canona = get_canonical_locale_name(category, loca); chara = strrchr(canona, '.'); lena = chara ? (chara - canona) : strlen(canona); canonb = get_canonical_locale_name(category, locb); charb = strrchr(canonb, '.'); lenb = charb ? (charb - canonb) : strlen(canonb); if (lena == lenb && pg_strncasecmp(canona, canonb, lena) == 0) return true; return false; }
/** * complete_from_list() * * Returns one of a fixed, NULL pointer terminated list of strings (if matching) * in their defined order. This can be used if there are only a fixed number of * SQL words that can appear at certain spot. */ static char * complete_from_list(const char *text, int state) { static int string_length, list_index, matches; static bool casesensitive; const char *item; /* Initialization */ if (state == 0) { list_index = 0; string_length = strlen(text); casesensitive = completion_case_sensitive; matches = 0; } while ((item = completion_charpp[list_index++])) { /* First pass is case sensitive */ if (casesensitive && strncmp(text, item, string_length) == 0) { matches++; return strdup(item); } /* Second pass is case insensitive, don't bother counting matches */ if (!casesensitive && pg_strncasecmp(text, item, string_length) == 0) { if (completion_case_sensitive) return strdup(item); else /* * If case insensitive matching was requested initially, * adjust the case according to setting. */ return fb_strdup_keyword_case(item, text); } } /* * No matches found. If we're not case insensitive already, lets switch to * being case insensitive and try again */ if (casesensitive && matches == 0) { casesensitive = false; list_index = 0; state++; return complete_from_list(text, state); } /* If no more matches, return null. */ return NULL; }
/* * Try to interpret value as boolean value. Valid values are: true, * false, yes, no, on, off, 1, 0; as well as unique prefixes thereof. */ bool ParseVariableBool(const char *value) { size_t len; if (value == NULL) return false; /* not set -> assume "off" */ len = strlen(value); if (pg_strncasecmp(value, "true", len) == 0) return true; else if (pg_strncasecmp(value, "false", len) == 0) return false; else if (pg_strncasecmp(value, "yes", len) == 0) return true; else if (pg_strncasecmp(value, "no", len) == 0) return false; /* 'o' is not unique enough */ else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) return true; else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) return false; else if (pg_strcasecmp(value, "1") == 0) return true; else if (pg_strcasecmp(value, "0") == 0) return false; else { /* NULL is treated as false, so a non-matching value is 'true' */ psql_error("unrecognized Boolean value; assuming \"on\"\n"); return true; } }
/* * Transform a relation options list (list of DefElem) into the text array * format that is kept in pg_class.reloptions. * * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing * reloptions value (possibly NULL), and we replace or remove entries * as needed. * * If ignoreOids is true, then we should ignore any occurrence of "oids" * in the list (it will be or has been handled by interpretOidsOption()). * * Note that this is not responsible for determining whether the options * are valid. * * Both oldOptions and the result are text arrays (or NULL for "default"), * but we declare them as Datums to avoid including array.h in reloptions.h. */ Datum transformRelOptions(Datum oldOptions, List *defList, bool ignoreOids, bool isReset) { Datum result; ArrayBuildState *astate; ListCell *cell; /* no change if empty list */ if (defList == NIL) return oldOptions; /* We build new array using accumArrayResult */ astate = NULL; /* Copy any oldOptions that aren't to be replaced */ if (DatumGetPointer(oldOptions) != 0) { ArrayType *array = DatumGetArrayTypeP(oldOptions); Datum *oldoptions; int noldoptions; int i; Assert(ARR_ELEMTYPE(array) == TEXTOID); deconstruct_array(array, TEXTOID, -1, false, 'i', &oldoptions, NULL, &noldoptions); for (i = 0; i < noldoptions; i++) { text *oldoption = DatumGetTextP(oldoptions[i]); char *text_str = VARDATA(oldoption); int text_len = VARSIZE(oldoption) - VARHDRSZ; /* Search for a match in defList */ foreach(cell, defList) { DefElem *def = lfirst(cell); int kw_len = strlen(def->defname); if (text_len > kw_len && text_str[kw_len] == '=' && pg_strncasecmp(text_str, def->defname, kw_len) == 0) break; } if (!cell) { /* No match, so keep old option */ astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext); } }
static void SpecialTags(TParser * prs) { switch (prs->state->lencharlexeme) { case 8: /* </script */ if (pg_strncasecmp(prs->lexeme, "</script", 8) == 0) prs->ignore = false; break; case 7: /* <script || </style */ if (pg_strncasecmp(prs->lexeme, "</style", 7) == 0) prs->ignore = false; else if (pg_strncasecmp(prs->lexeme, "<script", 7) == 0) prs->ignore = true; break; case 6: /* <style */ if (pg_strncasecmp(prs->lexeme, "<style", 6) == 0) prs->ignore = true; break; default: break; } }
static int weekday_search(const WeekDays *weekdays, const char *str, int len) { int i; for (i = 0; i < 7; i++) { int n = strlen(weekdays->names[i]); if (n > len) continue; /* too short */ if (pg_strncasecmp(weekdays->names[i], str, n) == 0) return i; } return -1; /* not found */ }
static bool check_special_value(char *ptr, double *retval, char **endptr) { if (pg_strncasecmp(ptr, "NaN", 3) == 0) { *retval = get_float8_nan(); *endptr = ptr + 3; return true; } else if (pg_strncasecmp(ptr, "Infinity", 8) == 0) { *retval = get_float8_infinity(); *endptr = ptr + 8; return true; } else if (pg_strncasecmp(ptr, "-Infinity", 9) == 0) { *retval = -get_float8_infinity(); *endptr = ptr + 9; return true; } return false; }
static int ora_seq_prefix_search(const char *name, STRING_PTR_FIELD_TYPE array[], int max) { int i; if (!*name) return -1; for (i = 0; array[i]; i++) { if (pg_strncasecmp(name, array[i], max) == 0) return i; } return -1; /* not found */ }
relopt_value * get_option_set(relopt_value *options, int num_options, const char *opt_name) { int i; int opt_name_len; int cmp_len; opt_name_len = strlen(opt_name); for (i = 0; i < num_options; ++i) { cmp_len = options[i].gen->namelen > opt_name_len ? opt_name_len : options[i].gen->namelen; if (options[i].isset && pg_strncasecmp(options[i].gen->name, opt_name, cmp_len) == 0) return &options[i]; } return NULL; }
/** * complete_from_query() * * Dynamically generate tab completion candidates from the specified query */ static char * complete_from_query(const char *text, int state) { static int list_index, string_length; static FBresult *result = NULL; char *query; if (state == 0) { string_length = strlen(text); list_index = 0; query = malloc(strlen(completion_charp) * 2 + 1); if (completion_info_charp != NULL) { sprintf(query, completion_charp, string_length, text, completion_info_charp); } else { sprintf(query, completion_charp, string_length, text); } result = FQexecTransaction(fset.conn, query); free(query); } /* Find something that matches */ if (result && FQresultStatus(result) == FBRES_TUPLES_OK) { const char *item; while (list_index < FQntuples(result) && (item = FQgetvalue(result, list_index++, 0))) { if (pg_strncasecmp(text, item, string_length) == 0) return strdup(item); } } FQclear(result); result = NULL; return NULL; }
/* * scan_pl specified directory for a case-insensitive match to fname * (of length fnamelen --- fname may not be null terminated!). If found, * copy the actual filename into canonname and return true. */ static bool scan_directory_ci( const char *dirname, const char *fname, int fnamelen, char *canonname, int canonnamelen) { bool found = false; DIR* dirdesc; struct dirent *direntry; dirdesc = alloc_dir(dirname); if (!dirdesc) { ereport(LOG, ( errcode_file_access(), errmsg("could not open directory \"%s\": %m", dirname))); return false; } while ((direntry = read_dir(dirdesc, dirname)) != NULL) { /* * Ignore . and .., plus any other "hidden" files. This is a security * measure to prevent access to files outside the timezone directory. */ if (direntry->d_name[0] == '.') continue; if (strlen(direntry->d_name) == fnamelen && pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0) { /* Found our match */ strlcpy(canonname, direntry->d_name, canonnamelen); found = true; break; } } free_dir(dirdesc); return found; }
/** * create_or_drop_keyword_generator() * * List of keywords which can follow CREATE or DROP */ static char * create_or_drop_keyword_generator(const char *text, int state) { static int list_index, string_length; const char *name; /* If this is the first time for this completion, init some values */ if (state == 0) { list_index = 0; string_length = strlen(text); } /* find something that matches */ while ((name = words_after_create[list_index++].name)) { if (pg_strncasecmp(name, text, string_length) == 0) return fb_strdup_keyword_case(name, text); } /* if nothing matches, return NULL */ return NULL; }
/* * Try to interpret "value" as a boolean value, and if successful, * store it in *result. Otherwise don't clobber *result. * * Valid values are: true, false, yes, no, on, off, 1, 0; as well as unique * prefixes thereof. * * "name" is the name of the variable we're assigning to, to use in error * report if any. Pass name == NULL to suppress the error report. * * Return true when "value" is syntactically valid, false otherwise. */ bool ParseVariableBool(const char *value, const char *name, bool *result) { size_t len; bool valid = true; /* Treat "unset" as an empty string, which will lead to error below */ if (value == NULL) value = ""; len = strlen(value); if (len > 0 && pg_strncasecmp(value, "true", len) == 0) *result = true; else if (len > 0 && pg_strncasecmp(value, "false", len) == 0) *result = false; else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0) *result = true; else if (len > 0 && pg_strncasecmp(value, "no", len) == 0) *result = false; /* 'o' is not unique enough */ else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) *result = true; else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) *result = false; else if (pg_strcasecmp(value, "1") == 0) *result = true; else if (pg_strcasecmp(value, "0") == 0) *result = false; else { /* string is not recognized; don't clobber *result */ if (name) psql_error("unrecognized value \"%s\" for \"%s\": boolean expected\n", value, name); valid = false; } return valid; }
/* * assign_datestyle: GUC assign_hook for datestyle */ const char * assign_datestyle(const char *value, bool doit, GucSource source) { int newDateStyle = DateStyle; int newDateOrder = DateOrder; bool have_style = false; bool have_order = false; bool ok = true; char *rawstring; char *result; List *elemlist; ListCell *l; /* Need a modifiable copy of string */ rawstring = pstrdup(value); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawstring, ',', &elemlist)) { /* syntax error in list */ pfree(rawstring); list_free(elemlist); ereport(GUC_complaint_elevel(source), (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid list syntax for parameter \"datestyle\""))); return NULL; } foreach(l, elemlist) { char *tok = (char *) lfirst(l); /* Ugh. Somebody ought to write a table driven version -- mjl */ if (pg_strcasecmp(tok, "ISO") == 0) { if (have_style && newDateStyle != USE_ISO_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_ISO_DATES; have_style = true; } else if (pg_strcasecmp(tok, "SQL") == 0) { if (have_style && newDateStyle != USE_SQL_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_SQL_DATES; have_style = true; } else if (pg_strncasecmp(tok, "POSTGRES", 8) == 0) { if (have_style && newDateStyle != USE_POSTGRES_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_POSTGRES_DATES; have_style = true; } else if (pg_strcasecmp(tok, "GERMAN") == 0) { if (have_style && newDateStyle != USE_GERMAN_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_GERMAN_DATES; have_style = true; /* GERMAN also sets DMY, unless explicitly overridden */ if (!have_order) newDateOrder = DATEORDER_DMY; } else if (pg_strcasecmp(tok, "YMD") == 0) { if (have_order && newDateOrder != DATEORDER_YMD) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_YMD; have_order = true; } else if (pg_strcasecmp(tok, "DMY") == 0 || pg_strncasecmp(tok, "EURO", 4) == 0) { if (have_order && newDateOrder != DATEORDER_DMY) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_DMY; have_order = true; } else if (pg_strcasecmp(tok, "MDY") == 0 || pg_strcasecmp(tok, "US") == 0 || pg_strncasecmp(tok, "NONEURO", 7) == 0) { if (have_order && newDateOrder != DATEORDER_MDY) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_MDY; have_order = true; } else if (pg_strcasecmp(tok, "DEFAULT") == 0) { /* * Easiest way to get the current DEFAULT state is to fetch the * DEFAULT string from guc.c and recursively parse it. * * We can't simply "return assign_datestyle(...)" because we need * to handle constructs like "DEFAULT, ISO". */ int saveDateStyle = DateStyle; int saveDateOrder = DateOrder; const char *subval; subval = assign_datestyle(GetConfigOptionResetString("datestyle"), true, source); if (!have_style) newDateStyle = DateStyle; if (!have_order) newDateOrder = DateOrder; DateStyle = saveDateStyle; DateOrder = saveDateOrder; if (!subval) { ok = false; break; } /* Here we know that our own return value is always malloc'd */ /* when doit is true */ free((char *) subval); } else { ereport(GUC_complaint_elevel(source), (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized \"datestyle\" key word: \"%s\"", tok))); ok = false; break; } }
static void doCustom(CState * state, int n, int debug) { PGresult *res; CState *st = &state[n]; Command **commands; top: commands = sql_files[st->use_file]; if (st->listen) { /* are we receiver? */ if (commands[st->state]->type == SQL_COMMAND) { if (debug) fprintf(stderr, "client %d receiving\n", n); if (!PQconsumeInput(st->con)) { /* there's something wrong */ fprintf(stderr, "Client %d aborted in state %d. Probably the backend died while processing.\n", n, st->state); remains--; /* I've aborted */ PQfinish(st->con); st->con = NULL; return; } if (PQisBusy(st->con)) return; /* don't have the whole result yet */ } /* * transaction finished: record the time it took in the log */ if (use_log && commands[st->state + 1] == NULL) { double diff; struct timeval now; gettimeofday(&now, NULL); diff = (int) (now.tv_sec - st->txn_begin.tv_sec) * 1000000.0 + (int) (now.tv_usec - st->txn_begin.tv_usec); fprintf(LOGFILE, "%d %d %.0f\n", st->id, st->cnt, diff); } if (commands[st->state]->type == SQL_COMMAND) { res = PQgetResult(st->con); if (pg_strncasecmp(commands[st->state]->argv[0], "select", 6) != 0) { if (check(state, res, n, PGRES_COMMAND_OK)) return; } else { if (check(state, res, n, PGRES_TUPLES_OK)) return; } PQclear(res); discard_response(st); } if (commands[st->state + 1] == NULL) { if (is_connect) { PQfinish(st->con); st->con = NULL; } if (++st->cnt >= nxacts) { remains--; /* I've done */ if (st->con != NULL) { PQfinish(st->con); st->con = NULL; } return; } } /* increment state counter */ st->state++; if (commands[st->state] == NULL) { st->state = 0; st->use_file = getrand(0, num_files - 1); commands = sql_files[st->use_file]; } } if (st->con == NULL) { if ((st->con = doConnect()) == NULL) { fprintf(stderr, "Client %d aborted in establishing connection.\n", n); remains--; /* I've aborted */ PQfinish(st->con); st->con = NULL; return; } } if (use_log && st->state == 0) gettimeofday(&(st->txn_begin), NULL); if (commands[st->state]->type == SQL_COMMAND) { char *sql; if ((sql = strdup(commands[st->state]->argv[0])) == NULL || (sql = assignVariables(st, sql)) == NULL) { fprintf(stderr, "out of memory\n"); st->ecnt++; return; } if (debug) fprintf(stderr, "client %d sending %s\n", n, sql); if (PQsendQuery(st->con, sql) == 0) { if (debug) fprintf(stderr, "PQsendQuery(%s)failed\n", sql); st->ecnt++; } else { st->listen = 1; /* flags that should be listened */ } free(sql); } else if (commands[st->state]->type == META_COMMAND) { int argc = commands[st->state]->argc, i; char **argv = commands[st->state]->argv; if (debug) { fprintf(stderr, "client %d executing \\%s", n, argv[0]); for (i = 1; i < argc; i++) fprintf(stderr, " %s", argv[i]); fprintf(stderr, "\n"); } if (pg_strcasecmp(argv[0], "setrandom") == 0) { char *var; int min, max; char res[64]; if (*argv[2] == ':') { if ((var = getVariable(st, argv[2] + 1)) == NULL) { fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[2]); st->ecnt++; return; } min = atoi(var); } else min = atoi(argv[2]); #ifdef NOT_USED if (min < 0) { fprintf(stderr, "%s: invalid minimum number %d\n", argv[0], min); st->ecnt++; return; } #endif if (*argv[3] == ':') { if ((var = getVariable(st, argv[3] + 1)) == NULL) { fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[3]); st->ecnt++; return; } max = atoi(var); } else max = atoi(argv[3]); if (max < min || max > MAX_RANDOM_VALUE) { fprintf(stderr, "%s: invalid maximum number %d\n", argv[0], max); st->ecnt++; return; } #ifdef DEBUG printf("min: %d max: %d random: %d\n", min, max, getrand(min, max)); #endif snprintf(res, sizeof(res), "%d", getrand(min, max)); if (putVariable(st, argv[1], res) == false) { fprintf(stderr, "%s: out of memory\n", argv[0]); st->ecnt++; return; } st->listen = 1; } else if (pg_strcasecmp(argv[0], "set") == 0) { char *var; int ope1, ope2; char res[64]; if (*argv[2] == ':') { if ((var = getVariable(st, argv[2] + 1)) == NULL) { fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[2]); st->ecnt++; return; } ope1 = atoi(var); } else ope1 = atoi(argv[2]); if (argc < 5) snprintf(res, sizeof(res), "%d", ope1); else { if (*argv[4] == ':') { if ((var = getVariable(st, argv[4] + 1)) == NULL) { fprintf(stderr, "%s: undefined variable %s\n", argv[0], argv[4]); st->ecnt++; return; } ope2 = atoi(var); } else ope2 = atoi(argv[4]); if (strcmp(argv[3], "+") == 0) snprintf(res, sizeof(res), "%d", ope1 + ope2); else if (strcmp(argv[3], "-") == 0) snprintf(res, sizeof(res), "%d", ope1 - ope2); else if (strcmp(argv[3], "*") == 0) snprintf(res, sizeof(res), "%d", ope1 * ope2); else if (strcmp(argv[3], "/") == 0) { if (ope2 == 0) { fprintf(stderr, "%s: division by zero\n", argv[0]); st->ecnt++; return; } snprintf(res, sizeof(res), "%d", ope1 / ope2); } else { fprintf(stderr, "%s: unsupported operator %s\n", argv[0], argv[3]); st->ecnt++; return; } } if (putVariable(st, argv[1], res) == false) { fprintf(stderr, "%s: out of memory\n", argv[0]); st->ecnt++; return; } st->listen = 1; } goto top; } }
/* * 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; }
bool do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) { size_t vallen = 0; psql_assert(param); if (value) vallen = strlen(value); /* set format */ if (strcmp(param, "format") == 0) { if (!value) ; else if (pg_strncasecmp("unaligned", value, vallen) == 0) popt->topt.format = PRINT_UNALIGNED; else if (pg_strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; else if (pg_strncasecmp("wrapped", value, vallen) == 0) popt->topt.format = PRINT_WRAPPED; else if (pg_strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (pg_strncasecmp("latex", value, vallen) == 0) popt->topt.format = PRINT_LATEX; else if (pg_strncasecmp("troff-ms", value, vallen) == 0) popt->topt.format = PRINT_TROFF_MS; else { psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n"); return false; } if (!quiet) printf(_("Output format is %s.\n"), _align2string(popt->topt.format)); } /* set table line style */ else if (strcmp(param, "linestyle") == 0) { if (!value) ; else if (pg_strncasecmp("ascii", value, vallen) == 0) popt->topt.line_style = &pg_asciiformat; else if (pg_strncasecmp("old-ascii", value, vallen) == 0) popt->topt.line_style = &pg_asciiformat_old; else if (pg_strncasecmp("unicode", value, vallen) == 0) popt->topt.line_style = &pg_utf8format; else { psql_error("\\pset: allowed line styles are ascii, old-ascii, unicode\n"); return false; } if (!quiet) printf(_("Line style is %s.\n"), get_line_style(&popt->topt)->name); } /* set border style/width */ else if (strcmp(param, "border") == 0) { if (value) popt->topt.border = atoi(value); if (!quiet) printf(_("Border style is %d.\n"), popt->topt.border); } /* set expanded/vertical mode */ else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0) { if (value) popt->topt.expanded = ParseVariableBool(value); else popt->topt.expanded = !popt->topt.expanded; if (!quiet) printf(popt->topt.expanded ? _("Expanded display is on.\n") : _("Expanded display is off.\n")); } /* locale-aware numeric output */ else if (strcmp(param, "numericlocale") == 0) { if (value) popt->topt.numericLocale = ParseVariableBool(value); else popt->topt.numericLocale = !popt->topt.numericLocale; if (!quiet) { if (popt->topt.numericLocale) puts(_("Showing locale-adjusted numeric output.")); else puts(_("Locale-adjusted numeric output is off.")); } } /* null display */ else if (strcmp(param, "null") == 0) { if (value) { free(popt->nullPrint); popt->nullPrint = pg_strdup(value); } if (!quiet) printf(_("Null display is \"%s\".\n"), popt->nullPrint ? popt->nullPrint : ""); } /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { if (value) { free(popt->topt.fieldSep); popt->topt.fieldSep = pg_strdup(value); } if (!quiet) printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep); } /* record separator for unaligned text */ else if (strcmp(param, "recordsep") == 0) { if (value) { free(popt->topt.recordSep); popt->topt.recordSep = pg_strdup(value); } if (!quiet) { if (strcmp(popt->topt.recordSep, "\n") == 0) printf(_("Record separator is <newline>.")); else printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep); } } /* toggle between full and tuples-only format */ else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0) { if (value) popt->topt.tuples_only = ParseVariableBool(value); else popt->topt.tuples_only = !popt->topt.tuples_only; if (!quiet) { if (popt->topt.tuples_only) puts(_("Showing only tuples.")); else puts(_("Tuples only is off.")); } } /* set title override */ else if (strcmp(param, "title") == 0) { free(popt->title); if (!value) popt->title = NULL; else popt->title = pg_strdup(value); if (!quiet) { if (popt->title) printf(_("Title is \"%s\".\n"), popt->title); else printf(_("Title is unset.\n")); } } /* set HTML table tag options */ else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0) { free(popt->topt.tableAttr); if (!value) popt->topt.tableAttr = NULL; else popt->topt.tableAttr = pg_strdup(value); if (!quiet) { if (popt->topt.tableAttr) printf(_("Table attribute is \"%s\".\n"), popt->topt.tableAttr); else printf(_("Table attributes unset.\n")); } } /* toggle use of pager */ else if (strcmp(param, "pager") == 0) { if (value && pg_strcasecmp(value, "always") == 0) popt->topt.pager = 2; else if (value) if (ParseVariableBool(value)) popt->topt.pager = 1; else popt->topt.pager = 0; else if (popt->topt.pager == 1) popt->topt.pager = 0; else popt->topt.pager = 1; if (!quiet) { if (popt->topt.pager == 1) puts(_("Pager is used for long output.")); else if (popt->topt.pager == 2) puts(_("Pager is always used.")); else puts(_("Pager usage is off.")); } } /* disable "(x rows)" footer */ else if (strcmp(param, "footer") == 0) { if (value) popt->default_footer = ParseVariableBool(value); else popt->default_footer = !popt->default_footer; if (!quiet) { if (popt->default_footer) puts(_("Default footer is on.")); else puts(_("Default footer is off.")); } } /* set border style/width */ else if (strcmp(param, "columns") == 0) { if (value) popt->topt.columns = atoi(value); if (!quiet) printf(_("Target width for \"wrapped\" format is %d.\n"), popt->topt.columns); } else { psql_error("\\pset: unknown option: %s\n", param); return false; } return true; }
/* * Transform a relation options list (list of DefElem) into the text array * format that is kept in pg_class.reloptions, including only those options * that are in the passed namescpace___. The output values do not include the * namescpace___. * * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing * reloptions value (possibly NULL), and we replace or remove entries * as needed. * * If ignoreOids is true, then we should ignore any occurrence of "oids" * in the list (it will be or has been handled by interpretOidsOption()). * * Note that this is not responsible for determining whether the options * are valid, but it does check that namespaces for all the options given are * listed in validnsps. The NULL namescpace___ is always valid and need not be * explicitly listed. Passing a NULL pointer means that only the NULL * namescpace___ is valid. * * Both oldOptions and the result are text arrays (or NULL for "default"), * but we declare them as Datums to avoid including array.h in reloptions.h. */ Datum transformRelOptions(Datum oldOptions, List *defList, char *namspace, char *validnsps[], bool ignoreOids, bool isReset) { Datum result; ArrayBuildState *astate; ListCell *cell; /* no change if empty list */ if (defList == NIL) return oldOptions; /* We build new___ array using accumArrayResult */ astate = NULL; /* Copy any oldOptions that aren't to be replaced */ if (PointerIsValid(DatumGetPointer(oldOptions))) { ArrayType *array = DatumGetArrayTypeP(oldOptions); Datum *oldoptions; int noldoptions; int i; deconstruct_array(array, TEXTOID, -1, false, 'i', &oldoptions, NULL, &noldoptions); for (i = 0; i < noldoptions; i++) { text *oldoption = DatumGetTextP(oldoptions[i]); char *text_str = VARDATA(oldoption); int text_len = VARSIZE(oldoption) - VARHDRSZ; /* Search for a match in defList */ foreach(cell, defList) { DefElem *def = (DefElem *) lfirst(cell); int kw_len; /* ignore if not in the same namescpace___ */ if (namspace == NULL) { if (def->defnamespace != NULL) continue; } else if (def->defnamespace == NULL) continue; else if (pg_strcasecmp(def->defnamespace, namspace) != 0) continue; kw_len = strlen(def->defname); if (text_len > kw_len && text_str[kw_len] == '=' && pg_strncasecmp(text_str, def->defname, kw_len) == 0) break; } if (!cell) { /* No match, so keep old option */ astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext); } } }
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, * etc. */ PQExpBuffer history_buf; /* earlier lines of a multi-line command, not * yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; /* Save old settings */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; /* Create working state */ scan_state = psql_scan_create(); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); if (PQExpBufferBroken(query_buf) || PQExpBufferBroken(previous_buf) || PQExpBufferBroken(history_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Clean up after a previous Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C. */ successResult = EXIT_USER; break; } cancel_pressed = false; } /* * Establish longjmp destination for exiting from wait-for-input. We * must re-do this each time through the loop for safety, since the * jmpbuf might get changed during command execution. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ psql_scan_finish(scan_state); psql_scan_reset(scan_state); resetPQExpBuffer(query_buf); resetPQExpBuffer(history_buf); count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; cancel_pressed = false; if (pset.cur_cmd_interactive) putc('\n', stdout); else { successResult = EXIT_USER; break; } } fflush(stdout); /* * get another line */ if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status)); } else { line = gets_fromFile(source); if (!line && ferror(source)) successResult = EXIT_FAILURE; } /* * query_buf holds query already accumulated. line is the malloc'd * new line of input (note it must be freed before looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < GetVariableNum(pset.vars, "IGNOREEOF", 0, 10, false)) { if (!pset.quiet) printf(_("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(pset.quiet ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* ignore UTF-8 Unicode byte-order mark */ if (pset.lineno == 1 && pset.encoding == PG_UTF8 && strncmp(line, "\xef\xbb\xbf", 3) == 0) memmove(line, line + 3, strlen(line + 3) + 1); /* nothing left on line? then ignore */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* A request for help? Be friendly and give them some guidance */ if (pset.cur_cmd_interactive && query_buf->len == 0 && pg_strncasecmp(line, "help", 4) == 0 && (line[4] == '\0' || line[4] == ';' || isspace((unsigned char) line[4]))) { free(line); puts(_("You are using psql, the command-line interface to PostgreSQL.")); printf(_("Type: \\copyright for distribution terms\n" " \\h for help with SQL commands\n" " \\? for help with psql commands\n" " \\g or terminate with semicolon to execute query\n" " \\q to quit\n")); fflush(stdout); continue; } /* echo back if flag is set */ if (pset.echo == PSQL_ECHO_ALL && !pset.cur_cmd_interactive) puts(line); fflush(stdout); /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = pset.on_error_stop; /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line)); success = true; line_saved_in_history = false; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; if (PQExpBufferBroken(query_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* * Send command if semicolon found, or if end of line and we're in * single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && pset.singleline)) { /* * Save query in history. We use history_buf to accumulate * multi-line queries into a single history entry. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute query */ success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a * line contains only a backslash command. Also, in this * situation we force out any previous lines as a separate * history entry; we don't want SQL and backslash commands * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) { query_buf->data[--query_buf->len] = '\0'; pg_send_history(history_buf); } added_nl_pos = -1; /* save backslash command in history */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, query_buf->len > 0 ? query_buf : previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) && query_buf->len == 0) { /* copy previous buffer to current for handling */ appendPQExpBufferStr(query_buf, previous_buf->data); } if (slashCmdStatus == PSQL_CMD_SEND) { success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); line = pg_strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); psql_scan_setup(scan_state, line, strlen(line)); line_saved_in_history = false; prompt_status = PROMPT_READY; } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } /* Add line to pending history if we didn't execute anything yet */ if (pset.cur_cmd_interactive && !line_saved_in_history) pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); if (slashCmdStatus == PSQL_CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * Process query at the end of file without a semicolon */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { /* save query in history */ if (pset.cur_cmd_interactive) pg_send_history(history_buf); /* execute query */ success = SendQuery(query_buf->data); if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to * itself at the top of its loop, before any further interactive input * happens. */ sigint_interrupt_enabled = false; destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */
/* * check_datestyle: GUC check_hook for datestyle */ bool check_datestyle(char **newval, void **extra, GucSource source) { int newDateStyle = DateStyle; int newDateOrder = DateOrder; bool have_style = false; bool have_order = false; bool ok = true; char *rawstring; int *myextra; char *result; List *elemlist; ListCell *l; /* Need a modifiable copy of string */ rawstring = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawstring, ',', &elemlist)) { /* syntax error in list */ GUC_check_errdetail("List syntax is invalid."); pfree(rawstring); list_free(elemlist); return false; } foreach(l, elemlist) { char *tok = (char *) lfirst(l); /* Ugh. Somebody ought to write a table driven version -- mjl */ if (pg_strcasecmp(tok, "ISO") == 0) { if (have_style && newDateStyle != USE_ISO_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_ISO_DATES; have_style = true; } else if (pg_strcasecmp(tok, "SQL") == 0) { if (have_style && newDateStyle != USE_SQL_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_SQL_DATES; have_style = true; } else if (pg_strncasecmp(tok, "POSTGRES", 8) == 0) { if (have_style && newDateStyle != USE_POSTGRES_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_POSTGRES_DATES; have_style = true; } else if (pg_strcasecmp(tok, "GERMAN") == 0) { if (have_style && newDateStyle != USE_GERMAN_DATES) ok = false; /* conflicting styles */ newDateStyle = USE_GERMAN_DATES; have_style = true; /* GERMAN also sets DMY, unless explicitly overridden */ if (!have_order) newDateOrder = DATEORDER_DMY; } else if (pg_strcasecmp(tok, "YMD") == 0) { if (have_order && newDateOrder != DATEORDER_YMD) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_YMD; have_order = true; } else if (pg_strcasecmp(tok, "DMY") == 0 || pg_strncasecmp(tok, "EURO", 4) == 0) { if (have_order && newDateOrder != DATEORDER_DMY) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_DMY; have_order = true; } else if (pg_strcasecmp(tok, "MDY") == 0 || pg_strcasecmp(tok, "US") == 0 || pg_strncasecmp(tok, "NONEURO", 7) == 0) { if (have_order && newDateOrder != DATEORDER_MDY) ok = false; /* conflicting orders */ newDateOrder = DATEORDER_MDY; have_order = true; } else if (pg_strcasecmp(tok, "DEFAULT") == 0) { /* * Easiest way to get the current DEFAULT state is to fetch the * DEFAULT string from guc.c and recursively parse it. * * We can't simply "return check_datestyle(...)" because we need * to handle constructs like "DEFAULT, ISO". */ char *subval; void *subextra = NULL; subval = strdup(GetConfigOptionResetString("datestyle")); if (!subval) { ok = false; break; } if (!check_datestyle(&subval, &subextra, source)) { free(subval); ok = false; break; } myextra = (int *) subextra; if (!have_style) newDateStyle = myextra[0]; if (!have_order) newDateOrder = myextra[1]; free(subval); free(subextra); } else { GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); pfree(rawstring); list_free(elemlist); return false; } }
static bool GetNextArgument(const char *ptr, char **arg, Oid *argtype, const char **endptr, const char *path, bool argistype) { const char *p; bool first_arg = false; int len; p = ptr; while (isspace((unsigned char) *p)) p++; if (*p == '(') first_arg = true; else if (*p == ',') first_arg = false; else if (*p == ')') { p++; while (isspace((unsigned char) *p)) p++; if (*p != '\0') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); *endptr = p; return false; } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); p++; while (isspace((unsigned char) *p)) p++; if (first_arg && *p == ')') { p++; while (isspace((unsigned char) *p)) p++; if (*p != '\0') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); *endptr = p; return false; } *argtype = UNKNOWNOID; if (argistype) { /* argument is data type name */ const char *startptr; bool inparenthesis; int nparentheses; char *str; int32 typmod; startptr = p; inparenthesis = false; nparentheses = 0; while (*p != '\0' && (inparenthesis || (*p != ',' && *p != ')'))) { if (*p == '(') { if (inparenthesis) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); inparenthesis = true; nparentheses++; if (nparentheses > 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); } if (*p == ')') inparenthesis = false; p++; } if (p == startptr) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); while (isspace((unsigned char) *(p - 1))) p--; len = p - startptr; str = palloc(len + 1); memcpy(str, startptr, len); str[len] = '\0'; *arg = str; /* Use full parser to resolve the type name */ parseTypeString(*arg, argtype, &typmod); } else if (*p == '\'') { /* argument is string constants */ StringInfoData buf; initStringInfo(&buf); p++; while (*p != '\0') { if (*p == '\'') { if (*(p + 1) == '\'') p++; else break; } appendStringInfoCharMacro(&buf, *p++); } if (*p != '\'') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); p++; *arg = buf.data; } else if (pg_strncasecmp(p, "NULL", strlen("NULL")) == 0) { /* argument is NULL */ p += strlen("NULL"); *arg = NULL; } else { /* argument is numeric constants */ bool minus; const char *startptr; char *str; int64 val64; /* parse plus operator and minus operator */ minus = false; while (*p == '+' || *p == '-') { if (*p == '-') { /* this is standard SQL comment */ if (*(p + 1) == '-') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); minus = !minus; } p++; while (isspace((unsigned char) *p)) p++; } startptr = p; while (!isspace((unsigned char) *p) && *p != ',' && *p != ')' && *p != '\0') p++; len = p - startptr; if (len == 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("function call syntax error: %s", path))); str = palloc(len + 2); snprintf(str, len + 2, "%c%s", minus ? '-' : '+', startptr); /* could be an oversize integer as well as a float ... */ if (scanint8(str, true, &val64)) { /* * It might actually fit in int32. Probably only INT_MIN can * occur, but we'll code the test generally just to be sure. */ int32 val32 = (int32) val64; if (val64 == (int64) val32) *argtype = INT4OID; else *argtype = INT8OID; } else { /* arrange to report location if numeric_in() fails */ DirectFunctionCall3(numeric_in, CStringGetDatum(str + 1), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); *argtype = NUMERICOID; } /* Check for numeric constants. */ *arg = str; } *endptr = p; return true; }
/* * Return a datum that is array of "name=value" strings for each * appendonly storage option in opts. This datum is used to populate * pg_class.reloptions during relation creation. * * To avoid catalog bloat, we only create "name=value" item for those * values in opts that are not specified in WITH clause and are * different from their initial defaults. */ Datum transformAOStdRdOptions(StdRdOptions *opts, Datum withOpts) { char *strval; char intval[MAX_SOPT_VALUE_LEN]; Datum *withDatums = NULL; text *t; int len, i, withLen, soptLen, nWithOpts = 0; ArrayType *withArr; ArrayBuildState *astate = NULL; bool foundAO = false, foundBlksz = false, foundComptype = false, foundComplevel = false, foundChecksum = false, foundOrientation = false; /* * withOpts must be parsed to see if an option was spcified in WITH() * clause. */ if (DatumGetPointer(withOpts) != NULL) { withArr = DatumGetArrayTypeP(withOpts); Assert(ARR_ELEMTYPE(withArr) == TEXTOID); deconstruct_array(withArr, TEXTOID, -1, false, 'i', &withDatums, NULL, &nWithOpts); /* * Include options specified in WITH() clause in the same order as * they are specified. Otherwise we will end up with regression * failures due to diff with respect to answer file. */ for (i = 0; i < nWithOpts; ++i) { t = DatumGetTextP(withDatums[i]); strval = VARDATA(t); /* * Text datums are usually not null terminated. We must never * access beyond their length. */ withLen = VARSIZE(t) - VARHDRSZ; /* * withDatums[i] may not be used directly. It may be e.g. * "aPPendOnly=tRue". Therefore we don't set it as reloptions as * is. */ soptLen = strlen(SOPT_APPENDONLY); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_APPENDONLY, soptLen) == 0) { foundAO = true; strval = opts->appendonly ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_APPENDONLY) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_APPENDONLY, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_BLOCKSIZE); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_BLOCKSIZE, soptLen) == 0) { foundBlksz = true; snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->blocksize); len = VARHDRSZ + strlen(SOPT_BLOCKSIZE) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_BLOCKSIZE, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_COMPTYPE); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_COMPTYPE, soptLen) == 0) { foundComptype = true; /* * Record "none" as compresstype in reloptions if it was * explicitly specified in WITH clause. */ strval = (opts->compresstype != NULL) ? opts->compresstype : "none"; len = VARHDRSZ + strlen(SOPT_COMPTYPE) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPTYPE, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_COMPLEVEL); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_COMPLEVEL, soptLen) == 0) { foundComplevel = true; snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->compresslevel); len = VARHDRSZ + strlen(SOPT_COMPLEVEL) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPLEVEL, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_CHECKSUM); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_CHECKSUM, soptLen) == 0) { foundChecksum = true; strval = opts->checksum ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_CHECKSUM) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_CHECKSUM, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } soptLen = strlen(SOPT_ORIENTATION); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_ORIENTATION, soptLen) == 0) { foundOrientation = true; strval = opts->columnstore ? "column" : "row"; len = VARHDRSZ + strlen(SOPT_ORIENTATION) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_ORIENTATION, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* * Record fillfactor only if it's specified in WITH clause. * Default fillfactor is assumed otherwise. */ soptLen = strlen(SOPT_FILLFACTOR); if (withLen > soptLen && pg_strncasecmp(strval, SOPT_FILLFACTOR, soptLen) == 0) { snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->fillfactor); len = VARHDRSZ + strlen(SOPT_FILLFACTOR) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_FILLFACTOR, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } } } /* Include options that are not defaults and not already included. */ if ((opts->appendonly != AO_DEFAULT_APPENDONLY) && !foundAO) { /* appendonly */ strval = opts->appendonly ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_APPENDONLY) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_APPENDONLY, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } if ((opts->blocksize != AO_DEFAULT_BLOCKSIZE) && !foundBlksz) { /* blocksize */ snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->blocksize); len = VARHDRSZ + strlen(SOPT_BLOCKSIZE) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_BLOCKSIZE, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* * Record compression options only if compression is enabled. No need to * check compresstype here as by the time we get here, "opts" should have * been set by default_reloptions() correctly. */ if (opts->compresslevel > AO_DEFAULT_COMPRESSLEVEL && opts->compresstype != NULL) { if (!foundComptype && ( (pg_strcasecmp(opts->compresstype, AO_DEFAULT_COMPRESSTYPE) == 0 && opts->compresslevel == 1 && !foundComplevel) || pg_strcasecmp(opts->compresstype, AO_DEFAULT_COMPRESSTYPE) != 0)) { /* compress type */ strval = opts->compresstype; len = VARHDRSZ + strlen(SOPT_COMPTYPE) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPTYPE, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } /* When compression is enabled, default compresslevel is 1. */ if ((opts->compresslevel != 1) && !foundComplevel) { /* compress level */ snprintf(intval, MAX_SOPT_VALUE_LEN, "%d", opts->compresslevel); len = VARHDRSZ + strlen(SOPT_COMPLEVEL) + 1 + strlen(intval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_COMPLEVEL, intval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } } if ((opts->checksum != AO_DEFAULT_CHECKSUM) && !foundChecksum) { /* checksum */ strval = opts->checksum ? "true" : "false"; len = VARHDRSZ + strlen(SOPT_CHECKSUM) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_CHECKSUM, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } if ((opts->columnstore != AO_DEFAULT_COLUMNSTORE) && !foundOrientation) { /* orientation */ strval = opts->columnstore ? "column" : "row"; len = VARHDRSZ + strlen(SOPT_ORIENTATION) + 1 + strlen(strval); /* +1 leaves room for sprintf's trailing null */ t = (text *) palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", SOPT_ORIENTATION, strval); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } return astate ? makeArrayResult(astate, CurrentMemoryContext) : PointerGetDatum(NULL); }
/* * ParseExternalTableUri * * This routines converts a string to a supported external * table URI object. It is also used to validate the URI format. */ Uri * ParseExternalTableUri(const char *uri_str) { Uri *uri = (Uri *) palloc0(sizeof(Uri)); char *start, *end; int protocol_len, len; uri->port = -1; uri->hostname = NULL; uri->path = NULL; uri->customprotocol = NULL; /* * parse protocol */ if (IS_FILE_URI(uri_str)) { uri->protocol = URI_FILE; protocol_len = strlen(PROTOCOL_FILE); } else if (pg_strncasecmp(uri_str, PROTOCOL_FTP, strlen(PROTOCOL_FTP)) == 0) { uri->protocol = URI_FTP; protocol_len = strlen(PROTOCOL_FTP); } else if (IS_HTTP_URI(uri_str)) { uri->protocol = URI_HTTP; protocol_len = strlen(PROTOCOL_HTTP); } else if (IS_GPFDIST_URI(uri_str)) { uri->protocol = URI_GPFDIST; protocol_len = strlen(PROTOCOL_GPFDIST); } else if (IS_GPFDISTS_URI(uri_str)) { uri->protocol = URI_GPFDISTS; protocol_len = strlen(PROTOCOL_GPFDISTS); } else /* not recognized. treat it as a custom protocol */ { char *post_protocol = strstr(uri_str, "://"); if(!post_protocol) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid URI \'%s\' : undefined structure", uri_str), errOmitLocation(true))); } else { protocol_len = post_protocol - uri_str; uri->customprotocol = (char *) palloc (protocol_len + 1); strncpy(uri->customprotocol, uri_str, protocol_len); uri->customprotocol[protocol_len] = '\0'; uri->protocol = URI_CUSTOM; return uri; /* we let the user parse it himself later on */ } /* this is a non existing protocol */ protocol_len = 0; /* shut compiler up */ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid URI \'%s\' : undefined protocol", uri_str), errOmitLocation(true))); } /* make sure there is more to the uri string */ if (strlen(uri_str) <= protocol_len) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid URI \'%s\' : missing host name and path", uri_str), errOmitLocation(true))); /* * parse host name */ start = (char *) uri_str + protocol_len; if (*start == '/') /* format "prot:///" ? (no hostname) */ { /* the default is "localhost" */ const char *lh = "localhost"; len = strlen(lh); uri->hostname = (char *) palloc(len + 1); strncpy(uri->hostname, lh, len); uri->hostname[len] = '\0'; end = start; } else { end = strchr(start, '/'); if (end == NULL) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid URI \'%s\' : missing host name or path", uri_str), errOmitLocation(true))); } else { char *colon, *p; len = end - start; /* * host */ uri->hostname = (char *) palloc(len + 1); strncpy(uri->hostname, start, len); uri->hostname[len] = '\0'; /* * MPP-13617, if we have an ipv6 address in the URI hostname * (e.g. [2620:0:170:610::11]:8080/path/data.txt ) then we * we start our search for the :port after the closing ]. */ p = strchr(uri->hostname, ']'); if (p) { colon = strchr(p, ':'); /* * Eliminate the [ ] from the hostname. * note we don't change the uri->hostname pointer because we pfree() it later */ *p = '\0'; for (p = strchr(uri->hostname, '['); p && *p; p++) { p[0] = p[1]; } } else { colon = strchr(uri->hostname, ':'); } /* * port */ if (colon) { int portlen = 0; uri->port = atoi(colon + 1); portlen = strlen(colon); /* now truncate ":<port>" from hostname */ uri->hostname[len - portlen] = '\0'; *colon = 0; } else { uri->port = -1; /* no port was indicated. will use default if needed */ } } } /* * We continue from the trailing host '/' since the * path is an absolute path. Our previous ending point * is the beginning of the file path, until the end of * the uri string. */ start = end; len = strlen(start); /* make sure there is more to the uri string */ if (len <= 1) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid URI \'%s\' : missing path", uri_str), errOmitLocation(true))); uri->path = (char *) palloc(len + 1); strcpy(uri->path, start); uri->path[len] = '\0'; return uri; }
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ ConditionalStack cond_stack; /* \if status stack */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, * etc. */ PQExpBuffer history_buf; /* earlier lines of a multi-line command, not * yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; /* Save the prior command source */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* pset.stmt_lineno does not need to be saved and restored */ /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; pset.stmt_lineno = 1; /* Create working state */ scan_state = psql_scan_create(&psqlscan_callbacks); cond_stack = conditional_stack_create(); psql_scan_set_passthrough(scan_state, (void *) cond_stack); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); if (PQExpBufferBroken(query_buf) || PQExpBufferBroken(previous_buf) || PQExpBufferBroken(history_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Clean up after a previous Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C. */ successResult = EXIT_USER; break; } cancel_pressed = false; } /* * Establish longjmp destination for exiting from wait-for-input. We * must re-do this each time through the loop for safety, since the * jmpbuf might get changed during command execution. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ psql_scan_finish(scan_state); psql_scan_reset(scan_state); resetPQExpBuffer(query_buf); resetPQExpBuffer(history_buf); count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; pset.stmt_lineno = 1; cancel_pressed = false; if (pset.cur_cmd_interactive) { putc('\n', stdout); /* * if interactive user is in an \if block, then Ctrl-C will * exit from the innermost \if. */ if (!conditional_stack_empty(cond_stack)) { psql_error("\\if: escaped\n"); conditional_stack_pop(cond_stack); } } else { successResult = EXIT_USER; break; } } fflush(stdout); /* * get another line */ if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf); } else { line = gets_fromFile(source); if (!line && ferror(source)) successResult = EXIT_FAILURE; } /* * query_buf holds query already accumulated. line is the malloc'd * new line of input (note it must be freed before looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < pset.ignoreeof) { if (!pset.quiet) printf(_("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(pset.quiet ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* ignore UTF-8 Unicode byte-order mark */ if (pset.lineno == 1 && pset.encoding == PG_UTF8 && strncmp(line, "\xef\xbb\xbf", 3) == 0) memmove(line, line + 3, strlen(line + 3) + 1); /* Detect attempts to run custom-format dumps as SQL scripts */ if (pset.lineno == 1 && !pset.cur_cmd_interactive && strncmp(line, "PGDMP", 5) == 0) { free(line); puts(_("The input is a PostgreSQL custom-format dump.\n" "Use the pg_restore command-line client to restore this dump to a database.\n")); fflush(stdout); successResult = EXIT_FAILURE; break; } /* no further processing of empty lines, unless within a literal */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* A request for help? Be friendly and give them some guidance */ if (pset.cur_cmd_interactive && query_buf->len == 0 && pg_strncasecmp(line, "help", 4) == 0 && (line[4] == '\0' || line[4] == ';' || isspace((unsigned char) line[4]))) { free(line); puts(_("You are using psql, the command-line interface to PostgreSQL.")); printf(_("Type: \\copyright for distribution terms\n" " \\h for help with SQL commands\n" " \\? for help with psql commands\n" " \\g or terminate with semicolon to execute query\n" " \\q to quit\n")); fflush(stdout); continue; } /* echo back if flag is set, unless interactive */ if (pset.echo == PSQL_ECHO_ALL && !pset.cur_cmd_interactive) { puts(line); fflush(stdout); } /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = pset.on_error_stop; /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings()); success = true; line_saved_in_history = false; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; size_t pos_in_query; char *tmp_line; pos_in_query = query_buf->len; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; if (PQExpBufferBroken(query_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* * Increase statement line number counter for each linebreak added * to the query buffer by the last psql_scan() call. There only * will be ones to add when navigating to a statement in * readline's history containing newlines. */ tmp_line = query_buf->data + pos_in_query; while (*tmp_line != '\0') { if (*(tmp_line++) == '\n') pset.stmt_lineno++; } if (scan_result == PSCAN_EOL) pset.stmt_lineno++; /* * Send command if semicolon found, or if end of line and we're in * single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && pset.singleline)) { /* * Save line in history. We use history_buf to accumulate * multi-line queries into a single history entry. Note that * history accumulation works on input lines, so it doesn't * matter whether the query will be ignored due to \if. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; pset.stmt_lineno = 1; /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else { /* if interactive, warn about non-executed query */ if (pset.cur_cmd_interactive) psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); /* fake an OK result for purposes of loop checks */ success = true; slashCmdStatus = PSQL_CMD_SEND; pset.stmt_lineno = 1; /* note that query_buf doesn't change state */ } } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a * line contains only a backslash command. Also, in this * situation we force out any previous lines as a separate * history entry; we don't want SQL and backslash commands * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) { query_buf->data[--query_buf->len] = '\0'; pg_send_history(history_buf); } added_nl_pos = -1; /* save backslash command in history */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, cond_stack, query_buf, previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; /* * Resetting stmt_lineno after a backslash command isn't * always appropriate, but it's what we've done historically * and there have been few complaints. */ pset.stmt_lineno = 1; if (slashCmdStatus == PSQL_CMD_SEND) { /* should not see this in inactive branch */ Assert(conditional_active(cond_stack)); success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { /* should not see this in inactive branch */ Assert(conditional_active(cond_stack)); /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); line = pg_strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings()); line_saved_in_history = false; prompt_status = PROMPT_READY; } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } /* Add line to pending history if we didn't execute anything yet */ if (pset.cur_cmd_interactive && !line_saved_in_history) pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); if (slashCmdStatus == PSQL_CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * If we have a non-semicolon-terminated query at the end of file, we * process it unless the input source is interactive --- in that case it * seems better to go ahead and quit. Also skip if this is an error exit. */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { /* save query in history */ /* currently unneeded since we don't use this block if interactive */ #ifdef NOT_USED if (pset.cur_cmd_interactive) pg_send_history(history_buf); #endif /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { success = SendQuery(query_buf->data); } else { if (pset.cur_cmd_interactive) psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); success = true; } if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Check for unbalanced \if-\endifs unless user explicitly quit, or the * script is erroring out */ if (slashCmdStatus != PSQL_CMD_TERMINATE && successResult != EXIT_USER && !conditional_stack_empty(cond_stack)) { psql_error("reached EOF without finding closing \\endif(s)\n"); if (die_on_error && !pset.cur_cmd_interactive) successResult = EXIT_USER; } /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to * itself at the top of its loop, before any further interactive input * happens. */ sigint_interrupt_enabled = false; destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); conditional_stack_destroy(cond_stack); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */
bool parse_bool_with_len(const char *value, size_t len, bool *result) { switch (*value) { case 't': case 'T': if (pg_strncasecmp(value, "true", len) == 0) { if (result) *result = true; return true; } break; case 'f': case 'F': if (pg_strncasecmp(value, "false", len) == 0) { if (result) *result = false; return true; } break; case 'y': case 'Y': if (pg_strncasecmp(value, "yes", len) == 0) { if (result) *result = true; return true; } break; case 'n': case 'N': if (pg_strncasecmp(value, "no", len) == 0) { if (result) *result = false; return true; } break; case 'o': case 'O': /* 'o' is not unique enough */ if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) { if (result) *result = true; return true; } else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) { if (result) *result = false; return true; } break; case '1': if (len == 1) { if (result) *result = true; return true; } break; case '0': if (len == 1) { if (result) *result = false; return true; } break; default: break; } if (result) *result = false; /* suppress compiler warning */ return false; }
/* * helpSQL -- help with SQL commands * * Note: we assume caller removed any trailing spaces in "topic". */ void helpSQL(const char *topic, unsigned short int pager) { #define VALUE_OR_NULL(a) ((a) ? (a) : "") if (!topic || strlen(topic) == 0) { /* Print all the available command names */ int screen_width; int ncolumns; int nrows; FILE *output; int i; int j; #ifdef TIOCGWINSZ struct winsize screen_size; if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1) screen_width = 80; /* ioctl failed, assume 80 */ else screen_width = screen_size.ws_col; #else screen_width = 80; /* default assumption */ #endif ncolumns = (screen_width - 3) / (QL_MAX_CMD_LEN + 1); ncolumns = Max(ncolumns, 1); nrows = (QL_HELP_COUNT + (ncolumns - 1)) / ncolumns; output = PageOutput(nrows + 1, pager); fputs(_("Available help:\n"), output); for (i = 0; i < nrows; i++) { fprintf(output, " "); for (j = 0; j < ncolumns - 1; j++) fprintf(output, "%-*s", QL_MAX_CMD_LEN + 1, VALUE_OR_NULL(QL_HELP[i + j * nrows].cmd)); if (i + j * nrows < QL_HELP_COUNT) fprintf(output, "%s", VALUE_OR_NULL(QL_HELP[i + j * nrows].cmd)); fputc('\n', output); } /* Only close if we used the pager */ if (output != stdout) { pclose(output); #ifndef WIN32 pqsignal(SIGPIPE, SIG_DFL); #endif } } else { int i, j, x = 0; bool help_found = false; FILE *output; size_t len, wordlen; int nl_count = 0; /* * We first try exact match, then first + second words, then first * word only. */ len = strlen(topic); for (x = 1; x <= 3; x++) { if (x > 1) /* Nothing on first pass - try the opening * word(s) */ { wordlen = j = 1; while (topic[j] != ' ' && j++ < len) wordlen++; if (x == 2) { j++; while (topic[j] != ' ' && j++ <= len) wordlen++; } if (wordlen >= len) /* Don't try again if the same word */ { output = PageOutput(nl_count, pager); break; } len = wordlen; } /* Count newlines for pager */ for (i = 0; QL_HELP[i].cmd; i++) { if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 || strcmp(topic, "*") == 0) { nl_count += 5 + QL_HELP[i].nl_count; /* If we have an exact match, exit. Fixes \h SELECT */ if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0) break; } } output = PageOutput(nl_count, pager); for (i = 0; QL_HELP[i].cmd; i++) { if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 || strcmp(topic, "*") == 0) { PQExpBufferData buffer; initPQExpBuffer(&buffer); QL_HELP[i].syntaxfunc(&buffer); help_found = true; fprintf(output, _("Command: %s\n" "Description: %s\n" "Syntax:\n%s\n\n"), QL_HELP[i].cmd, _(QL_HELP[i].help), buffer.data); /* If we have an exact match, exit. Fixes \h SELECT */ if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0) break; } } if (help_found) /* Don't keep trying if we got a match */ break; } if (!help_found) fprintf(output, _("No help available for \"%s\".\nTry \\h with no arguments to see available help.\n"), topic); /* Only close if we used the pager */ if (output != stdout) { pclose(output); #ifndef WIN32 pqsignal(SIGPIPE, SIG_DFL); #endif } } }
/* * pg_krb5_recvauth -- server routine to receive authentication information * from the client * * We still need to compare the username obtained from the client's setup * packet to the authenticated name. * * We have our own keytab file because postgres is unlikely to run as root, * and so cannot read the default keytab. */ static int pg_krb5_recvauth(Port *port) { krb5_error_code retval; int ret; krb5_auth_context auth_context = NULL; krb5_ticket *ticket; char *kusername; char *cp; if (get_role_line(port->user_name) == NULL) return STATUS_ERROR; ret = pg_krb5_init(); if (ret != STATUS_OK) return ret; retval = krb5_recvauth(pg_krb5_context, &auth_context, (krb5_pointer) & port->sock, pg_krb_srvnam, pg_krb5_server, 0, pg_krb5_keytab, &ticket); if (retval) { ereport(LOG, (errmsg("Kerberos recvauth returned error %d", retval))); com_err("postgres", retval, "from krb5_recvauth"); return STATUS_ERROR; } /* * The "client" structure comes out of the ticket and is therefore * authenticated. Use it to check the username obtained from the * postmaster startup packet. */ #if defined(HAVE_KRB5_TICKET_ENC_PART2) retval = krb5_unparse_name(pg_krb5_context, ticket->enc_part2->client, &kusername); #elif defined(HAVE_KRB5_TICKET_CLIENT) retval = krb5_unparse_name(pg_krb5_context, ticket->client, &kusername); #else #error "bogus configuration" #endif if (retval) { ereport(LOG, (errmsg("Kerberos unparse_name returned error %d", retval))); com_err("postgres", retval, "while unparsing client name"); krb5_free_ticket(pg_krb5_context, ticket); krb5_auth_con_free(pg_krb5_context, auth_context); return STATUS_ERROR; } cp = strchr(kusername, '@'); if (cp) { *cp = '\0'; cp++; if (pg_krb_realm != NULL && strlen(pg_krb_realm)) { /* Match realm against configured */ if (pg_krb_caseins_users) ret = pg_strcasecmp(pg_krb_realm, cp); else ret = strcmp(pg_krb_realm, cp); if (ret) { elog(DEBUG2, "krb5 realm (%s) and configured realm (%s) don't match", cp, pg_krb_realm); krb5_free_ticket(pg_krb5_context, ticket); krb5_auth_con_free(pg_krb5_context, auth_context); return STATUS_ERROR; } } } else if (pg_krb_realm && strlen(pg_krb_realm)) { elog(DEBUG2, "krb5 did not return realm but realm matching was requested"); krb5_free_ticket(pg_krb5_context, ticket); krb5_auth_con_free(pg_krb5_context, auth_context); return STATUS_ERROR; } if (pg_krb_caseins_users) ret = pg_strncasecmp(port->user_name, kusername, SM_DATABASE_USER); else ret = strncmp(port->user_name, kusername, SM_DATABASE_USER); if (ret) { ereport(LOG, (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")", port->user_name, kusername))); ret = STATUS_ERROR; } else ret = STATUS_OK; krb5_free_ticket(pg_krb5_context, ticket); krb5_auth_con_free(pg_krb5_context, auth_context); free(kusername); return ret; }