diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c333e26d..ac717b0e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,6 +67,8 @@ set(SOURCES if (CMAKE_BUILD_TYPE MATCHES Debug) set(TS_DEBUG 1) set(DEBUG 1) + list(APPEND SOURCES + debug_guc.c) endif (CMAKE_BUILD_TYPE MATCHES Debug) include(build-defs.cmake) diff --git a/src/debug_guc.c b/src/debug_guc.c new file mode 100644 index 000000000..55ec911e7 --- /dev/null +++ b/src/debug_guc.c @@ -0,0 +1,215 @@ +/* + * This file and its contents are licensed under the Apache License 2.0. + * Please see the included NOTICE for copyright information and + * LICENSE-APACHE for a copy of the license. + */ +#include +#include +#include "debug_guc.h" + +#include +#include +#if PG10_GE +#include +#endif + +TSDLLEXPORT DebugOptimizerFlags ts_debug_optimizer_flags; + +enum DebugFlag +{ + DEBUG_FLAG_UPPER, + DEBUG_FLAG_REL +}; + +struct DebugFlagDef +{ + const char *name; + enum DebugFlag flag; +}; + +static struct DebugFlagDef g_flag_names[] = { + /* Show paths considered when planning a query. */ + { "show_upper_paths", DEBUG_FLAG_UPPER }, + /* Show relations generated when planning a query. */ + { "show_rel_pathlist", DEBUG_FLAG_REL }, +}; + +static unsigned long +get_show_upper_mask(const char *paths, size_t paths_len) +{ + unsigned long mask = 0UL; + const char *beg = paths; + + /* We can return early if there are no flags provided */ + if (paths_len == 0) + return mask; + + while (true) + { + const char *const maybe_end = strchr(beg, ','); + const char *const end = maybe_end == NULL ? paths + paths_len : maybe_end; + const size_t len = end - beg; + if (len > 0) + { + /* For each of the checks below, we check the provided string and + * allow a prefix to the full name, so "fin" will match + * "final". We have special support for "*" to denote setting all + * stages. */ + if (strncmp(beg, "*", len) == 0) + mask |= ~0UL; + else if (strncmp(beg, "setop", len) == 0) + mask |= STAGE_SETOP; +#if PG11_GE + else if (strncmp(beg, "partial_group_agg", len) == 0) + mask |= STAGE_PARTIAL_GROUP_AGG; +#endif + else if (strncmp(beg, "group_agg", len) == 0) + mask |= STAGE_GROUP_AGG; + else if (strncmp(beg, "window", len) == 0) + mask |= STAGE_WINDOW; + else if (strncmp(beg, "distinct", len) == 0) + mask |= STAGE_DISTINCT; + else if (strncmp(beg, "ordered", len) == 0) + mask |= STAGE_ORDERED; + else if (strncmp(beg, "final", len) == 0) + mask |= STAGE_FINAL; + else + { + char buf[20] = { 0 }; + char *ptr; + strncpy(buf, beg, sizeof(buf)); + + /* If the path name was long, make it clear that it is + * incomplete in the printout */ + if (buf[19] != '\0') + { + buf[19] = '\0'; + buf[18] = '.'; + buf[17] = '.'; + buf[16] = '.'; + } + + /* Terminate the path if it is followed by a comma */ + ptr = strchr(buf, ','); + if (ptr) + *ptr = '\0'; + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg_internal("unrecognized flag option \"%s\"", buf))); + } + } + if (maybe_end == NULL) + break; + beg = maybe_end + 1; + } + return mask; +} + +static bool +set_debug_flag(const char *flag_string, size_t length, DebugOptimizerFlags *flags) +{ + int i; + char *end; + size_t flag_length; + + if ((end = strchr(flag_string, '=')) != NULL) + { + Assert(end - flag_string >= 0); + flag_length = end - flag_string; + } + else + { + flag_length = length; + } + + for (i = 0; i < sizeof(g_flag_names) / sizeof(*g_flag_names); ++i) + if (strncmp(g_flag_names[i].name, flag_string, flag_length) == 0) + switch (g_flag_names[i].flag) + { + case DEBUG_FLAG_UPPER: + flags->show_upper = get_show_upper_mask(end + 1, length - flag_length - 1); + return true; + case DEBUG_FLAG_REL: + flags->show_rel = true; + return true; + } + return false; +} + +static bool +parse_optimizer_flags(const char *string, DebugOptimizerFlags *flags) +{ + char *rawname; + List *namelist; + ListCell *cell; + + Assert(string && flags); + + if (strlen(string) == 0) + return true; + + rawname = pstrdup(string); + if (!SplitIdentifierString(rawname, ':', &namelist)) + { + GUC_check_errdetail("Invalid flag string syntax."); + GUC_check_errhint("The flags string should be a list of colon-separated identifiers."); + pfree(rawname); + list_free(namelist); + return false; + } + + foreach (cell, namelist) + { + char *flag_string = (char *) lfirst(cell); + if (!set_debug_flag(flag_string, strlen(flag_string), flags)) + { + GUC_check_errdetail("Unrecognized flag setting \"%s\".", flag_string); + GUC_check_errhint("Allowed values are: show_upper_paths show_rel_pathlist"); + pfree(rawname); + list_free(namelist); + return false; + } + } + + pfree(rawname); + list_free(namelist); + return true; +} + +static bool +debug_optimizer_flags_check(char **newval, void **extra, GucSource source) +{ + DebugOptimizerFlags flags; + + Assert(newval); + + if (*newval) + return parse_optimizer_flags(*newval, &flags); + return true; +} + +static void +debug_optimizer_flags_assign(const char *newval, void *extra) +{ + if (newval && !parse_optimizer_flags(newval, &ts_debug_optimizer_flags)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("cannot parse \"%s\" as debug optimizer flags", newval))); +} + +void +ts_debug_init(void) +{ + static char *debug_optimizer_flags_string = NULL; + DefineCustomStringVariable("timescaledb.debug_optimizer_flags", + "List of optimizer debug flags", + "A list of flags for configuring the optimizer debug output.", + &debug_optimizer_flags_string, + NULL, + PGC_USERSET, + GUC_LIST_INPUT, + /* check_hook= */ debug_optimizer_flags_check, + /* assign_hook= */ debug_optimizer_flags_assign, + /* show_hook= */ NULL); +} diff --git a/src/debug_guc.h b/src/debug_guc.h new file mode 100644 index 000000000..3c55f9f60 --- /dev/null +++ b/src/debug_guc.h @@ -0,0 +1,50 @@ +/* + * This file and its contents are licensed under the Apache License 2.0. + * Please see the included NOTICE for copyright information and + * LICENSE-APACHE for a copy of the license. + */ +#ifndef TIMESCALEDB_DEBUG_GUC_H +#define TIMESCALEDB_DEBUG_GUC_H + +#include +#include +#include + +#include "compat.h" +#include "export.h" + +/* + * Enable printout inside ts_create_upper based on the stage provided. It is + * possible to enable printout for multiple stages, so we take the existing + * stage list and create a mask from it. + */ +#define STAGE_SETOP (1UL << UPPERREL_SETOP) /* Enabled using "setop" */ +#if PG11_GE +#define STAGE_PARTIAL_GROUP_AGG \ + (1UL << UPPERREL_PARTIAL_GROUP_AGG) /* Enabled using "partial_group_agg" */ +#endif +#define STAGE_GROUP_AGG (1UL << UPPERREL_GROUP_AGG) /* Enabled using "group_agg" */ +#define STAGE_WINDOW (1UL << UPPERREL_WINDOW) /* Enabled using "window" */ +#define STAGE_DISTINCT (1UL << UPPERREL_DISTINCT) /* Enabled using "distinct" */ +#define STAGE_ORDERED (1UL << UPPERREL_ORDERED) /* Enabled using "ordered" */ +#define STAGE_FINAL (1UL << UPPERREL_FINAL) /* Enabled using "final" */ + +/* + * Debug flags for the optimizer. + * + * Add new flags here as you see fit, but don't forget to update the flag list + * `flag_names` in guc.c. + */ +typedef struct DebugOptimizerFlags +{ + /* Bit mask to represent set of UpperRelationKind, which is used as the + * stage inside create_upper. */ + unsigned long show_upper; + bool show_rel; +} DebugOptimizerFlags; + +extern TSDLLEXPORT DebugOptimizerFlags ts_debug_optimizer_flags; + +extern void ts_debug_init(void); + +#endif /* TIMESCALEDB_DEBUG_GUC_H */ diff --git a/src/init.c b/src/init.c index f06c83ca6..be5304393 100644 --- a/src/init.c +++ b/src/init.c @@ -14,6 +14,7 @@ #include "extension.h" #include "bgw/launcher_interface.h" #include "guc.h" +#include "debug_guc.h" #include "catalog.h" #include "version.h" #include "compat.h" @@ -89,6 +90,7 @@ _PG_init(void) #endif #ifdef TS_DEBUG _conn_mock_init(); + ts_debug_init(); #endif } diff --git a/test/expected/guc_options.out b/test/expected/guc_options.out new file mode 100644 index 000000000..b0badf3df --- /dev/null +++ b/test/expected/guc_options.out @@ -0,0 +1,111 @@ +-- This file and its contents are licensed under the Apache License 2.0. +-- Please see the included NOTICE for copyright information and +-- LICENSE-APACHE for a copy of the license. +\c :TEST_DBNAME :ROLE_SUPERUSER +SET timescaledb.debug_optimizer_flags = ''; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=final'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=final +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=fin'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=fin +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=fin,win'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=fin,win +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=*,fin,win'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=*,fin,win +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=*'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=* +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=win:show_rel'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=win:show_rel +(1 row) + +SET timescaledb.debug_optimizer_flags = '"show_upper=win":show_rel'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + "show_upper=win":show_rel +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_upper=win : show_rel'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_upper=win : show_rel +(1 row) + +SET timescaledb.debug_optimizer_flags = 'show_rel:show_upper=win'; +SHOW timescaledb.debug_optimizer_flags; + timescaledb.debug_optimizer_flags +----------------------------------- + show_rel:show_upper=win +(1 row) + +-- These should all fail +\set ON_ERROR_STOP 0 +SET timescaledb.debug_optimizer_flags = NULL; +ERROR: syntax error at or near "NULL" at character 41 +SET timescaledb.debug_optimizer_flags = 'invalid'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": "invalid" +SET timescaledb.debug_optimizer_flags = '"unmatched quote:'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": ""unmatched quote:" +SET timescaledb.debug_optimizer_flags = 'space between'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": "space between" +SET timescaledb.debug_optimizer_flags = 'space between:'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": "space between:" +SET timescaledb.debug_optimizer_flags = 'show_rel:invalid'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": "show_rel:invalid" +SET timescaledb.debug_optimizer_flags = 'invalid:show_rel'; +ERROR: invalid value for parameter "timescaledb.debug_optimizer_flags": "invalid:show_rel" +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=fin,xxx'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,fin'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=win,xxx,fin'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=*,xxx'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,*'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,*,yyy'; +ERROR: unrecognized flag option "xxx" +SET timescaledb.debug_optimizer_flags = 'show_upper=supercalifragilisticexpialidochious'; +ERROR: unrecognized flag option "supercalifragili..." +SET timescaledb.debug_optimizer_flags = 'show_upper=super,califragilisticexpialidochious'; +ERROR: unrecognized flag option "super" +\set ON_ERROR_STOP 1 diff --git a/test/sql/CMakeLists.txt b/test/sql/CMakeLists.txt index e9d8174d2..add763efe 100644 --- a/test/sql/CMakeLists.txt +++ b/test/sql/CMakeLists.txt @@ -79,6 +79,7 @@ if (CMAKE_BUILD_TYPE MATCHES Debug) bgw_launcher.sql bgw_db_scheduler.sql c_unit_tests.sql + guc_options.sql loader.sql metadata.sql net.sql diff --git a/test/sql/guc_options.sql b/test/sql/guc_options.sql new file mode 100644 index 000000000..005620c5c --- /dev/null +++ b/test/sql/guc_options.sql @@ -0,0 +1,46 @@ +-- This file and its contents are licensed under the Apache License 2.0. +-- Please see the included NOTICE for copyright information and +-- LICENSE-APACHE for a copy of the license. + +\c :TEST_DBNAME :ROLE_SUPERUSER +SET timescaledb.debug_optimizer_flags = ''; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=final'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=fin'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=fin,win'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=*,fin,win'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=*'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=win:show_rel'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = '"show_upper=win":show_rel'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_upper=win : show_rel'; +SHOW timescaledb.debug_optimizer_flags; +SET timescaledb.debug_optimizer_flags = 'show_rel:show_upper=win'; +SHOW timescaledb.debug_optimizer_flags; + +-- These should all fail +\set ON_ERROR_STOP 0 +SET timescaledb.debug_optimizer_flags = NULL; +SET timescaledb.debug_optimizer_flags = 'invalid'; +SET timescaledb.debug_optimizer_flags = '"unmatched quote:'; +SET timescaledb.debug_optimizer_flags = 'space between'; +SET timescaledb.debug_optimizer_flags = 'space between:'; +SET timescaledb.debug_optimizer_flags = 'show_rel:invalid'; +SET timescaledb.debug_optimizer_flags = 'invalid:show_rel'; +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx'; +SET timescaledb.debug_optimizer_flags = 'show_upper=fin,xxx'; +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,fin'; +SET timescaledb.debug_optimizer_flags = 'show_upper=win,xxx,fin'; +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx'; +SET timescaledb.debug_optimizer_flags = 'show_upper=*,xxx'; +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,*'; +SET timescaledb.debug_optimizer_flags = 'show_upper=xxx,*,yyy'; +SET timescaledb.debug_optimizer_flags = 'show_upper=supercalifragilisticexpialidochious'; +SET timescaledb.debug_optimizer_flags = 'show_upper=super,califragilisticexpialidochious'; +\set ON_ERROR_STOP 1