diff --git a/bin/Makefile b/bin/Makefile index 370f4d9..5e168c5 100755 --- a/bin/Makefile +++ b/bin/Makefile @@ -3,7 +3,7 @@ # # Copyright (c) 2008-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION # -SRCS = pg_reorg.c pgut/pgut.c +SRCS = pg_reorg.c pgut/pgut.c pgut/pgut-fe.c OBJS = $(SRCS:.c=.o) PROGRAM = pg_reorg REGRESS = init reorg diff --git a/bin/pg_reorg.c b/bin/pg_reorg.c index 9f16607..30bc208 100755 --- a/bin/pg_reorg.c +++ b/bin/pg_reorg.c @@ -8,15 +8,16 @@ * @brief Client Modules */ -const char *PROGRAM_VERSION = "1.0.8"; +const char *PROGRAM_VERSION = "1.1.0"; const char *PROGRAM_URL = "http://reorg.projects.postgresql.org/"; const char *PROGRAM_EMAIL = "reorg-general@lists.pgfoundry.org"; -#include "pgut/pgut.h" +#include "pgut/pgut-fe.h" #include #include #include +#include #define APPLY_COUNT 1000 @@ -84,21 +85,22 @@ static void reorg_cleanup(bool fatal, void *userdata); static char *getstr(PGresult *res, int row, int col); static Oid getoid(PGresult *res, int row, int col); +static void lock_exclusive(const char *relid, const char *lock_query); #define SQLSTATE_INVALID_SCHEMA_NAME "3F000" -#define SQLSTATE_LOCK_NOT_AVAILABLE "55P03" +#define SQLSTATE_QUERY_CANCELED "57014" static bool sqlstate_equals(PGresult *res, const char *state) { return strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), state) == 0; } -static bool verbose = false; static bool analyze = true; static bool alldb = false; static bool noorder = false; static char *table = NULL; static char *orderby = NULL; +static int wait_timeout = 60; /* in seconds */ /* buffer should have at least 11 bytes */ static char * @@ -110,11 +112,11 @@ utoa(unsigned int value, char *buffer) static pgut_option options[] = { - { 'b', 'v', "verbose", &verbose }, { 'b', 'a', "all", &alldb }, { 's', 't', "table", &table }, { 'b', 'n', "no-order", &noorder }, { 's', 'o', "order-by", &orderby }, + { 'i', 'T', "wait-timeout", &wait_timeout }, { 'B', 'Z', "no-analyze", &analyze }, { 0 }, }; @@ -129,7 +131,9 @@ main(int argc, char *argv[]) if (i == argc - 1) dbname = argv[i]; else if (i < argc) - elog(ERROR_ARGS, "too many arguments"); + ereport(ERROR, + (errcode(EINVAL), + errmsg("too many arguments"))); if (noorder) orderby = ""; @@ -137,13 +141,17 @@ main(int argc, char *argv[]) if (alldb) { if (table) - elog(ERROR, "cannot reorg a specific table in all databases"); + ereport(ERROR, + (errcode(EINVAL), + errmsg("cannot reorg a specific table in all databases"))); reorg_all_databases(orderby); } else { if (!reorg_one_database(orderby, table)) - elog(ERROR, "%s is not installed", PROGRAM_NAME); + ereport(ERROR, + (errcode(ENOENT), + errmsg("%s is not installed", PROGRAM_NAME))); } return 0; @@ -159,7 +167,7 @@ reorg_all_databases(const char *orderby) int i; dbname = "postgres"; - reconnect(); + reconnect(ERROR); result = execute("SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", 0, NULL); disconnect(); @@ -169,7 +177,7 @@ reorg_all_databases(const char *orderby) dbname = PQgetvalue(result, i, 0); - if (!quiet) + if (pgut_log_level >= INFO) { printf("%s: reorg database \"%s\"", PROGRAM_NAME, dbname); fflush(stdout); @@ -177,7 +185,7 @@ reorg_all_databases(const char *orderby) ret = reorg_one_database(orderby, NULL); - if (!quiet) + if (pgut_log_level >= INFO) { if (ret) printf("\n"); @@ -223,7 +231,10 @@ reorg_one_database(const char *orderby, const char *table) initStringInfo(&sql); - reconnect(); + reconnect(ERROR); + + /* Disable statement timeout. */ + command("SET statement_timeout = 0", 0, NULL); /* Restrict search_path to system catalog. */ command("SET search_path = pg_catalog, pg_temp, public", 0, NULL); @@ -236,14 +247,14 @@ reorg_one_database(const char *orderby, const char *table) if (table) { appendStringInfoString(&sql, "relid = $1::regclass"); - res = execute_elevel(sql.data, 1, &table, LOG); + res = execute_elevel(sql.data, 1, &table, DEBUG2); } else { appendStringInfoString(&sql, "pkid IS NOT NULL"); if (!orderby) appendStringInfoString(&sql, " AND ckid IS NOT NULL"); - res = execute_elevel(sql.data, 0, NULL, LOG); + res = execute_elevel(sql.data, 0, NULL, DEBUG2); } if (PQresultStatus(res) != PGRES_TUPLES_OK) @@ -280,7 +291,9 @@ reorg_one_database(const char *orderby, const char *table) table.ckid = getoid(res, i, c++); if (table.pkid == 0) - elog(ERROR, "relation \"%s\" has no primary key", table.target_name); + ereport(ERROR, + (errcode(E_PG_COMMAND), + errmsg("relation \"%s\" has no primary key", table.target_name))); table.create_pktype = getstr(res, i, c++); table.create_log = getstr(res, i, c++); @@ -296,7 +309,9 @@ reorg_one_database(const char *orderby, const char *table) { /* CLUSTER mode */ if (ckey == NULL) - elog(ERROR, "relation \"%s\" has no cluster key", table.target_name); + ereport(ERROR, + (errcode(E_PG_COMMAND), + errmsg("relation \"%s\" has no cluster key", table.target_name))); appendStringInfo(&sql, "%s ORDER BY %s", create_table, ckey); table.create_table = sql.data; } @@ -367,35 +382,30 @@ reorg_one_table(const reorg_table *table, const char *orderby) initStringInfo(&sql); - if (verbose) - { - fprintf(stderr, "---- reorg_one_table ----\n"); - fprintf(stderr, "target_name : %s\n", table->target_name); - fprintf(stderr, "target_oid : %u\n", table->target_oid); - fprintf(stderr, "target_toast : %u\n", table->target_toast); - fprintf(stderr, "target_tidx : %u\n", table->target_tidx); - fprintf(stderr, "pkid : %u\n", table->pkid); - fprintf(stderr, "ckid : %u\n", table->ckid); - fprintf(stderr, "create_pktype : %s\n", table->create_pktype); - fprintf(stderr, "create_log : %s\n", table->create_log); - fprintf(stderr, "create_trigger : %s\n", table->create_trigger); - fprintf(stderr, "create_table : %s\n", table->create_table); - fprintf(stderr, "delete_log : %s\n", table->delete_log); - fprintf(stderr, "lock_table : %s\n", table->lock_table); - fprintf(stderr, "sql_peek : %s\n", table->sql_peek); - fprintf(stderr, "sql_insert : %s\n", table->sql_insert); - fprintf(stderr, "sql_delete : %s\n", table->sql_delete); - fprintf(stderr, "sql_update : %s\n", table->sql_update); - fprintf(stderr, "sql_pop : %s\n", table->sql_pop); - } + elog(DEBUG2, "---- reorg_one_table ----"); + elog(DEBUG2, "target_name : %s", table->target_name); + elog(DEBUG2, "target_oid : %u", table->target_oid); + elog(DEBUG2, "target_toast : %u", table->target_toast); + elog(DEBUG2, "target_tidx : %u", table->target_tidx); + elog(DEBUG2, "pkid : %u", table->pkid); + elog(DEBUG2, "ckid : %u", table->ckid); + elog(DEBUG2, "create_pktype : %s", table->create_pktype); + elog(DEBUG2, "create_log : %s", table->create_log); + elog(DEBUG2, "create_trigger : %s", table->create_trigger); + elog(DEBUG2, "create_table : %s", table->create_table); + elog(DEBUG2, "delete_log : %s", table->delete_log); + elog(DEBUG2, "lock_table : %s", table->lock_table); + elog(DEBUG2, "sql_peek : %s", table->sql_peek); + elog(DEBUG2, "sql_insert : %s", table->sql_insert); + elog(DEBUG2, "sql_delete : %s", table->sql_delete); + elog(DEBUG2, "sql_update : %s", table->sql_update); + elog(DEBUG2, "sql_pop : %s", table->sql_pop); /* * 1. Setup workspaces and a trigger. */ - if (verbose) - fprintf(stderr, "---- setup ----\n"); - - command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL); + elog(DEBUG2, "---- setup ----"); + lock_exclusive(utoa(table->target_oid, buffer), table->lock_table); /* * Check z_reorg_trigger is the trigger executed at last so that @@ -405,8 +415,10 @@ reorg_one_table(const reorg_table *table, const char *orderby) res = execute("SELECT reorg.conflicted_triggers($1)", 1, params); if (PQntuples(res) > 0) - elog(ERROR, "trigger %s conflicted for %s", - PQgetvalue(res, 0, 0), table->target_name); + ereport(ERROR, + (errcode(E_PG_COMMAND), + errmsg("trigger %s conflicted for %s", + PQgetvalue(res, 0, 0), table->target_name))); command(table->create_pktype, 0, NULL); command(table->create_log, 0, NULL); @@ -425,8 +437,7 @@ reorg_one_table(const reorg_table *table, const char *orderby) /* * 2. Copy tuples into temp table. */ - if (verbose) - fprintf(stderr, "---- copy tuples ----\n"); + elog(DEBUG2, "---- copy tuples ----"); command("BEGIN ISOLATION LEVEL SERIALIZABLE", 0, NULL); /* SET work_mem = maintenance_work_mem */ @@ -445,8 +456,7 @@ reorg_one_table(const reorg_table *table, const char *orderby) /* * 3. Create indexes on temp table. */ - if (verbose) - fprintf(stderr, "---- create indexes ----\n"); + elog(DEBUG2, "---- create indexes ----"); params[0] = utoa(table->target_oid, buffer); res = execute("SELECT indexrelid," @@ -462,12 +472,9 @@ reorg_one_table(const reorg_table *table, const char *orderby) index.target_oid = getoid(res, i, c++); index.create_index = getstr(res, i, c++); - if (verbose) - { - fprintf(stderr, "[%d]\n", i); - fprintf(stderr, "target_oid : %u\n", index.target_oid); - fprintf(stderr, "create_index : %s\n", index.create_index); - } + elog(DEBUG2, "[%d]", i); + elog(DEBUG2, "target_oid : %u", index.target_oid); + elog(DEBUG2, "create_index : %s", index.create_index); /* * NOTE: If we want to create multiple indexes in parallel, @@ -506,35 +513,8 @@ reorg_one_table(const reorg_table *table, const char *orderby) /* * 5. Swap. */ - if (verbose) - fprintf(stderr, "---- swap ----\n"); - - for (;;) - { - command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL); - res = execute_elevel(table->lock_table, 0, NULL, NOTICE); - if (PQresultStatus(res) == PGRES_COMMAND_OK) - { - PQclear(res); - break; - } - else if (sqlstate_equals(res, SQLSTATE_LOCK_NOT_AVAILABLE)) - { - /* retry if lock conflicted */ - PQclear(res); - command("ROLLBACK", 0, NULL); - sleep(1); - continue; - } - else - { - /* exit otherwise */ - printf("%s", PQerrorMessage(connection)); - PQclear(res); - exit(1); - } - } - + elog(DEBUG2, "---- swap ----"); + lock_exclusive(utoa(table->target_oid, buffer), table->lock_table); apply_log(table, 0); params[0] = utoa(table->target_oid, buffer); command("SELECT reorg.reorg_swap($1)", 1, params); @@ -543,8 +523,7 @@ reorg_one_table(const reorg_table *table, const char *orderby) /* * 6. Drop. */ - if (verbose) - fprintf(stderr, "---- drop ----\n"); + elog(DEBUG2, "---- drop ----"); command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL); params[0] = utoa(table->target_oid, buffer); @@ -561,12 +540,10 @@ reorg_one_table(const reorg_table *table, const char *orderby) */ if (analyze) { - if (verbose) - fprintf(stderr, "---- analyze ----\n"); + elog(DEBUG2, "---- analyze ----"); command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL); - printfStringInfo(&sql, "ANALYZE %s%s", - (verbose ? "VERBOSE " : ""), table->target_name); + printfStringInfo(&sql, "ANALYZE %s", table->target_name); command(sql.data, 0, NULL); command("COMMIT", 0, NULL); } @@ -574,6 +551,79 @@ reorg_one_table(const reorg_table *table, const char *orderby) termStringInfo(&sql); } +/* + * Try acquire a table lock but avoid long time locks when conflict. + */ +static void +lock_exclusive(const char *relid, const char *lock_query) +{ + time_t start = time(NULL); + int i; + + for (i = 1; ; i++) + { + time_t duration; + char sql[1024]; + PGresult *res; + int wait_msec; + + command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL); + + duration = time(NULL) - start; + if (duration > wait_timeout) + { + const char *cancel_query; + if (PQserverVersion(connection) >= 80400 && + duration > wait_timeout * 2) + { + elog(WARNING, "terminating conflicted backends"); + cancel_query = + "SELECT pg_terminate_backend(pid) FROM pg_locks" + " WHERE locktype = 'relation'" + " AND relation = $1 AND pid <> pg_backend_pid()"; + } + else + { + elog(WARNING, "canceling conflicted backends"); + cancel_query = + "SELECT pg_cancel_backend(pid) FROM pg_locks" + " WHERE locktype = 'relation'" + " AND relation = $1 AND pid <> pg_backend_pid()"; + } + + command(cancel_query, 1, &relid); + } + + /* wait for a while to lock the table. */ + wait_msec = Min(1000, i * 100); + snprintf(sql, lengthof(sql), "SET LOCAL statement_timeout = %d", wait_msec); + command(sql, 0, NULL); + + res = execute_elevel(lock_query, 0, NULL, DEBUG2); + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + PQclear(res); + break; + } + else if (sqlstate_equals(res, SQLSTATE_QUERY_CANCELED)) + { + /* retry if lock conflicted */ + PQclear(res); + command("ROLLBACK", 0, NULL); + continue; + } + else + { + /* exit otherwise */ + printf("%s", PQerrorMessage(connection)); + PQclear(res); + exit(1); + } + } + + command("RESET statement_timeout", 0, NULL); +} + /* * The userdata pointing a table being re-organized. We need to cleanup temp * objects before the program exits. @@ -598,7 +648,7 @@ reorg_cleanup(bool fatal, void *userdata) /* Try reconnection if not available. */ if (PQstatus(connection) != CONNECTION_OK) - reconnect(); + reconnect(ERROR); /* do cleanup */ params[0] = utoa(table->target_oid, buffer); @@ -621,6 +671,6 @@ pgut_help(bool details) printf(" -t, --table=TABLE reorg specific table only\n"); printf(" -n, --no-order do vacuum full instead of cluster\n"); printf(" -o, --order-by=columns order by columns instead of cluster keys\n"); + printf(" -T, --wait-timeout=secs timeout to cancel other backends on conflict.\n"); printf(" -Z, --no-analyze don't analyze at end\n"); - printf(" -v, --verbose display detailed information during processing\n"); } diff --git a/bin/pgut/pgut-fe.c b/bin/pgut/pgut-fe.c new file mode 100755 index 0000000..1c916f6 --- /dev/null +++ b/bin/pgut/pgut-fe.c @@ -0,0 +1,687 @@ +/*------------------------------------------------------------------------- + * + * pgut-fe.c + * + * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + *------------------------------------------------------------------------- + */ + +#define FRONTEND +#include "pgut-fe.h" + +#ifdef HAVE_GETOPT_H +#include +#else +#include +#endif + +char *dbname = NULL; +char *host = NULL; +char *port = NULL; +char *username = NULL; +char *password = NULL; +YesNo prompt_password = DEFAULT; + +PGconn *connection = NULL; + +static bool parse_pair(const char buffer[], char key[], char value[]); +static char *get_username(void); + +/* + * the result is also available with the global variable 'connection'. + */ +void +reconnect(int elevel) +{ + StringInfoData buf; + char *new_password; + + disconnect(); + initStringInfo(&buf); + if (dbname && dbname[0]) + appendStringInfo(&buf, "dbname=%s ", dbname); + if (host && host[0]) + appendStringInfo(&buf, "host=%s ", host); + if (port && port[0]) + appendStringInfo(&buf, "port=%s ", port); + if (username && username[0]) + appendStringInfo(&buf, "username=%s ", username); + if (password && password[0]) + appendStringInfo(&buf, "password=%s ", password); + + connection = pgut_connect(buf.data, prompt_password, elevel); + + /* update password */ + if (connection) + { + new_password = PQpass(connection); + if (new_password && (!password || strcmp(new_password, password))) + { + free(password); + password = new_password; + } + } + + termStringInfo(&buf); +} + +void +disconnect(void) +{ + if (connection) + { + pgut_disconnect(connection); + connection = NULL; + } +} + +static void +option_from_env(pgut_option options[]) +{ + size_t i; + + for (i = 0; options && options[i].type; i++) + { + pgut_option *opt = &options[i]; + char name[256]; + size_t j; + const char *s; + const char *value; + + if (opt->source > SOURCE_ENV || + opt->allowed == SOURCE_DEFAULT || opt->allowed > SOURCE_ENV) + continue; + + for (s = opt->lname, j = 0; *s && j < lengthof(name) - 1; s++, j++) + { + if (strchr("-_ ", *s)) + name[j] = '_'; /* - to _ */ + else + name[j] = toupper(*s); + } + name[j] = '\0'; + + if ((value = getenv(name)) != NULL) + pgut_setopt(opt, value, SOURCE_ENV); + } +} + +/* compare two strings ignore cases and ignore -_ */ +bool +pgut_keyeq(const char *lhs, const char *rhs) +{ + for (; *lhs && *rhs; lhs++, rhs++) + { + if (strchr("-_ ", *lhs)) + { + if (!strchr("-_ ", *rhs)) + return false; + } + else if (ToLower(*lhs) != ToLower(*rhs)) + return false; + } + + return *lhs == '\0' && *rhs == '\0'; +} + +void +pgut_setopt(pgut_option *opt, const char *optarg, pgut_optsrc src) +{ + const char *message; + + if (opt == NULL) + { + fprintf(stderr, "Try \"%s --help\" for more information.\n", PROGRAM_NAME); + exit(EINVAL); + } + + if (opt->source > src) + { + /* high prior value has been set already. */ + return; + } + else if (src >= SOURCE_CMDLINE && opt->source >= src) + { + /* duplicated option in command line */ + message = "specified only once"; + } + else + { + /* can be overwritten if non-command line source */ + opt->source = src; + + switch (opt->type) + { + case 'b': + case 'B': + if (optarg == NULL) + { + *((bool *) opt->var) = (opt->type == 'b'); + return; + } + else if (parse_bool(optarg, (bool *) opt->var)) + { + return; + } + message = "a boolean"; + break; + case 'f': + ((pgut_optfn) opt->var)(opt, optarg); + return; + case 'i': + if (parse_int32(optarg, opt->var)) + return; + message = "a 32bit signed integer"; + break; + case 'u': + if (parse_uint32(optarg, opt->var)) + return; + message = "a 32bit unsigned integer"; + break; + case 'I': + if (parse_int64(optarg, opt->var)) + return; + message = "a 64bit signed integer"; + break; + case 'U': + if (parse_uint64(optarg, opt->var)) + return; + message = "a 64bit unsigned integer"; + break; + case 's': + if (opt->source != SOURCE_DEFAULT) + free(*(char **) opt->var); + *(char **) opt->var = pgut_strdup(optarg); + return; + case 't': + if (parse_time(optarg, opt->var)) + return; + message = "a time"; + break; + case 'y': + case 'Y': + if (optarg == NULL) + { + *(YesNo *) opt->var = (opt->type == 'y' ? YES : NO); + return; + } + else + { + bool value; + if (parse_bool(optarg, &value)) + { + *(YesNo *) opt->var = (value ? YES : NO); + return; + } + } + message = "a boolean"; + break; + default: + ereport(ERROR, + (errcode(EINVAL), + errmsg("invalid option type: %c", opt->type))); + return; /* keep compiler quiet */ + } + } + + if (isprint(opt->sname)) + ereport(ERROR, + (errcode(EINVAL), + errmsg("option -%c, --%s should be %s: '%s'", + opt->sname, opt->lname, message, optarg))); + else + ereport(ERROR, + (errcode(EINVAL), + errmsg("option --%s should be %s: '%s'", + opt->lname, message, optarg))); +} + +/* + * Get configuration from configuration file. + */ +void +pgut_readopt(const char *path, pgut_option options[], int elevel) +{ + FILE *fp; + char buf[1024]; + char key[1024]; + char value[1024]; + + if (!options) + return; + + if ((fp = pgut_fopen(path, "Rt")) == NULL) + return; + + while (fgets(buf, lengthof(buf), fp)) + { + size_t i; + + for (i = strlen(buf); i > 0 && IsSpace(buf[i - 1]); i--) + buf[i - 1] = '\0'; + + if (parse_pair(buf, key, value)) + { + for (i = 0; options[i].type; i++) + { + pgut_option *opt = &options[i]; + + if (pgut_keyeq(key, opt->lname)) + { + if (opt->allowed == SOURCE_DEFAULT || + opt->allowed > SOURCE_FILE) + elog(elevel, "option %s cannot specified in file", opt->lname); + else if (opt->source <= SOURCE_FILE) + pgut_setopt(opt, value, SOURCE_FILE); + break; + } + } + if (!options[i].type) + elog(elevel, "invalid option \"%s\"", key); + } + } + + fclose(fp); +} + +static const char * +skip_space(const char *str, const char *line) +{ + while (IsSpace(*str)) { str++; } + return str; +} + +static const char * +get_next_token(const char *src, char *dst, const char *line) +{ + const char *s; + size_t i; + size_t j; + + if ((s = skip_space(src, line)) == NULL) + return NULL; + + /* parse quoted string */ + if (*s == '\'') + { + s++; + for (i = 0, j = 0; s[i] != '\0'; i++) + { + if (s[i] == '\\') + { + i++; + switch (s[i]) + { + case 'b': + dst[j] = '\b'; + break; + case 'f': + dst[j] = '\f'; + break; + case 'n': + dst[j] = '\n'; + break; + case 'r': + dst[j] = '\r'; + break; + case 't': + dst[j] = '\t'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int k; + long octVal = 0; + + for (k = 0; + s[i + k] >= '0' && s[i + k] <= '7' && k < 3; + k++) + octVal = (octVal << 3) + (s[i + k] - '0'); + i += k - 1; + dst[j] = ((char) octVal); + } + break; + default: + dst[j] = s[i]; + break; + } + } + else if (s[i] == '\'') + { + i++; + /* doubled quote becomes just one quote */ + if (s[i] == '\'') + dst[j] = s[i]; + else + break; + } + else + dst[j] = s[i]; + j++; + } + } + else + { + i = j = strcspn(s, "# \n\r\t\v"); + memcpy(dst, s, j); + } + + dst[j] = '\0'; + return s + i; +} + +static bool +parse_pair(const char buffer[], char key[], char value[]) +{ + const char *start; + const char *end; + + key[0] = value[0] = '\0'; + + /* + * parse key + */ + start = buffer; + if ((start = skip_space(start, buffer)) == NULL) + return false; + + end = start + strcspn(start, "=# \n\r\t\v"); + + /* skip blank buffer */ + if (end - start <= 0) + { + if (*start == '=') + elog(WARNING, "syntax error in \"%s\"", buffer); + return false; + } + + /* key found */ + strncpy(key, start, end - start); + key[end - start] = '\0'; + + /* find key and value split char */ + if ((start = skip_space(end, buffer)) == NULL) + return false; + + if (*start != '=') + { + elog(WARNING, "syntax error in \"%s\"", buffer); + return false; + } + + start++; + + /* + * parse value + */ + if ((end = get_next_token(start, value, buffer)) == NULL) + return false; + + if ((start = skip_space(end, buffer)) == NULL) + return false; + + if (*start != '\0' && *start != '#') + { + elog(WARNING, "syntax error in \"%s\"", buffer); + return false; + } + + return true; +} + +/* + * execute - Execute a SQL and return the result. + */ +PGresult * +execute(const char *query, int nParams, const char **params) +{ + return pgut_execute(connection, query, nParams, params); +} + +PGresult * +execute_elevel(const char *query, int nParams, const char **params, int elevel) +{ + return pgut_execute_elevel(connection, query, nParams, params, elevel); +} + +/* + * command - Execute a SQL and discard the result. + */ +ExecStatusType +command(const char *query, int nParams, const char **params) +{ + return pgut_command(connection, query, nParams, params); +} + +static void +set_elevel(pgut_option *opt, const char *arg) +{ + pgut_log_level = parse_elevel(arg); +} + +static pgut_option default_options[] = +{ + { 'b', 'e', "echo" , &pgut_echo }, + { 'f', 'E', "elevel" , set_elevel }, + { 's', 'd', "dbname" , &dbname }, + { 's', 'h', "host" , &host }, + { 's', 'p', "port" , &port }, + { 's', 'U', "username" , &username }, + { 'y', 'w', "no-password" , &prompt_password }, + { 'Y', 'W', "password" , &prompt_password }, + { 0 } +}; + +static size_t +option_length(const pgut_option opts[]) +{ + size_t len; + for (len = 0; opts && opts[len].type; len++) { } + return len; +} + +static pgut_option * +option_find(int c, pgut_option opts1[], pgut_option opts2[]) +{ + size_t i; + + for (i = 0; opts1 && opts1[i].type; i++) + if (opts1[i].sname == c) + return &opts1[i]; + for (i = 0; opts2 && opts2[i].type; i++) + if (opts2[i].sname == c) + return &opts2[i]; + + return NULL; /* not found */ +} + +/* + * Returns the current user name. + */ +static char * +get_username(void) +{ + char *ret; + +#ifndef WIN32 + struct passwd *pw; + + pw = getpwuid(geteuid()); + ret = (pw ? pw->pw_name : NULL); +#else + static char username[128]; /* remains after function execute */ + DWORD len = sizeof(username) - 1; + + if (GetUserNameA(username, &len)) + ret = username; + else + { + _dosmaperr(GetLastError()); + ret = NULL; + } +#endif + + if (ret == NULL) + ereport(ERROR, + (errcode_errno(), + errmsg("could not get current user name: "))); + return ret; +} + +static int +option_has_arg(char type) +{ + switch (type) + { + case 'b': + case 'B': + case 'y': + case 'Y': + return no_argument; + default: + return required_argument; + } +} + +static void +option_copy(struct option dst[], const pgut_option opts[], size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + { + dst[i].name = opts[i].lname; + dst[i].has_arg = option_has_arg(opts[i].type); + dst[i].flag = NULL; + dst[i].val = opts[i].sname; + } +} + +static struct option * +option_merge(const pgut_option opts1[], const pgut_option opts2[]) +{ + struct option *result; + size_t len1 = option_length(opts1); + size_t len2 = option_length(opts2); + size_t n = len1 + len2; + + result = pgut_newarray(struct option, n + 1); + option_copy(result, opts1, len1); + option_copy(result + len1, opts2, len2); + memset(&result[n], 0, sizeof(pgut_option)); + + return result; +} + +static char * +longopts_to_optstring(const struct option opts[]) +{ + size_t len; + char *result; + char *s; + + for (len = 0; opts[len].name; len++) { } + result = pgut_malloc(len * 2 + 1); + + s = result; + for (len = 0; opts[len].name; len++) + { + if (!isprint(opts[len].val)) + continue; + *s++ = opts[len].val; + if (opts[len].has_arg != no_argument) + *s++ = ':'; + } + *s = '\0'; + + return result; +} + +int +pgut_getopt(int argc, char **argv, pgut_option options[]) +{ + int c; + int optindex = 0; + char *optstring; + struct option *longopts; + pgut_option *opt; + + pgut_init(argc, argv); + + /* Help message and version are handled at first. */ + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + help(true); + exit(1); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + fprintf(stderr, "%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); + exit(1); + } + } + + /* Merge default and user options. */ + longopts = option_merge(default_options, options); + optstring = longopts_to_optstring(longopts); + + /* Assign named options */ + while ((c = getopt_long(argc, argv, optstring, longopts, &optindex)) != -1) + { + opt = option_find(c, default_options, options); + pgut_setopt(opt, optarg, SOURCE_CMDLINE); + } + + /* Read environment variables */ + option_from_env(options); + (void) (dbname || + (dbname = getenv("PGDATABASE")) || + (dbname = getenv("PGUSER")) || + (dbname = get_username())); + + return optind; +} + +void +help(bool details) +{ + pgut_help(details); + + if (details) + { + printf("\nConnection options:\n"); + printf(" -d, --dbname=DBNAME database to connect\n"); + printf(" -h, --host=HOSTNAME database server host or socket directory\n"); + printf(" -p, --port=PORT database server port\n"); + printf(" -U, --username=USERNAME user name to connect as\n"); + printf(" -w, --no-password never prompt for password\n"); + printf(" -W, --password force password prompt\n"); + } + + printf("\nGeneric options:\n"); + if (details) + { + printf(" -e, --echo echo queries\n"); + printf(" -E, --elevel=LEVEL set output message level\n"); + } + printf(" --help show this help, then exit\n"); + printf(" --version output version information, then exit\n"); + + if (details && (PROGRAM_URL || PROGRAM_EMAIL)) + { + printf("\n"); + if (PROGRAM_URL) + printf("Read the website for details. <%s>\n", PROGRAM_URL); + if (PROGRAM_EMAIL) + printf("Report bugs to <%s>.\n", PROGRAM_EMAIL); + } +} diff --git a/bin/pgut/pgut-fe.h b/bin/pgut/pgut-fe.h new file mode 100755 index 0000000..48d48ef --- /dev/null +++ b/bin/pgut/pgut-fe.h @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * pgut-fe.h + * + * Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + * + *------------------------------------------------------------------------- + */ + +#ifndef PGUT_FE_H +#define PGUT_FE_H + +#include "pgut.h" + +typedef enum pgut_optsrc +{ + SOURCE_DEFAULT, + SOURCE_ENV, + SOURCE_FILE, + SOURCE_CMDLINE, + SOURCE_CONST +} pgut_optsrc; + +/* + * type: + * b: bool (true) + * B: bool (false) + * f: pgut_optfn + * i: 32bit signed integer + * u: 32bit unsigned integer + * I: 64bit signed integer + * U: 64bit unsigned integer + * s: string + * t: time_t + * y: YesNo (YES) + * Y: YesNo (NO) + */ +typedef struct pgut_option +{ + char type; + char sname; /* short name */ + const char *lname; /* long name */ + void *var; /* pointer to variable */ + pgut_optsrc allowed; /* allowed source */ + pgut_optsrc source; /* actual source */ +} pgut_option; + +typedef void (*pgut_optfn) (pgut_option *opt, const char *arg); + + +extern char *dbname; +extern char *host; +extern char *port; +extern char *username; +extern char *password; +extern YesNo prompt_password; + +extern PGconn *connection; + +extern void pgut_help(bool details); +extern void help(bool details); + +extern void disconnect(void); +extern void reconnect(int elevel); +extern PGresult *execute(const char *query, int nParams, const char **params); +extern PGresult *execute_elevel(const char *query, int nParams, const char **params, int elevel); +extern ExecStatusType command(const char *query, int nParams, const char **params); + +extern int pgut_getopt(int argc, char **argv, pgut_option options[]); +extern void pgut_readopt(const char *path, pgut_option options[], int elevel); +extern void pgut_setopt(pgut_option *opt, const char *optarg, pgut_optsrc src); +extern bool pgut_keyeq(const char *lhs, const char *rhs); + +#endif /* PGUT_FE_H */ diff --git a/bin/pgut/pgut.c b/bin/pgut/pgut.c index 43691d2..8ae9353 100755 --- a/bin/pgut/pgut.c +++ b/bin/pgut/pgut.c @@ -7,18 +7,26 @@ *------------------------------------------------------------------------- */ -#define FRONTEND #include "postgres_fe.h" #include "libpq/pqsignal.h" -#include #include #include #include -#include #include "pgut.h" +#ifdef PGUT_MULTI_THREADED +#include "pgut-pthread.h" +static pthread_key_t pgut_edata_key; +static pthread_mutex_t pgut_conn_mutex; +#define pgut_conn_lock() pthread_mutex_lock(&pgut_conn_mutex) +#define pgut_conn_unlock() pthread_mutex_unlock(&pgut_conn_mutex) +#else +#define pgut_conn_lock() ((void) 0) +#define pgut_conn_unlock() ((void) 0) +#endif + /* old gcc doesn't have LLONG_MAX. */ #ifndef LLONG_MAX #if defined(HAVE_LONG_INT_64) || !defined(HAVE_LONG_LONG_INT_64) @@ -30,224 +38,65 @@ const char *PROGRAM_NAME = NULL; -const char *dbname = NULL; -const char *host = NULL; -const char *port = NULL; -const char *username = NULL; -char *password = NULL; -bool debug = false; -bool quiet = false; +/* Interrupted by SIGINT (Ctrl+C) ? */ +bool interrupted = false; +static bool in_cleanup = false; -#ifndef PGUT_NO_PROMPT -YesNo prompt_password = DEFAULT; -#endif +/* log min messages */ +int pgut_log_level = INFO; +int pgut_abort_level = ERROR; +bool pgut_echo = false; /* Database connections */ -PGconn *connection = NULL; -static PGcancel *volatile cancel_conn = NULL; +typedef struct pgutConn pgutConn; +struct pgutConn +{ + PGconn *conn; + PGcancel *cancel; + pgutConn *next; +}; -/* Interrupted by SIGINT (Ctrl+C) ? */ -bool interrupted = false; -static bool in_cleanup = false; - -static bool parse_pair(const char buffer[], char key[], char value[]); +static pgutConn *pgut_connections; /* Connection routines */ static void init_cancel_handler(void); -static void on_before_exec(PGconn *conn); -static void on_after_exec(void); +static void on_before_exec(pgutConn *conn); +static void on_after_exec(pgutConn *conn); static void on_interrupt(void); static void on_cleanup(void); static void exit_or_abort(int exitcode); -static const char *get_username(void); -static pgut_option default_options[] = +void +pgut_init(int argc, char **argv) { - { 'b', '!', "debug" , &debug }, - { 's', 'd', "dbname" , &dbname }, - { 's', 'h', "host" , &host }, - { 's', 'p', "port" , &port }, - { 'b', 'q', "quiet" , &quiet }, - { 's', 'U', "username" , &username }, -#ifndef PGUT_NO_PROMPT - { 'y', 'w', "no-password" , &prompt_password }, - { 'Y', 'W', "password" , &prompt_password }, + if (PROGRAM_NAME == NULL) + { + PROGRAM_NAME = get_progname(argv[0]); + set_pglocale_pgservice(argv[0], "pgscripts"); + +#ifdef PGUT_MULTI_THREADED + pthread_key_create(&pgut_edata_key, NULL); + pthread_mutex_init(&pgut_conn_mutex, NULL); #endif - { 0 } -}; -static size_t -option_length(const pgut_option opts[]) -{ - size_t len; - for (len = 0; opts && opts[len].type; len++) { } - return len; -} +#if PG_VERSION_NUM >= 90000 + /* application_name for 9.0 or newer versions */ + if (getenv("PGAPPNAME") == NULL) + pgut_putenv("PGAPPNAME", PROGRAM_NAME); +#endif -static int -option_has_arg(char type) -{ - switch (type) - { - case 'b': - case 'B': - case 'y': - case 'Y': - return no_argument; - default: - return required_argument; + init_cancel_handler(); + atexit(on_cleanup); } } -static void -option_copy(struct option dst[], const pgut_option opts[], size_t len) -{ - size_t i; - - for (i = 0; i < len; i++) - { - dst[i].name = opts[i].lname; - dst[i].has_arg = option_has_arg(opts[i].type); - dst[i].flag = NULL; - dst[i].val = opts[i].sname; - } -} - -static struct option * -option_merge(const pgut_option opts1[], const pgut_option opts2[]) -{ - struct option *result; - size_t len1 = option_length(opts1); - size_t len2 = option_length(opts2); - size_t n = len1 + len2; - - result = pgut_newarray(struct option, n + 1); - option_copy(result, opts1, len1); - option_copy(result + len1, opts2, len2); - memset(&result[n], 0, sizeof(pgut_option)); - - return result; -} - -static pgut_option * -option_find(int c, pgut_option opts1[], pgut_option opts2[]) -{ - size_t i; - - for (i = 0; opts1 && opts1[i].type; i++) - if (opts1[i].sname == c) - return &opts1[i]; - for (i = 0; opts2 && opts2[i].type; i++) - if (opts2[i].sname == c) - return &opts2[i]; - - return NULL; /* not found */ -} - void -pgut_setopt(pgut_option *opt, const char *optarg, pgut_optsrc src) +pgut_putenv(const char *key, const char *value) { - const char *message; + char buf[1024]; - if (opt == NULL) - { - fprintf(stderr, "Try \"%s --help\" for more information.\n", PROGRAM_NAME); - exit_or_abort(ERROR_ARGS); - } - - if (opt->source > src) - { - /* high prior value has been set already. */ - return; - } - else if (src >= SOURCE_CMDLINE && opt->source >= src) - { - /* duplicated option in command line */ - message = "specified only once"; - } - else - { - /* can be overwritten if non-command line source */ - opt->source = src; - - switch (opt->type) - { - case 'b': - case 'B': - if (optarg == NULL) - { - *((bool *) opt->var) = (opt->type == 'b'); - return; - } - else if (parse_bool(optarg, (bool *) opt->var)) - { - return; - } - message = "a boolean"; - break; - case 'f': - ((pgut_optfn) opt->var)(opt, optarg); - return; - case 'i': - if (parse_int32(optarg, opt->var)) - return; - message = "a 32bit signed integer"; - break; - case 'u': - if (parse_uint32(optarg, opt->var)) - return; - message = "a 32bit unsigned integer"; - break; - case 'I': - if (parse_int64(optarg, opt->var)) - return; - message = "a 64bit signed integer"; - break; - case 'U': - if (parse_uint64(optarg, opt->var)) - return; - message = "a 64bit unsigned integer"; - break; - case 's': - if (opt->source != SOURCE_DEFAULT) - free(*(char **) opt->var); - *(char **) opt->var = pgut_strdup(optarg); - return; - case 't': - if (parse_time(optarg, opt->var)) - return; - message = "a time"; - break; - case 'y': - case 'Y': - if (optarg == NULL) - { - *(YesNo *) opt->var = (opt->type == 'y' ? YES : NO); - return; - } - else - { - bool value; - if (parse_bool(optarg, &value)) - { - *(YesNo *) opt->var = (value ? YES : NO); - return; - } - } - message = "a boolean"; - break; - default: - elog(ERROR, "invalid option type: %c", opt->type); - return; /* keep compiler quiet */ - } - } - - if (isprint(opt->sname)) - elog(ERROR_ARGS, "option -%c, --%s should be %s: '%s'", - opt->sname, opt->lname, message, optarg); - else - elog(ERROR_ARGS, "option --%s should be %s: '%s'", - opt->lname, message, optarg); + snprintf(buf, lengthof(buf), "%s=%s", key, value); + putenv(pgut_strdup(buf)); /* putenv requires malloc'ed buffer */ } /* @@ -368,7 +217,7 @@ parse_int32(const char *value, int32 *result) if (errno == ERANGE || val != (int64) ((int32) val)) return false; - *result = val; + *result = (int32) val; return true; } @@ -397,7 +246,7 @@ parse_uint32(const char *value, uint32 *result) if (errno == ERANGE || val != (uint64) ((uint32) val)) return false; - *result = val; + *result = (uint32) val; return true; } @@ -419,10 +268,11 @@ parse_int64(const char *value, int64 *result) } errno = 0; -#if defined(HAVE_LONG_INT_64) +#ifdef WIN32 + val = _strtoi64(value, &endptr, 0); +#elif defined(HAVE_LONG_INT_64) val = strtol(value, &endptr, 0); #elif defined(HAVE_LONG_LONG_INT_64) - val = strtoll(value, &endptr, 0); #else val = strtol(value, &endptr, 0); #endif @@ -460,7 +310,9 @@ parse_uint64(const char *value, uint64 *result) } errno = 0; -#if defined(HAVE_LONG_INT_64) +#ifdef WIN32 + val = _strtoui64(value, &endptr, 0); +#elif defined(HAVE_LONG_INT_64) val = strtoul(value, &endptr, 0); #elif defined(HAVE_LONG_LONG_INT_64) val = strtoull(value, &endptr, 0); @@ -528,351 +380,11 @@ parse_time(const char *value, time_t *time) } static char * -longopts_to_optstring(const struct option opts[]) +prompt_for_password(void) { - size_t len; - char *result; - char *s; - - for (len = 0; opts[len].name; len++) { } - result = pgut_malloc(len * 2 + 1); - - s = result; - for (len = 0; opts[len].name; len++) - { - if (!isprint(opts[len].val)) - continue; - *s++ = opts[len].val; - if (opts[len].has_arg != no_argument) - *s++ = ':'; - } - *s = '\0'; - - return result; + return simple_prompt("Password: ", 100, false); } -static void -option_from_env(pgut_option options[]) -{ - size_t i; - - for (i = 0; options && options[i].type; i++) - { - pgut_option *opt = &options[i]; - char name[256]; - size_t j; - const char *s; - const char *value; - - if (opt->source > SOURCE_ENV || - opt->allowed == SOURCE_DEFAULT || opt->allowed > SOURCE_ENV) - continue; - - for (s = opt->lname, j = 0; *s && j < lengthof(name) - 1; s++, j++) - { - if (strchr("-_ ", *s)) - name[j] = '_'; /* - to _ */ - else - name[j] = toupper(*s); - } - name[j] = '\0'; - - if ((value = getenv(name)) != NULL) - pgut_setopt(opt, value, SOURCE_ENV); - } -} - -int -pgut_getopt(int argc, char **argv, pgut_option options[]) -{ - int c; - int optindex = 0; - char *optstring; - struct option *longopts; - pgut_option *opt; - - if (PROGRAM_NAME == NULL) - { - PROGRAM_NAME = get_progname(argv[0]); - set_pglocale_pgservice(argv[0], "pgscripts"); - } - - /* Help message and version are handled at first. */ - if (argc > 1) - { - if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) - { - help(true); - exit_or_abort(HELP); - } - if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) - { - fprintf(stderr, "%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); - exit_or_abort(HELP); - } - } - - /* Merge default and user options. */ - longopts = option_merge(default_options, options); - optstring = longopts_to_optstring(longopts); - - /* Assign named options */ - while ((c = getopt_long(argc, argv, optstring, longopts, &optindex)) != -1) - { - opt = option_find(c, default_options, options); - pgut_setopt(opt, optarg, SOURCE_CMDLINE); - } - - /* Read environment variables */ - option_from_env(options); - (void) (dbname || - (dbname = getenv("PGDATABASE")) || - (dbname = getenv("PGUSER")) || - (dbname = get_username())); - - init_cancel_handler(); - atexit(on_cleanup); - - return optind; -} - -/* compare two strings ignore cases and ignore -_ */ -bool -pgut_keyeq(const char *lhs, const char *rhs) -{ - for (; *lhs && *rhs; lhs++, rhs++) - { - if (strchr("-_ ", *lhs)) - { - if (!strchr("-_ ", *rhs)) - return false; - } - else if (ToLower(*lhs) != ToLower(*rhs)) - return false; - } - - return *lhs == '\0' && *rhs == '\0'; -} - -/* - * Get configuration from configuration file. - */ -void -pgut_readopt(const char *path, pgut_option options[], int elevel) -{ - FILE *fp; - char buf[1024]; - char key[1024]; - char value[1024]; - - if (!options) - return; - - if ((fp = pgut_fopen(path, "rt", true)) == NULL) - return; - - while (fgets(buf, lengthof(buf), fp)) - { - size_t i; - - for (i = strlen(buf); i > 0 && IsSpace(buf[i - 1]); i--) - buf[i - 1] = '\0'; - - if (parse_pair(buf, key, value)) - { - for (i = 0; options[i].type; i++) - { - pgut_option *opt = &options[i]; - - if (pgut_keyeq(key, opt->lname)) - { - if (opt->allowed == SOURCE_DEFAULT || - opt->allowed > SOURCE_FILE) - elog(elevel, "option %s cannot specified in file", opt->lname); - else if (opt->source <= SOURCE_FILE) - pgut_setopt(opt, value, SOURCE_FILE); - break; - } - } - if (!options[i].type) - elog(elevel, "invalid option \"%s\"", key); - } - } - - fclose(fp); -} - -static const char * -skip_space(const char *str, const char *line) -{ - while (IsSpace(*str)) { str++; } - return str; -} - -static const char * -get_next_token(const char *src, char *dst, const char *line) -{ - const char *s; - int i; - int j; - - if ((s = skip_space(src, line)) == NULL) - return NULL; - - /* parse quoted string */ - if (*s == '\'') - { - s++; - for (i = 0, j = 0; s[i] != '\0'; i++) - { - if (s[i] == '\\') - { - i++; - switch (s[i]) - { - case 'b': - dst[j] = '\b'; - break; - case 'f': - dst[j] = '\f'; - break; - case 'n': - dst[j] = '\n'; - break; - case 'r': - dst[j] = '\r'; - break; - case 't': - dst[j] = '\t'; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - { - int k; - long octVal = 0; - - for (k = 0; - s[i + k] >= '0' && s[i + k] <= '7' && k < 3; - k++) - octVal = (octVal << 3) + (s[i + k] - '0'); - i += k - 1; - dst[j] = ((char) octVal); - } - break; - default: - dst[j] = s[i]; - break; - } - } - else if (s[i] == '\'') - { - i++; - /* doubled quote becomes just one quote */ - if (s[i] == '\'') - dst[j] = s[i]; - else - break; - } - else - dst[j] = s[i]; - j++; - } - } - else - { - i = j = strcspn(s, "# \n\r\t\v"); - memcpy(dst, s, j); - } - - dst[j] = '\0'; - return s + i; -} - -static bool -parse_pair(const char buffer[], char key[], char value[]) -{ - const char *start; - const char *end; - - key[0] = value[0] = '\0'; - - /* - * parse key - */ - start = buffer; - if ((start = skip_space(start, buffer)) == NULL) - return false; - - end = start + strcspn(start, "=# \n\r\t\v"); - - /* skip blank buffer */ - if (end - start <= 0) - { - if (*start == '=') - elog(WARNING, "syntax error in \"%s\"", buffer); - return false; - } - - /* key found */ - strncpy(key, start, end - start); - key[end - start] = '\0'; - - /* find key and value split char */ - if ((start = skip_space(end, buffer)) == NULL) - return false; - - if (*start != '=') - { - elog(WARNING, "syntax error in \"%s\"", buffer); - return false; - } - - start++; - - /* - * parse value - */ - if ((end = get_next_token(start, value, buffer)) == NULL) - return false; - - if ((start = skip_space(end, buffer)) == NULL) - return false; - - if (*start != '\0' && *start != '#') - { - elog(WARNING, "syntax error in \"%s\"", buffer); - return false; - } - - return true; -} - -#ifndef PGUT_NO_PROMPT -/* - * Ask the user for a password; 'username' is the username the - * password is for, if one has been explicitly specified. - * Set malloc'd string to the global variable 'password'. - */ -static char * -prompt_for_password(const char *username) -{ - if (username == NULL) - return simple_prompt("Password: ", 100, false); - else - { - char message[256]; - snprintf(message, lengthof(message), "Password for user %s: ", username); - return simple_prompt(message, 100, false); - } -} -#endif - #if PG_VERSION_NUM < 80300 static bool PQconnectionNeedsPassword(PGconn *conn) @@ -881,200 +393,150 @@ PQconnectionNeedsPassword(PGconn *conn) } #endif -static PGconn * -do_connect(int elevel, - const char *my_host, - const char *my_port, - const char *my_dbname, - const char *my_username, - const char *my_password, - char **new_password) +PGconn * +pgut_connect(const char *info, YesNo prompt, int elevel) { char *passwd; CHECK_FOR_INTERRUPTS(); - if (new_password) - *new_password = NULL; - -#ifndef PGUT_NO_PROMPT - if (prompt_password == YES) - passwd = prompt_for_password(my_username); + if (prompt == YES) + passwd = prompt_for_password(); else -#endif - passwd = (char *) my_password; + passwd = NULL; /* Start the connection. Loop until we have a password if requested by backend. */ for (;;) { PGconn *conn; - conn = PQsetdbLogin(my_host, my_port, NULL, NULL, my_dbname, my_username, passwd); + conn = PQconnectdb(info); if (PQstatus(conn) == CONNECTION_OK) { - if (new_password) - *new_password = passwd; - else - free(passwd); + pgutConn *c; + + free(passwd); + + c = pgut_new(pgutConn); + c->conn = conn; + c->cancel = NULL; + + pgut_conn_lock(); + c->next = pgut_connections; + pgut_connections = c; + pgut_conn_unlock(); + return conn; } - if (passwd != my_password) - free(passwd); - -#ifndef PGUT_NO_PROMPT - if (conn && PQconnectionNeedsPassword(conn) && prompt_password != NO) + if (conn && PQconnectionNeedsPassword(conn) && prompt != NO) { PQfinish(conn); - passwd = prompt_for_password(username); + free(passwd); + passwd = prompt_for_password(); continue; } -#endif - elog(elevel, "could not connect to database %s: %s", - (dbname ? dbname : "(default)"), PQerrorMessage(conn)); + ereport(elevel, + (errcode(E_PG_CONNECT), + errmsg("could not connect to database with \"%s\": %s", + info, PQerrorMessage(conn)))); PQfinish(conn); return NULL; } } -PGconn * -pgut_connect(int elevel) -{ - PGconn *conn; - char *new_password; - - conn = do_connect(elevel, host, port, dbname, username, password, &new_password); - - /* update password if a new one is supplied */ - if (password != new_password) - { - free(password); - password = new_password; - } - - return conn; -} - -PGconn * -pgut_connectdb(const char *conninfo, int elevel) -{ - PGconn *conn; - const char *my_host = NULL; - const char *my_port = NULL; - const char *my_dbname = NULL; - const char *my_username = NULL; - const char *my_password = NULL; - PQconninfoOption *options; - char *message = NULL; - - options = PQconninfoParse(conninfo, &message); - if (message != NULL) - { - elog(elevel, "%s", message); - PQfreemem(message); - return NULL; - } - else if (options) - { - PQconninfoOption *option; - - for (option = options; option->keyword != NULL; option++) - { - if (!option->val || !option->val[0]) - continue; - if (strcmp(option->keyword, "host") == 0) - my_host = option->val; - else if (strcmp(option->keyword, "port") == 0) - my_port = option->val; - else if (strcmp(option->keyword, "dbname") == 0) - my_dbname = option->val; - else if (strcmp(option->keyword, "user") == 0) - my_username = option->val; - else if (strcmp(option->keyword, "password") == 0) - my_password = option->val; - else - elog(WARNING, "unsupported connection option: %s = %s", - option->keyword, option->val); - } - } - - conn = do_connect(elevel, my_host, my_port, my_dbname, my_username, my_password, NULL); - - PQconninfoFree(options); - - return conn; -} - void pgut_disconnect(PGconn *conn) { if (conn) { + pgutConn *c; + pgutConn **prev; + + pgut_conn_lock(); + prev = &pgut_connections; + for (c = pgut_connections; c; c = c->next) + { + if (c->conn == conn) + { + *prev = c->next; + break; + } + prev = &c->next; + } + pgut_conn_unlock(); + PQfinish(conn); - if (conn == connection) - connection = NULL; } } -/* - * the result is also available with the global variable 'connection'. - */ -PGconn * -reconnect_elevel(int elevel) -{ - disconnect(); - return connection = pgut_connect(elevel); -} - void -reconnect(void) +pgut_disconnect_all(void) { - reconnect_elevel(ERROR_PG_CONNECT); -} - -void -disconnect(void) -{ - if (connection) + pgut_conn_lock(); + while (pgut_connections) { - PQfinish(connection); - connection = NULL; + PQfinish(pgut_connections->conn); + pgut_connections = pgut_connections->next; } + pgut_conn_unlock(); +} + +static void +echo_query(const char *query, int nParams, const char **params) +{ + int i; + + if (strchr(query, '\n')) + elog(LOG, "(query)\n%s", query); + else + elog(LOG, "(query) %s", query); + for (i = 0; i < nParams; i++) + elog(LOG, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)"); } PGresult * -pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, int elevel) +pgut_execute(PGconn* conn, const char *query, int nParams, const char **params) +{ + return pgut_execute_elevel(conn, query, nParams, params, ERROR); +} + +PGresult * +pgut_execute_elevel(PGconn* conn, const char *query, int nParams, const char **params, int elevel) { PGresult *res; + pgutConn *c; CHECK_FOR_INTERRUPTS(); /* write query to elog if debug */ - if (debug) - { - int i; - - if (strchr(query, '\n')) - elog(LOG, "(query)\n%s", query); - else - elog(LOG, "(query) %s", query); - for (i = 0; i < nParams; i++) - elog(LOG, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)"); - } + if (pgut_echo) + echo_query(query, nParams, params); if (conn == NULL) { - elog(elevel, "not connected"); + ereport(elevel, + (errcode(E_PG_COMMAND), + errmsg("not connected"))); return NULL; } - on_before_exec(conn); + /* find connection */ + pgut_conn_lock(); + for (c = pgut_connections; c; c = c->next) + if (c->conn == conn) + break; + pgut_conn_unlock(); + + if (c) + on_before_exec(c); if (nParams == 0) res = PQexec(conn, query); else res = PQexecParams(conn, query, nParams, NULL, params, NULL, NULL, 0); - on_after_exec(); + if (c) + on_after_exec(c); switch (PQresultStatus(res)) { @@ -1083,8 +545,10 @@ pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, case PGRES_COPY_IN: break; default: - elog(elevel, "query failed: %squery was: %s", - PQerrorMessage(conn), query); + ereport(elevel, + (errcode(E_PG_COMMAND), + errmsg("query failed: %s", PQerrorMessage(conn)), + errdetail("query was: %s", query))); break; } @@ -1092,41 +556,52 @@ pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, } ExecStatusType -pgut_command(PGconn* conn, const char *query, int nParams, const char **params, int elevel) +pgut_command(PGconn* conn, const char *query, int nParams, const char **params) { PGresult *res; ExecStatusType code; - res = pgut_execute(conn, query, nParams, params, elevel); + res = pgut_execute(conn, query, nParams, params); code = PQresultStatus(res); PQclear(res); return code; } +/* commit if needed */ bool -pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int elevel) +pgut_commit(PGconn *conn) +{ + if (conn && PQtransactionStatus(conn) != PQTRANS_IDLE) + return pgut_command(conn, "COMMIT", 0, NULL) == PGRES_COMMAND_OK; + + return true; /* nothing to do */ +} + +/* rollback if needed */ +void +pgut_rollback(PGconn *conn) +{ + if (conn && PQtransactionStatus(conn) != PQTRANS_IDLE) + pgut_command(conn, "ROLLBACK", 0, NULL); +} + +bool +pgut_send(PGconn* conn, const char *query, int nParams, const char **params) { int res; CHECK_FOR_INTERRUPTS(); /* write query to elog if debug */ - if (debug) - { - int i; - - if (strchr(query, '\n')) - elog(LOG, "(query)\n%s", query); - else - elog(LOG, "(query) %s", query); - for (i = 0; i < nParams; i++) - elog(LOG, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)"); - } + if (pgut_echo) + echo_query(query, nParams, params); if (conn == NULL) { - elog(elevel, "not connected"); + ereport(ERROR, + (errcode(E_PG_COMMAND), + errmsg("not connected"))); return false; } @@ -1137,8 +612,10 @@ pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int if (res != 1) { - elog(elevel, "query failed: %squery was: %s", - PQerrorMessage(conn), query); + ereport(ERROR, + (errcode(E_PG_COMMAND), + errmsg("query failed: %s", PQerrorMessage(conn)), + errdetail("query was: %s", query))); return false; } @@ -1199,30 +676,6 @@ pgut_wait(int num, PGconn *connections[], struct timeval *timeout) return -1; } -PGresult * -execute_elevel(const char *query, int nParams, const char **params, int elevel) -{ - return pgut_execute(connection, query, nParams, params, elevel); -} - -/* - * execute - Execute a SQL and return the result, or exit_or_abort() if failed. - */ -PGresult * -execute(const char *query, int nParams, const char **params) -{ - return execute_elevel(query, nParams, params, ERROR_PG_COMMAND); -} - -/* - * command - Execute a SQL and discard the result, or exit_or_abort() if failed. - */ -void -command(const char *query, int nParams, const char **params) -{ - PQclear(execute(query, nParams, params)); -} - /* * CHECK_FOR_INTERRUPTS - Ctrl+C pressed? */ @@ -1230,56 +683,284 @@ void CHECK_FOR_INTERRUPTS(void) { if (interrupted && !in_cleanup) - elog(ERROR_INTERRUPTED, "interrupted"); + ereport(FATAL, (errcode(EINTR), errmsg("interrupted"))); } /* - * elog - log to stderr and exit if ERROR or FATAL + * elog staffs */ +typedef struct pgutErrorData +{ + int elevel; + int save_errno; + int code; + StringInfoData msg; + StringInfoData detail; +} pgutErrorData; + +/* FIXME: support recursive error */ +static pgutErrorData * +getErrorData(void) +{ +#ifdef PGUT_MULTI_THREADED + pgutErrorData *edata = pthread_getspecific(pgut_edata_key); + + if (edata == NULL) + { + edata = pgut_new(pgutErrorData); + memset(edata, 0, sizeof(pgutErrorData)); + pthread_setspecific(pgut_edata_key, edata); + } + + return edata; +#else + static pgutErrorData edata; + + return &edata; +#endif +} + +static pgutErrorData * +pgut_errinit(int elevel) +{ + int save_errno = errno; + pgutErrorData *edata = getErrorData(); + + edata->elevel = elevel; + edata->save_errno = save_errno; + edata->code = (elevel >= ERROR ? 1 : 0); + + /* reset msg */ + if (edata->msg.data) + resetStringInfo(&edata->msg); + else + initStringInfo(&edata->msg); + + /* reset detail */ + if (edata->detail.data) + resetStringInfo(&edata->detail); + else + initStringInfo(&edata->detail); + + return edata; +} + +/* remove white spaces and line breaks from the end of buffer */ +static void +trimStringBuffer(StringInfo str) +{ + while (str->len > 0 && IsSpace(str->data[str->len - 1])) + str->data[--str->len] = '\0'; +} + void elog(int elevel, const char *fmt, ...) { - va_list args; + va_list args; + bool ok; + size_t len; + pgutErrorData *edata; - if (!debug && elevel <= LOG) - return; - if (quiet && elevel < WARNING) + if (elevel < pgut_abort_level && !log_required(elevel, pgut_log_level)) return; + edata = pgut_errinit(elevel); + + do + { + va_start(args, fmt); + ok = appendStringInfoVA(&edata->msg, fmt, args); + va_end(args); + } while (!ok); + len = strlen(fmt); + if (len > 2 && strcmp(fmt + len - 2, ": ") == 0) + appendStringInfoString(&edata->msg, strerror(edata->save_errno)); + trimStringBuffer(&edata->msg); + + pgut_errfinish(true); +} + +bool +pgut_errstart(int elevel) +{ + if (elevel < pgut_abort_level && !log_required(elevel, pgut_log_level)) + return false; + + pgut_errinit(elevel); + return true; +} + +void +pgut_errfinish(int dummy, ...) +{ + pgutErrorData *edata = getErrorData(); + + if (log_required(edata->elevel, pgut_log_level)) + pgut_error(edata->elevel, edata->code, + edata->msg.data ? edata->msg.data : "unknown", + edata->detail.data); + + if (pgut_abort_level <= edata->elevel && edata->elevel <= PANIC) + exit_or_abort(edata->code); +} + +#ifndef PGUT_OVERRIDE_ELOG +void +pgut_error(int elevel, int code, const char *msg, const char *detail) +{ + const char *tag = format_elevel(elevel); + + if (detail && detail[0]) + fprintf(stderr, "%s: %s\nDETAIL: %s\n", tag, msg, detail); + else + fprintf(stderr, "%s: %s\n", tag, msg); + fflush(stderr); +} +#endif + +/* + * log_required -- is elevel logically >= log_min_level? + * + * physical order: + * DEBUG < LOG < INFO < NOTICE < WARNING < ERROR < FATAL < PANIC + * log_min_messages order: + * DEBUG < INFO < NOTICE < WARNING < ERROR < LOG < FATAL < PANIC + */ +bool +log_required(int elevel, int log_min_level) +{ + if (elevel == LOG || elevel == COMMERROR) + { + if (log_min_level == LOG || log_min_level <= ERROR) + return true; + } + else if (log_min_level == LOG) + { + /* elevel != LOG */ + if (elevel >= FATAL) + return true; + } + /* Neither is LOG */ + else if (elevel >= log_min_level) + return true; + + return false; +} + +const char * +format_elevel(int elevel) +{ switch (elevel) { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + return "DEBUG"; case LOG: - fputs("LOG: ", stderr); - break; + return "LOG"; case INFO: - fputs("INFO: ", stderr); - break; + return "INFO"; case NOTICE: - fputs("NOTICE: ", stderr); - break; + return "NOTICE"; case WARNING: - fputs("WARNING: ", stderr); - break; + return "WARNING"; + case COMMERROR: + case ERROR: + return "ERROR"; case FATAL: - fputs("FATAL: ", stderr); - break; + return "FATAL"; case PANIC: - fputs("PANIC: ", stderr); - break; + return "PANIC"; default: - if (elevel >= ERROR) - fputs("ERROR: ", stderr); - break; + ereport(ERROR, + (errcode(EINVAL), + errmsg("invalid elevel: %d", elevel))); + return ""; /* unknown value; just return an empty string */ } +} - va_start(args, fmt); - vfprintf(stderr, fmt, args); - fputc('\n', stderr); - fflush(stderr); - va_end(args); +int +parse_elevel(const char *value) +{ + if (pg_strcasecmp(value, "DEBUG") == 0) + return DEBUG2; + else if (pg_strcasecmp(value, "INFO") == 0) + return INFO; + else if (pg_strcasecmp(value, "NOTICE") == 0) + return NOTICE; + else if (pg_strcasecmp(value, "LOG") == 0) + return LOG; + else if (pg_strcasecmp(value, "WARNING") == 0) + return WARNING; + else if (pg_strcasecmp(value, "ERROR") == 0) + return ERROR; + else if (pg_strcasecmp(value, "FATAL") == 0) + return FATAL; + else if (pg_strcasecmp(value, "PANIC") == 0) + return PANIC; - if (elevel > 0) - exit_or_abort(elevel); + ereport(ERROR, + (errcode(EINVAL), + errmsg("invalid elevel: %s", value))); + return ERROR; /* unknown value; just return ERROR */ +} + +int +errcode(int sqlerrcode) +{ + pgutErrorData *edata = getErrorData(); + edata->code = sqlerrcode; + return 0; +} + +int +errcode_errno(void) +{ + pgutErrorData *edata = getErrorData(); + edata->code = edata->save_errno; + return 0; +} + +int +errmsg(const char *fmt,...) +{ + pgutErrorData *edata = getErrorData(); + va_list args; + size_t len; + bool ok; + + do + { + va_start(args, fmt); + ok = appendStringInfoVA(&edata->msg, fmt, args); + va_end(args); + } while (!ok); + len = strlen(fmt); + if (len > 2 && strcmp(fmt + len - 2, ": ") == 0) + appendStringInfoString(&edata->msg, strerror(edata->save_errno)); + trimStringBuffer(&edata->msg); + + return 0; /* return value does not matter */ +} + +int +errdetail(const char *fmt,...) +{ + pgutErrorData *edata = getErrorData(); + va_list args; + bool ok; + + do + { + va_start(args, fmt); + ok = appendStringInfoVA(&edata->detail, fmt, args); + va_end(args); + } while (!ok); + trimStringBuffer(&edata->detail); + + return 0; /* return value does not matter */ } #ifdef WIN32 @@ -1289,10 +970,10 @@ static CRITICAL_SECTION cancelConnLock; /* * on_before_exec * - * Set cancel_conn to point to the current database connection. + * Set cancel to point to the current database connection. */ static void -on_before_exec(PGconn *conn) +on_before_exec(pgutConn *conn) { PGcancel *old; @@ -1304,15 +985,15 @@ on_before_exec(PGconn *conn) #endif /* Free the old one if we have one */ - old = cancel_conn; + old = conn->cancel; /* be sure handle_sigint doesn't use pointer while freeing */ - cancel_conn = NULL; + conn->cancel = NULL; if (old != NULL) PQfreeCancel(old); - cancel_conn = PQgetCancel(conn); + conn->cancel = PQgetCancel(conn->conn); #ifdef WIN32 LeaveCriticalSection(&cancelConnLock); @@ -1325,7 +1006,7 @@ on_before_exec(PGconn *conn) * Free the current cancel connection, if any, and set to NULL. */ static void -on_after_exec(void) +on_after_exec(pgutConn *conn) { PGcancel *old; @@ -1336,10 +1017,10 @@ on_after_exec(void) EnterCriticalSection(&cancelConnLock); #endif - old = cancel_conn; + old = conn->cancel; /* be sure handle_sigint doesn't use pointer while freeing */ - cancel_conn = NULL; + conn->cancel = NULL; if (old != NULL) PQfreeCancel(old); @@ -1355,18 +1036,25 @@ on_after_exec(void) static void on_interrupt(void) { + pgutConn *c; int save_errno = errno; - char errbuf[256]; /* Set interruped flag */ interrupted = true; + if (in_cleanup) + return; + /* Send QueryCancel if we are processing a database query */ - if (!in_cleanup && cancel_conn != NULL && - PQcancel(cancel_conn, errbuf, sizeof(errbuf))) + pgut_conn_lock(); + for (c = pgut_connections; c; c = c->next) { - elog(WARNING, "Cancel request sent"); + char buf[256]; + + if (c->cancel != NULL && PQcancel(c->cancel, buf, sizeof(buf))) + elog(WARNING, "Cancel request sent"); } + pgut_conn_unlock(); errno = save_errno; /* just in case the write changed it */ } @@ -1430,7 +1118,7 @@ on_cleanup(void) in_cleanup = true; interrupted = false; call_atexit_callbacks(false); - disconnect(); + pgut_disconnect_all(); } static void @@ -1449,73 +1137,30 @@ exit_or_abort(int exitcode) } } -void -help(bool details) -{ - pgut_help(details); - - if (details) - { - printf("\nConnection options:\n"); - printf(" -d, --dbname=DBNAME database to connect\n"); - printf(" -h, --host=HOSTNAME database server host or socket directory\n"); - printf(" -p, --port=PORT database server port\n"); - printf(" -U, --username=USERNAME user name to connect as\n"); -#ifndef PGUT_NO_PROMPT - printf(" -w, --no-password never prompt for password\n"); - printf(" -W, --password force password prompt\n"); -#endif - } - - printf("\nGeneric options:\n"); - if (details) - { - printf(" -q, --quiet don't write any messages\n"); - printf(" --debug debug mode\n"); - } - printf(" --help show this help, then exit\n"); - printf(" --version output version information, then exit\n"); - - if (details && (PROGRAM_URL || PROGRAM_EMAIL)) - { - printf("\n"); - if (PROGRAM_URL) - printf("Read the website for details. <%s>\n", PROGRAM_URL); - if (PROGRAM_EMAIL) - printf("Report bugs to <%s>.\n", PROGRAM_EMAIL); - } -} - /* - * Returns the current user name. + * unlike the server code, this function automatically extend the buffer. */ -static const char * -get_username(void) +bool +appendStringInfoVA(StringInfo str, const char *fmt, va_list args) { - const char *ret; + size_t avail; + int nprinted; -#ifndef WIN32 - struct passwd *pw; + Assert(str != NULL); + Assert(str->maxlen > 0); - pw = getpwuid(geteuid()); - ret = (pw ? pw->pw_name : NULL); -#else - static char username[128]; /* remains after function exit */ - DWORD len = sizeof(username) - 1; + avail = str->maxlen - str->len - 1; + nprinted = vsnprintf(str->data + str->len, avail, fmt, args); - if (GetUserName(username, &len)) - ret = username; - else + if (nprinted >= 0 && nprinted < (int) avail - 1) { - _dosmaperr(GetLastError()); - ret = NULL; + str->len += nprinted; + return true; } -#endif - if (ret == NULL) - elog(ERROR_SYSTEM, "%s: could not get current user name: %s", - PROGRAM_NAME, strerror(errno)); - return ret; + /* Double the buffer size and try again. */ + enlargePQExpBuffer(str, str->maxlen); + return false; } int @@ -1578,8 +1223,10 @@ pgut_malloc(size_t size) char *ret; if ((ret = malloc(size)) == NULL) - elog(ERROR_NOMEM, "could not allocate memory (%lu bytes): %s", - (unsigned long) size, strerror(errno)); + ereport(FATAL, + (errcode_errno(), + errmsg("could not allocate memory (%lu bytes): ", + (unsigned long) size))); return ret; } @@ -1589,8 +1236,10 @@ pgut_realloc(void *p, size_t size) char *ret; if ((ret = realloc(p, size)) == NULL) - elog(ERROR_NOMEM, "could not re-allocate memory (%lu bytes): %s", - (unsigned long) size, strerror(errno)); + ereport(FATAL, + (errcode_errno(), + errmsg("could not re-allocate memory (%lu bytes): ", + (unsigned long) size))); return ret; } @@ -1603,8 +1252,9 @@ pgut_strdup(const char *str) return NULL; if ((ret = strdup(str)) == NULL) - elog(ERROR_NOMEM, "could not duplicate string \"%s\": %s", - str, strerror(errno)); + ereport(FATAL, + (errcode_errno(), + errmsg("could not duplicate string \"%s\": ", str))); return ret; } @@ -1638,11 +1288,24 @@ strdup_trim(const char *str) return strdup_with_len(str, len); } -/* Try open file. Also create parent directries if open for writes. */ +/* + * Try open file. Also create parent directries if open for writes. + * + * mode can contain 'R', that is same as 'r' but missing ok. + */ FILE * -pgut_fopen(const char *path, const char *mode, bool missing_ok) +pgut_fopen(const char *path, const char *omode) { - FILE *fp; + FILE *fp; + bool missing_ok = false; + char mode[16]; + + strlcpy(mode, omode, lengthof(mode)); + if (mode[0] == 'R') + { + mode[0] = 'r'; + missing_ok = true; + } retry: if ((fp = fopen(path, mode)) == NULL) @@ -1662,8 +1325,9 @@ retry: } } - elog(ERROR_SYSTEM, "could not open file \"%s\": %s", - path, strerror(errno)); + ereport(ERROR, + (errcode_errno(), + errmsg("could not open file \"%s\": ", path))); } return fp; @@ -1673,7 +1337,7 @@ retry: * this tries to build all the elements of a path to a directory a la mkdir -p * we assume the path is in canonical form, i.e. uses / as the separator. */ -void +bool pgut_mkdir(const char *dirpath) { struct stat sb; @@ -1697,7 +1361,13 @@ pgut_mkdir(const char *dirpath) /* network drive */ p = strstr(p + 2, "/"); if (p == NULL) - elog(ERROR_ARGS, "invalid path \"%s\"", dirpath); + { + free(path); + ereport(ERROR, + (errcode(EINVAL), + errmsg("invalid path \"%s\"", dirpath))); + return false; + } } else if (p[1] == ':' && ((p[0] >= 'a' && p[0] <= 'z') || @@ -1747,12 +1417,17 @@ retry: if (!last) *p = '/'; } - - if (retval != 0) - elog(ERROR_SYSTEM, "could not create directory \"%s\": %s", - dirpath, strerror(errno)); - free(path); + + if (retval == 0) + { + ereport(ERROR, + (errcode_errno(), + errmsg("could not create directory \"%s\": ", dirpath))); + return false; + } + + return true; } #ifdef WIN32 @@ -1782,7 +1457,12 @@ wait_for_sockets(int nfds, fd_set *fds, struct timeval *timeout) { CHECK_FOR_INTERRUPTS(); if (errno != EINTR) - elog(ERROR_SYSTEM, "select failed: %s", strerror(errno)); + { + ereport(ERROR, + (errcode_errno(), + errmsg("select failed: "))); + return -1; + } } else return i; @@ -1865,6 +1545,9 @@ select_win32(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, con { int ret; struct timeval onesec; + fd_set save_readfds; + fd_set save_writefds; + fd_set save_exceptfds; if (remain.tv_sec > 0) { @@ -1879,17 +1562,36 @@ select_win32(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, con remain.tv_usec = 0; } + /* save fds */ + if (readfds) + memcpy(&save_readfds, readfds, sizeof(fd_set)); + if (writefds) + memcpy(&save_writefds, writefds, sizeof(fd_set)); + if (exceptfds) + memcpy(&save_exceptfds, exceptfds, sizeof(fd_set)); + ret = select(nfds, readfds, writefds, exceptfds, &onesec); - if (ret != 0) + if (ret > 0) + return ret; /* succeeded */ + else if (ret < 0) { - /* succeeded or error */ + /* error */ + _dosmaperr(WSAGetLastError()); return ret; } else if (interrupted) { errno = EINTR; - return 0; + return -1; } + + /* restore fds */ + if (readfds) + memcpy(readfds, &save_readfds, sizeof(fd_set)); + if (writefds) + memcpy(writefds, &save_writefds, sizeof(fd_set)); + if (exceptfds) + memcpy(exceptfds, &save_exceptfds, sizeof(fd_set)); } return 0; /* timeout */ diff --git a/bin/pgut/pgut.h b/bin/pgut/pgut.h index 4774535..f34548f 100755 --- a/bin/pgut/pgut.h +++ b/bin/pgut/pgut.h @@ -11,23 +11,16 @@ #define PGUT_H #include "c.h" +#include + +#ifndef WIN32 +#include +#include +#endif + #include "libpq-fe.h" #include "pqexpbuffer.h" - -#include -#include - -#if !defined(C_H) && !defined(__cplusplus) -#ifndef bool -typedef char bool; -#endif -#ifndef true -#define true ((bool) 1) -#endif -#ifndef false -#define false ((bool) 0) -#endif -#endif +#include "utils/elog.h" #define INFINITE_STR "INFINITE" @@ -38,40 +31,6 @@ typedef enum YesNo YES } YesNo; -typedef enum pgut_optsrc -{ - SOURCE_DEFAULT, - SOURCE_ENV, - SOURCE_FILE, - SOURCE_CMDLINE, - SOURCE_CONST -} pgut_optsrc; - -/* - * type: - * b: bool (true) - * B: bool (false) - * f: pgut_optfn - * i: 32bit signed integer - * u: 32bit unsigned integer - * I: 64bit signed integer - * U: 64bit unsigned integer - * s: string - * t: time_t - * y: YesNo (YES) - * Y: YesNo (NO) - */ -typedef struct pgut_option -{ - char type; - char sname; /* short name */ - const char *lname; /* long name */ - void *var; /* pointer to variable */ - pgut_optsrc allowed; /* allowed source */ - pgut_optsrc source; /* actual source */ -} pgut_option; - -typedef void (*pgut_optfn) (pgut_option *opt, const char *arg); typedef void (*pgut_atexit_callback)(bool fatal, void *userdata); /* @@ -82,52 +41,33 @@ extern const char *PROGRAM_VERSION; extern const char *PROGRAM_URL; extern const char *PROGRAM_EMAIL; -extern void pgut_help(bool details); - /* * pgut framework variables and functions */ -extern const char *dbname; -extern const char *host; -extern const char *port; -extern const char *username; -extern char *password; -extern bool debug; -extern bool quiet; +extern bool interrupted; +extern int pgut_log_level; +extern int pgut_abort_level; +extern bool pgut_echo; -#ifndef PGUT_NO_PROMPT -extern YesNo prompt_password; -#endif - -extern PGconn *connection; -extern bool interrupted; - -extern void help(bool details); -extern int pgut_getopt(int argc, char **argv, pgut_option options[]); -extern void pgut_readopt(const char *path, pgut_option options[], int elevel); -extern void pgut_setopt(pgut_option *opt, const char *optarg, pgut_optsrc src); -extern bool pgut_keyeq(const char *lhs, const char *rhs); +extern void pgut_init(int argc, char **argv); extern void pgut_atexit_push(pgut_atexit_callback callback, void *userdata); extern void pgut_atexit_pop(pgut_atexit_callback callback, void *userdata); +extern void pgut_putenv(const char *key, const char *value); /* * Database connections */ -extern PGconn *pgut_connect(int elevel); -extern PGconn *pgut_connectdb(const char *conninfo, int elevel); +extern PGconn *pgut_connect(const char *info, YesNo prompt, int elevel); extern void pgut_disconnect(PGconn *conn); -extern PGresult *pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, int elevel); -extern ExecStatusType pgut_command(PGconn* conn, const char *query, int nParams, const char **params, int elevel); -extern bool pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int elevel); +extern void pgut_disconnect_all(void); +extern PGresult *pgut_execute(PGconn* conn, const char *query, int nParams, const char **params); +PGresult *pgut_execute_elevel(PGconn* conn, const char *query, int nParams, const char **params, int elevel); +extern ExecStatusType pgut_command(PGconn* conn, const char *query, int nParams, const char **params); +extern bool pgut_commit(PGconn *conn); +extern void pgut_rollback(PGconn *conn); +extern bool pgut_send(PGconn* conn, const char *query, int nParams, const char **params); extern int pgut_wait(int num, PGconn *connections[], struct timeval *timeout); -extern PGconn *reconnect_elevel(int elevel); -extern void reconnect(void); -extern void disconnect(void); -extern PGresult *execute_elevel(const char *query, int nParams, const char **params, int elevel); -extern PGresult *execute(const char *query, int nParams, const char **params); -extern void command(const char *query, int nParams, const char **params); - /* * memory allocators */ @@ -139,37 +79,38 @@ extern char *strdup_trim(const char *str); #define pgut_new(type) ((type *) pgut_malloc(sizeof(type))) #define pgut_newarray(type, n) ((type *) pgut_malloc(sizeof(type) * (n))) +#define pgut_newvar(type, m, n) ((type *) pgut_malloc(offsetof(type, m) + (n))) /* * file operations */ -extern FILE *pgut_fopen(const char *path, const char *mode, bool missing_ok); -extern void pgut_mkdir(const char *path); +extern FILE *pgut_fopen(const char *path, const char *mode); +extern bool pgut_mkdir(const char *path); /* * elog */ -#define LOG (-4) -#define INFO (-3) -#define NOTICE (-2) -#define WARNING (-1) -#define HELP 1 -#define ERROR 2 -#define FATAL 3 -#define PANIC 4 - -#define ERROR_SYSTEM 10 /* I/O or system error */ -#define ERROR_NOMEM 11 /* memory exhausted */ -#define ERROR_ARGS 12 /* some configurations are invalid */ -#define ERROR_INTERRUPTED 13 /* interrupted by signal */ -#define ERROR_PG_COMMAND 14 /* PostgreSQL query or command error */ -#define ERROR_PG_CONNECT 15 /* PostgreSQL connection error */ +#define E_PG_CONNECT (-1) /* PostgreSQL connection error */ +#define E_PG_COMMAND (-2) /* PostgreSQL query or command error */ #undef elog -extern void -elog(int elevel, const char *fmt, ...) -__attribute__((format(printf, 2, 3))); +#undef ereport +#define ereport(elevel, rest) \ + (pgut_errstart(elevel) ? (pgut_errfinish rest) : (void) 0) +extern void elog(int elevel, const char *fmt, ...) +__attribute__((format(printf, 2, 3))); +extern const char *format_elevel(int elevel); +extern int parse_elevel(const char *value); +extern int errcode_errno(void); +extern bool log_required(int elevel, int log_min_level); +extern bool pgut_errstart(int elevel); +extern void pgut_errfinish(int dummy, ...); +extern void pgut_error(int elevel, int code, const char *msg, const char *detail); + +/* + * CHECK_FOR_INTERRUPTS + */ #undef CHECK_FOR_INTERRUPTS extern void CHECK_FOR_INTERRUPTS(void); @@ -209,6 +150,7 @@ extern void CHECK_FOR_INTERRUPTS(void); #define appendStringInfoChar appendPQExpBufferChar #define appendBinaryStringInfo appendBinaryPQExpBuffer +extern bool appendStringInfoVA(StringInfo str, const char *fmt, va_list args); extern int appendStringInfoFile(StringInfo str, FILE *fp); extern int appendStringInfoFd(StringInfo str, int fd); diff --git a/doc/index-ja.html b/doc/index-ja.html index 68c4c24..5d6b176 100755 --- a/doc/index-ja.html +++ b/doc/index-ja.html @@ -17,11 +17,21 @@

pg_reorg ホームページへようこそ


+ +

このプロジェクトでは pg_reorgpg_batch の2つのツールを頒布しています。

+

-pg_reorg は PostgreSQL のテーブルを再編成するシェルコマンドです。 +pg_reorg は PostgreSQL のテーブルを再編成するシェルコマンドです。 共有ロックや排他ロックを取得しないため、再編成中であっても行の参照や更新を行うことができます。 このモジュールは CLUSTER や VACUUM FULL コマンドのより良い代替になります。

+ +

+pg_batch は PostgreSQL のためのSQLジョブ実行プログラムです。 +ジョブ一覧生成するスクリプトを SQL として外部から与え、その出力 SQL をジョブとしてシリアルまたはパラレルに実行します。 +VACUUM を行うスクリプトが付属しており、"より良い vacuumdb" として利用できます。 +

+

この pg_reorg プロジェクトは PostgreSQL コミュニティによる pgFoundry の中のプロジェクトです。

  • ダウンロード : ソースコードのほか、Windows 用バイナリもダウンロードできます。
  • @@ -34,9 +44,11 @@ pg_reorg は PostgreSQL のテーブルを再編成するシェルコマンド

    ドキュメント

    -

    -ドキュメントはこちら。 -

    + +

    実行時間

    diff --git a/doc/index.html b/doc/index.html index 34fffda..8fbc58f 100755 --- a/doc/index.html +++ b/doc/index.html @@ -17,10 +17,22 @@

    Welcome to the pg_reorg Project Home Page


    +

    -pg_reorg can re-organize tables on a postgres database without any locks so that you can retrieve or update rows in tables being reorganized. +This project provides two tools for PostgreSQL; pg_reorg and pg_batch. +

    + +

    +pg_reorg can re-organize tables on a postgres database without any locks so that you can retrieve or update rows in tables being reorganized. The module is developed to be a better alternative of CLUSTER and VACUUM FULL.

    + +

    +pg_batch is a SQL job executor program for PostgreSQL. +It generates job list from an external SQL script file, and execute the jobs in serial or parallel. +It can be used as "a better vacuumdb" with the attached script to run VACUUM. +

    +

    The pg_reorg project is a PostgreSQL Community project that is a part of the pgFoundry.

    @@ -34,9 +46,11 @@ where you can find download

    Documentation

    -

    -Documentations here. -

    + +

    Execution time

    diff --git a/doc/pg_reorg-ja.html b/doc/pg_reorg-ja.html index e991190..ce1fced 100755 --- a/doc/pg_reorg-ja.html +++ b/doc/pg_reorg-ja.html @@ -8,7 +8,12 @@ -

    pg_reorg

    +

    pg_reorg 1.1.0

    +